web-dev-qa-db-fra.com

Logger avec mockito en java

J'essaie de vérifier le message de l'enregistreur avec Mokito. 
Cependant, je ne peux pas exécuter ma classe Junit pour couvrir toutes les lignes de code. Avez-vous la raison?

Mon code:

    public class App {
    private static final Logger LOGGER = Logger.getLogger(App.class);

    public List<String> addToListIfSizeIsUnder3(final List<String> list, final String value) {
        if (list == null) {
            LOGGER.error("A null list was passed in");
            return null;
        }
        if (list.size() < 3) {
            list.add(value);
        } else {
            LOGGER.debug("The list already has {} entries"+ list.size());
        }
        return list;
    }
}

=========================================

Mon cours de junit

import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import Java.util.ArrayList;
import Java.util.List;

import org.Apache.log4j.Appender;
import org.Apache.log4j.Level;
import org.Apache.log4j.Logger;
import org.Apache.log4j.spi.LoggingEvent;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class AppTest {
    private App uut;
    @Mock
    private Appender mockAppender;
    @Captor
    private ArgumentCaptor<LoggingEvent> captorLoggingEvent;

    @Before
    public void setup() {
        uut = new App();

        Logger root = Logger.getRootLogger();
        root.addAppender(mockAppender);
        root.setLevel(Level.INFO);
    }

    /**
     * I want to test with over 3 elements.
     */
    @Test
    public void testWithOver3Element() {
        List<String> myList = new ArrayList<String>();
        myList.add("value 1");
        myList.add("value 2");
        myList.add("value 3");
        myList.add("value 4");
        List<String> outputList = uut.addToListIfSizeIsUnder3(myList, "some value");
        Assert.assertEquals(4, outputList.size());
        Assert.assertFalse(myList.contains("some value"));

try {
            verify(mockAppender, times(1)).doAppend(captorLoggingEvent.capture());
        } catch (AssertionError e) {
            e.printStackTrace();
        }

        LoggingEvent loggingEvent = captorLoggingEvent.getAllValues().get(0);
        Assert.assertEquals("The list already has {} entries", loggingEvent.getMessage());
        Assert.assertEquals(Level.DEBUG, loggingEvent.getLevel());
    }
}

ERREUR:

Recherché mais non appelé: mockAppender.doAppend (); -> à AppTest.testWithOver3Element (AppTest.Java:52) En fait, il n'y a eu aucune interaction avec ce modèle.

à AppTest.testWithOver3Element (AppTest.Java:52) à Sun.reflect.NativeMethodAccessorImpl.invoke0 (Méthode native) à Sun.reflect.NativeMethodAccessorImpl.invoke (Source inconnue) à Sun.reflect.DelegatingMethodAccessorImpl.invoke (Source inconnue) à Java.lang.reflect.Method.invoke (Source inconnue) à org.junit.runners.model.FrameworkMethod $ 1.runReflectiveCall (FrameworkMethod.Java:47) à org.junit.internal.runners.model.ReflectiveCallable.run (ReflectiveCallable.Java:12) à org.junit.runners.model.FrameworkMethod.invokeExplosively (FrameworkMethod.Java:44) à org.junit.internal.runners.statements.InvokeMethod.evaluate (InvokeMethod.Java:17) à org.junit.internal.runners.statements.RunBefores.evaluate (RunBefores.Java:26) à org.junit.runners.ParentRunner.runLeaf (ParentRunner.Java:271) à org.junit.runners.BlockJUnit4ClassRunner.runChild (BlockJUnit4ClassRunner.Java:70) à org.junit.runners.BlockJUnit4ClassRunner.runChild (BlockJUnit4ClassRunner.Java:50) à org.junit.runners.ParentRunner $ 3.run (ParentRunner.Java:238) à org.junit.runners.ParentRunner $ 1.schedule (ParentRunner.Java:63) à org.junit.runners.ParentRunner.runChildren (ParentRunner.Java:236) à l'adresse org.junit.runners.ParentRunner.access $ 000 (ParentRunner.Java:53) à l'adresse org.junit.runners.ParentRunner $ 2.evaluate (ParentRunner.Java:229) à org.junit.runners.ParentRunner.run (ParentRunner.Java:309) à l'adresse org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run (JUnit45AndHigherRunnerImpl.Java:37) à org.mockito.runners.MockitoJUnitRunner.run (MockitoJUnitRunner.Java:62) à org.Eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run (JUnit4TestReference.Java:86) à org.Eclipse.jdt.internal.junit.runner.TestExecution.run (TestExecution.Java:38) à org.Eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests (RemoteTestRunner.Java:459) à org.Eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests (RemoteTestRunner.Java:678) à org.Eclipse.jdt.internal.junit.runner.RemoteTestRunner.run (RemoteTestRunner.Java:382) à org.Eclipse.jdt.internal.junit.runner.RemoteTestRunner.main (RemoteTestRunner.Java:192)

5
user3205761

