web-dev-qa-db-fra.com

Enregistrer les messages dans Android test de junit studio

Existe-t-il un moyen d'imprimer les messages Logcat (Log.i, Log.d) lors de l'exécution d'un test JUnit (méthode) dans Android Studio?

Je peux voir le message System.out.print mais aucune impression logcat.

Dans la configuration d'exécution (fenêtre GUI de Android Studio), il existe des options logcat pour les tests sous Android tests mais pas pour les tests JUnit).

Est-ce possible d'une manière ou d'une autre? Merci pour tout indice!

17
daniel

La sortie Logcat ne sera pas vue dans les tests unitaires car Logcat est Android - Les tests JUnit peuvent utiliser uniquement Java standard, donc Android ne fonctionneront pas.

Ce que vous pouvez faire dans les tests unitaires, c'est injecter des "tests doubles" dans les composants testés. Mais Log.x les appels sont statiques, vous ne pouvez donc pas les remplacer (sans passer par exemple à PowerMock, que vous devez éviter à tout prix).

Par conséquent, la première étape consistera à introduire une classe non statique qui se comportera comme un proxy pour Log.x appels:

/**
 * 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);
    }
}

Utilisez cette classe partout où vous avez Log.x appelle maintenant.

La deuxième étape serait d'écrire une implémentation test-double de Logger qui redirige vers la sortie standard:

public class UnitTestLogger extends Logger{

    @Override
    public void e(String tag, String message) {
        System.out.println("E " + tag + ": " + message);
    }

    // similar for other methods
}

La dernière étape consiste à injecter UnitTestLogger au lieu de Logger dans les tests unitaires:

@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);
    }

}

Si vous voulez être rigoureusement strict sur les concepts OOP, vous pouvez extraire une interface commune pour Logger et UnitTestLogger.

Cela dit, je n'ai jamais rencontré le besoin d'enquêter Log.x appelle dans les tests unitaires. Je soupçonne que vous n'en avez pas besoin non plus. Vous pouvez exécuter des tests unitaires en mode débogage et parcourir le code ligne par ligne dans le débogueur, ce qui est beaucoup plus rapide que d'essayer d'étudier la sortie logcat ...

Conseils généraux:

Si le code que vous testez contient Log.x les appels statiques et vos tests unitaires ne se bloquent pas - vous avez un problème.

Je suppose que soit tous les tests sont exécutés avec Robolectric, soit vous avez cette instruction dans build.gradle: unitTests.returnDefaultValues = true.

Si vous exécutez tous les tests avec Robolectric, alors c'est juste inefficace, mais si tous les appels Android renvoient des valeurs par défaut, votre suite de tests n'est pas fiable. Je vous suggère de corriger ce problème avant de poursuivre, car il vous mordra à l'avenir d'une manière ou d'une autre.

9
Vasiliy

Je cherchais cette même chose et je n'ai jamais trouvé de réponse directe. Je sais que cette question remonte à plus d'un an, mais il serait bon d'avoir une réponse ici pour référence future.

La classe Android.util.Log se connecte directement à Logcat et l'implémentation d'Android.util.Log n'est pas disponible lors de l'exécution des tests unitaires sur la machine virtuelle Java locale. Vous recevrez une erreur lorsque vous essayez d'utiliser la classe Log dans vos tests unitaires, car "le fichier Android.jar utilisé pour exécuter les tests unitaires ne contient aucun code réel (ces API ne sont fournies que par le Android image système sur un appareil). "
Voir Android Documentation sur les tests unitaires

Donc, si vous voulez vraiment utiliser Android.util.Log, vous devrez vous en moquer localement et utiliser System.out.print pour imprimer sur la console. Pour commencer, ajoutez PowerMockito à votre projet. Si vous utilisez Gradle, vous pouvez simplement ajouter les dépendances suivantes:

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'

Ensuite, j'ai utilisé la réponse de Steve ici pour comprendre comment retourner un paramètre passé dans un objet factice en utilisant Mockito.

Le résultat était quelque chose comme:

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");
    }
}

J'espère que cela aide quelqu'un!

2
user3474985

Si vous préférez toujours utiliser la solution de piratage (à l'aide de PowerMockito), j'ai implémenté la classe suivante pour se moquer de toutes les fonctions de journalisation Android 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));
    }
}

avec importations:

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'

pour l'utilisation, étendez votre classe 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"));
    }

}

Mais @Vasiliy est juste, en suivant la méthode OOP est plus propre.

1
Lucien Zürcher

J'irais avec l'approche de @ Vasiliy mais avec un petit changement. À la place d'utiliser System.out.println pour la journalisation, nous pouvons réellement utiliser Java Logger configuré avec ConsoleHandler pour enregistrer les messages sur l'écran de sortie de test.

Ceci peut être réalisé dans les étapes simples énumérées ci-dessous

Étape 1: Définissez votre propre niveau de journal

  public enum LogLevel {
    VERBOSE,
    DEBUG,
    INFO,
    WARNING,
    ERROR,
    SILENT
}

Étape 2: Définir l'abstraction de l'enregistreur

public abstract class Logger {

    ....

    public abstract void debug(String tag, String message);

    public abstract void error(String tag, String message);
   ....

}

Étape 3: Implémentation basée sur Java.util.Logging pour Logger

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;
    }
}

Étape 4: Implémentation basée sur Android.util.Log pour 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);
    }
}

....

}

Étape 5: Créez une classe wrapper simple pour l'implémentation de Logger

   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);
    }

    ... 
}

Étape 6: Initialisation

Android

Application de la méthode onCreate

AppLogger.init(new AndroidLogger(LogLevel.DEBUG));

Junit

AppLogger peut être initialisé dans @BeforeClass ou @Before

AppLogger.init(new JavaLogger(LogLevel.DEBUG, "test-logger"));

Vous pouvez maintenant observer le message du journal dans la fenêtre de test de Android Studio

1
vijay_t