Android Studio)でJUnit(メソッド)テストを実行するときにLogcat(Log.i、Log.d)メッセージを出力する方法はありますか?
System.out.printメッセージは表示されますが、logcatプリントアウトは表示されません。
実行構成(Android StudioのGUIウィンドウ)には、Androidテストの下にテスト用のlogcatオプションがありますが、JUnitテスト用ではありません。
これはどういうわけか可能ですか?ヒントをありがとう!
LogcatはAndroid機能-JUnitテストは標準のJavaのみを使用できるため、Android機能は機能しません。
単体テストでできることは、「テストダブル」をテスト済みコンポーネントに注入することです。ただし、Log.x
の呼び出しは静的なので、オーバーライドすることはできません(たとえば、PowerMockに解決しないと、すべてのコストで回避する必要があります)。
したがって、最初のステップは、Log.x
呼び出しのプロキシとして動作する非静的クラスを導入することです。
/**
* This class is a non-static logger
*/
public class Logger {
public void e(String tag, String message) {
Log.e(tag, message);
}
public void w(String tag, String message) {
Log.w(tag, message);
}
public void v(String tag, String message) {
Log.v(tag, message);
}
public void d(String tag, String message) {
Log.d(tag, message);
}
}
このクラスは、現在Log.x
を呼び出すすべての場所で使用できます。
2番目のステップは、標準出力にリダイレクトするLogger
のtest-double実装を作成することです。
public class UnitTestLogger extends Logger{
@Override
public void e(String tag, String message) {
System.out.println("E " + tag + ": " + message);
}
// similar for other methods
}
最後のステップは、単体テストでUnitTestLogger
ではなくLogger
を注入することです。
@RunWith(MockitoJUnitRunner.class)
public class SomeClassTest {
private Logger mLogger = new UnitTestLogger();
private SomeClass SUT;
@Before
public void setup() throws Exception {
SUT = new SomeClass(/* other dependencies here */ mLogger);
}
}
OOPの概念について厳密に厳密にしたい場合は、Logger
およびUnitTestLogger
の共通インターフェースを抽出できます。
とはいえ、単体テストでLog.x
の呼び出しを調査する必要はありませんでした。あなたもそれを必要としないと思います。デバッグモードで単体テストを実行し、デバッガーでコードを1行ずつステップオーバーできます。これは、logcat出力を調査するよりもはるかに高速です...
一般的なアドバイス:
テストしているコードにLog.x
静的呼び出しが含まれていて、ユニットテストがクラッシュしない場合-問題があります。
すべてのテストがRobolectric
で実行されているか、build.gradleに次のステートメントがあると思います:unitTests.returnDefaultValues = true
。
すべてのテストをRobolectric
で実行する場合、それは単に非効率的ですが、すべてのAndroid呼び出しがデフォルト値を返す場合、テストスイートは信頼できません。修正することをお勧めしますこの問題は、先に進む前に、何らかの形で将来的にあなたを噛むためです。
私はこれと同じことを探していましたが、正解は見つかりませんでした。私はこの質問が1年以上前であることを知っていますが、それでも、今後の参考のためにここで回答を用意しておくとよいでしょう。
Android.util.LogクラスはLogcatに直接ログを記録し、Android.util.Logの実装は、ローカルJVMでユニットテストを実行しているときには使用できません。ユニットテストでLogクラスを使用しようとすると、エラーが発生します。「ユニットテストの実行に使用されるAndroid.jarファイルには実際のコードが含まれていないため、これらのAPIはAndroidデバイス上のシステムイメージ)。」
Androidユニットテストに関するドキュメント)を参照
したがって、Android.util.Logを本当に使用したい場合は、ローカルでモックアップし、System.out.printを使用してコンソールに出力する必要があります。まず、PowerMockitoをプロジェクトに追加します。 Gradleを使用している場合は、次の依存関係を追加するだけです。
testCompile 'junit:junit:4.12'
testCompile 'org.powermock:powermock:1.6.5'
testCompile 'org.powermock:powermock-module-junit4:1.6.5'
testCompile 'org.powermock:powermock-api-mockito:1.6.5'
次に、Steveの回答 here を使用して、Mockitoを使用してモックオブジェクトに渡されたパラメーターを返す方法を理解しました。
結果は次のようなものでした:
import Android.util.Log;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import static org.mockito.Matchers.anyString;
import static org.powermock.api.mockito.PowerMockito.when;
@RunWith(PowerMockRunner.class)
@PrepareForTest({Log.class})
public class SomeUnitTest {
@Test
public void testSomething() {
System.out.println("Running test");
PowerMockito.mockStatic(Log.class);
// Log warnings to the console
when(Log.w(anyString(), anyString())).thenAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
if (args.length > 1) { //cause I'm paranoid
System.out.println("Tag:" + args[0] + " Msg: " + args[1]);
}
return null;
}
});
Log.w("My Tag", "This is a warning");
}
}
これが誰かを助けることを願っています!
まだハッキングソリューション(PowerMockitoを使用)を使用することに慣れている場合は、次のクラスを実装して、すべてのAndroidログ関数をモックします。
import Android.util.Log;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import Java.util.HashMap;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.anyString;
import static org.powermock.api.mockito.PowerMockito.when;
@RunWith(PowerMockRunner.class)
@PrepareForTest({Log.class})
public abstract class AndroidLogMock {
private static HashMap<Integer, String> LOG_LEVELS = new HashMap<Integer, String>(){{
put(Log.VERBOSE, "VERBOSE");
put(Log.DEBUG, "DEBUG");
put(Log.INFO, "INFO");
put(Log.WARN, "WARN");
put(Log.ERROR, "ERROR");
put(Log.ASSERT, "ASSERT");
}};
private static Answer<?> getLogInvocation(int logLevel) {
return (InvocationOnMock invocation) -> {
Object[] args = invocation.getArguments();
if(args.length > 1 && args.length < 3) {
AndroidLogMock.log(logLevel, args[0].toString(), args[1].toString());
} else if (args.length > 1 && args[2] instanceof Throwable) { //cause I'm paranoid
AndroidLogMock.log(logLevel, args[0].toString(), args[1].toString(), (Throwable) args[2]);
} else {
new Exception("no matching function found with correct number and/or type of arguments");
}
return null;
};
}
private static void log(int logLevel, String tag, String message) {
System.out.println("[" + LOG_LEVELS.get(logLevel) + "}" + " Tag:" + tag + " Msg: " + message);
}
private static void log(int logLevel, String tag, String message, Throwable throwable) {
AndroidLogMock.log(logLevel, tag, message);
System.out.println("Exception: ");
throwable.printStackTrace();
}
@BeforeClass
public static void setupLogMocks() {
PowerMockito.mockStatic(Log.class);
when(Log.v(anyString(), anyString())).thenAnswer(AndroidLogMock.getLogInvocation(Log.VERBOSE));
when(Log.v(anyString(), anyString(), any(Throwable.class))).thenAnswer(AndroidLogMock.getLogInvocation(Log.VERBOSE));
when(Log.d(anyString(), anyString())).thenAnswer(AndroidLogMock.getLogInvocation(Log.DEBUG));
when(Log.d(anyString(), anyString(), any(Throwable.class))).thenAnswer(AndroidLogMock.getLogInvocation(Log.DEBUG));
when(Log.i(anyString(), anyString())).thenAnswer(AndroidLogMock.getLogInvocation(Log.INFO));
when(Log.i(anyString(), anyString(), any(Throwable.class))).thenAnswer(AndroidLogMock.getLogInvocation(Log.INFO));
when(Log.w(anyString(), anyString())).thenAnswer(AndroidLogMock.getLogInvocation(Log.WARN));
when(Log.w(anyString(), anyString(), any(Throwable.class))).thenAnswer(AndroidLogMock.getLogInvocation(Log.WARN));
when(Log.e(anyString(), anyString())).thenAnswer(AndroidLogMock.getLogInvocation(Log.ERROR));
when(Log.e(anyString(), anyString(), any(Throwable.class))).thenAnswer(AndroidLogMock.getLogInvocation(Log.ERROR));
when(Log.wtf(anyString(), anyString())).thenAnswer(AndroidLogMock.getLogInvocation(Log.ASSERT));
when(Log.wtf(anyString(), anyString(), any(Throwable.class))).thenAnswer(AndroidLogMock.getLogInvocation(Log.ASSERT));
}
}
インポートあり:
testImplementation 'junit:junit:4.12'
testImplementation 'org.powermock:powermock-core:2.0.4'
testImplementation 'org.powermock:powermock-module-junit4:2.0.4'
testImplementation 'org.powermock:powermock-api-mockito2:2.0.4'
使用方法については、UnitTestクラスを拡張します。
public class SomeUnitTest extends AndroidLogMock {
@Test
public void testSomething() {
Log.w("My Tag", "This is a warning");
Log.e("My Tag", "This is an error", new Exception("Error"));
}
}
しかし、@ Vasiliyは厳密であり、OOPの方法に従うとよりクリーンになります。
@Vasiliyのアプローチを使用しますが、小さな変更を加えます。ログにSystem.out.println
を使用する代わりに、実際に Java Logger を ConsoleHandler で構成して、テスト出力画面にメッセージを記録できます。
これは、以下にリストされた簡単なステップで達成できます
ステップ1:独自のログレベルを定義します
public enum LogLevel {
VERBOSE,
DEBUG,
INFO,
WARNING,
ERROR,
SILENT
}
ステップ2:ロガー抽象化を定義します
public abstract class Logger {
....
public abstract void debug(String tag, String message);
public abstract void error(String tag, String message);
....
}
ステップ3:ロガーのJava.util.Loggingベースの実装
public class JavaLogger extends Logger {
private Java.util.logging.Logger mLogger;
public JavaLogger(LogLevel logLevel, String name) {
mLogger = Java.util.logging.Logger.getLogger(name);
ConsoleHandler handler = new ConsoleHandler();
Level level = mapLogLevel(logLevel);
handler.setLevel(level);
mLogger.addHandler(handler);
mLogger.setLevel(level);
}
@Override
public void debug(String tag, String message) {
if (isLoggable(LogLevel.DEBUG)) {
mLogger.finer(message);
}
}
@Override
public void error(String tag, String message) {
if (isLoggable(LogLevel.ERROR)) {
mLogger.severe(message);
}
}
....
private Level mapLogLevel(LogLevel logLevel) {
Level level = Level.OFF;
switch (logLevel) {
case INFO:
level = Level.INFO;
break;
case WARNING:
level = Level.WARNING;
break;
case ERROR:
level = Level.SEVERE;
break;
case VERBOSE:
level = Level.FINEST;
break;
case DEBUG:
level = Level.FINER;
break;
case SILENT:
level = Level.OFF;
break;
default:
// no impl
}
return level;
}
}
ステップ4:Android.util.LogベースのLoggerの実装
public class AndroidLogger extends Logger {
public AndroidLogger(LogLevel logLevel) {
super(logLevel);
}
....
@Override
public void debug(String tag, String message) {
if (isLoggable(LogLevel.DEBUG)) {
Log.d(tag, message, null);
}
}
@Override
public void error(String tag, String message) {
if (isLoggable(LogLevel.ERROR)) {
Log.e(tag, message, tr);
}
}
....
}
ステップ5:オーバーロガー実装用の単純なラッパークラスを作成します
public class AppLogger {
private static Logger sLogger;
public static void init(Logger logger) {
sLogger = logger;
}
public static void debug(final String tag, String message) {
sLogger.debug(tag, message);
}
public static void error(final String tag, String message) {
sLogger.error(tag, message, null);
}
public static void error(final String tag, String message, Throwable t) {
sLogger.error(tag, message, t);
}
...
}
ステップ6:初期化
Android
アプリケーションのonCreateメソッド
AppLogger.init(new AndroidLogger(LogLevel.DEBUG));
Junit
AppLoggerは、@ BeforeClassまたは@Beforeのいずれかで初期化できます。
AppLogger.init(new JavaLogger(LogLevel.DEBUG, "test-logger"));
Android Studioのテスト実行ウィンドウでログメッセージを確認できます