Vous pouvez faire plusieurs choses pour améliorer votre code:

  1. Basculez vers slf4j. Cela vous permettra de coder sur une interface et d’être agnostique à l’égard de la mise en œuvre de la journalisation (Apache log4j dans votre cas).

  2. Le passage à slf4j vous permettra de ne pas avoir à concaténer des chaînes lors du passage au cadre de journalisation - exemple: 

    • cette instruction: LOGGER.debug ("La liste a déjà {} entrées" + list.size ());
    • peut être écrit comme ceci: LOGGER.debug ("La liste a déjà {} entrées", list.size ());

Cela présente l’avantage supplémentaire de faire fonctionner le détenteur d’emplacement dans la chaîne littérale.

  1. Vous tentez d'assigner et de capturer des appels à un objet indirectement via le cadre de journalisation. Cela sera fragile et sujet aux erreurs car vous ne savez jamais quels appels seront passés en interne dans le cadre de journalisation. Ne vous moquez que de vos dépendances directes.

  2. Ne testez pas les instructions de journalisation. Ce n'est pas exactement le comportement de la classe et cela rend le test fragile et compliqué. De plus, traiter les instructions de journalisation comme si vous utilisiez un ArrayList (c’est-à-dire une partie du langage) leur permet de s’exercer pleinement ET de transmettre des informations à la console qui peuvent être utiles pour déboguer un test ayant échoué. Par exemple, si vous modifiez l'instruction de journalisation pour ajouter plus d'informations ou si vous ajoutez une autre instruction de journalisation à la méthode, ce test pourrait échouer sans raison valable. À tout le moins, n'affirmez pas le nombre de fois appelé car cela sera extrêmement fragile.

Cela dit, si vous devez tester les interactions avec le framework de journalisation, voici une version modifiée de votre code qui s'exécute et fournit les mêmes fonctionnalités. C’est essentiellement l’option n ° 3 de la liste des améliorations - 

package com.spring.mockito;

import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import org.Apache.log4j.Logger;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;

import Java.util.Arrays;
import Java.util.List;

@RunWith(MockitoJUnitRunner.class)
public class AppTest {

    // create a mock of the logger
    @Mock
    private Logger logger;

    private App uut;

    // Not needed - dont test something that gets called through something else
    // @Captor
    // private ArgumentCaptor<LoggingEvent> captorLoggingEvent;

    @Before
    public void setup() {
        // spy the class under test so we can override the logger method to return our mock logger
        uut = spy(new App());
        when(uut.logger()).thenReturn(logger);

        // Not needed test with the mock directly.
        // Logger root = Logger.getRootLogger();
        // root.addAppender(mockAppender);
        // root.setLevel(Level.DEBUG);
    }

    /**
     * I want to test with over 3 elements.
     */
    @Test
    public void testWithOver3Element() {
        List<String> myList = Arrays.asList("value 1", "value 2", "value 3", "value 4");

        List<String> outputList = uut.addToListIfSizeIsUnder3(myList, "some value");

        Assert.assertEquals(4, outputList.size());
        Assert.assertFalse(myList.contains("some value"));
        verify(logger, times(1)).debug("The list already has {} entries4");

        // not needed
        // try {
        // verify(mockAppender, times(1)).doAppend(captorLoggingEvent.capture());
        // } catch (AssertionError e) {
        // e.printStackTrace();
        // }
        //
        // LoggingEvent loggingEvent = captorLoggingEvent.getAllValues().get(0);
        // Assert.assertEquals("The list already has {} entries", loggingEvent.getMessage());
        // Assert.assertEquals(Level.DEBUG, loggingEvent.getLevel());
    }

    public static class App {
        private static final Logger LOGGER = Logger.getLogger(App.class);

        public List<String> addToListIfSizeIsUnder3(final List<String> list, final String value) {
            if (list == null) {
                logger().error("A null list was passed in");
                return null;
            }
            if (list.size() < 3) {
                list.add(value);
            } else {
                // if you use slf4j this concatenation is not needed
                logger().debug("The list already has {} entries" + list.size());
            }
            return list;
        }

        // make a package private method for testing purposes to allow you to inject a mock
        Logger logger() {
            return LOGGER;
        }
    }
}

Vous pouvez également consulter des packages tels que PowerMockito pour les statiques moqueuses, mais uniquement si cela est absolument nécessaire.

J'espère que cela t'aides.

9
Bob Lukens

Votre annonceur ne se fait pas virer parce que vous l'avez défini au niveau INFO, mais votre code appelle LOGGER.debug. DEBUG est un niveau de journalisation inférieur à INFO, ainsi votre éditeur ne le verra pas. 

1
Dawood ibn Kareem

Votre problème est qu’il existe des incohérences entre vos tests et votre code de production.

Le test passe une liste de 3 éléments et le code de production indique que le deuxième argument est pas ajouté. Mais votre cas de test s'attend à ce qu'il soit ajouté!

La première chose à faire est donc de vous assurer que vos tests couvrent réellement le comportement que vous avez décrit dans le code de production que vous montrez.

Ensuite, je me demande pourquoi votre test fonctionne avec un véritable enregistreur ici; Je pense qu'il serait beaucoup plus facile pour vous de fournir un mocked logger et de spécifier les appels attendus pour cela!

0
GhostCat