[JAVA] Implémenter le test double dans JUnit sans utiliser de bibliothèques externes

Qu'est-ce qu'un test double?

Un mot dérivé du mot anglais «double», qui signifie un substitut pour un interprète ou un guerrier de l'ombre. Généralement appelé mock ou stub.

Pourquoi utiliser Test Double

Dans le test unitaire du système, la cible du test est Il y a peu de cas où cela ne dépend pas d'autres classes ou d'éléments externes. Les tests dépendants sont des tests non seulement sur le sujet, mais également sur la dépendance.

L'avantage est que le test est effectué dans le même état que lorsque le système est en cours d'exécution. Il y a un aspect que la valeur du contenu garanti par les résultats des tests augmente. D'autre part, l'inconvénient est que la portée de l'analyse des causes s'élargit lorsque le test échoue. Si le résultat du traitement de l'objet dépendant ne peut pas être prédit, la précision de la vérification diminuera.

En guise de contre-mesure contre les inconvénients ci-dessus, au lieu des facteurs qui affectent le test Il existe un moyen d'augmenter l'indépendance du test en utilisant un substitut facile à tester. C'est l'utilisation de stubs et de faux (test double) pour y parvenir.

Différence entre stub et mock

Les deux sont utilisés comme substituts des classes et des modules dont dépend la cible du test. Il n'y a pas de différence en termes de classes et de modules. Par conséquent, il est souvent utilisé comme synonyme, mais dans un sens étroit, il existe les différences suivantes.

bout

** Le but est de faire en sorte que l'objet dépendant se comporte de manière prévisible **.

De plus, il est utilisé comme substitut aux objets suivants.

--Comportement imprévisible

moquer

** Le but est de vérifier si l'objet dépendant a été appelé lors de l'exécution **.

Divers doubles tests

Stub qui renvoie une valeur fixe

Traitement difficile à obtenir la valeur souhaitée, comme les nombres aléatoires et les dates système, L'extraction en tant que méthode et classe et la commutation avec un stub facilitent le test. Il existe différentes façons d'implémenter les stubs, mais les détails sont "[Méthode de refactorisation dans JUnit](https://qiita.com/maple_syrup/items/c370f3f850972aafd101#junit%E3%81%AB%E3%81%8A%E3" % 81% 91% E3% 82% 8B% E3% 83% AA% E3% 83% 95% E3% 82% A1% E3% 82% AF% E3% 82% BF% E3% 83% AA% E3% 83 % B3% E3% 82% B0% E3% 81% AE% E6% 89% 8B% E6% B3% 95) ".

Dans l'exemple suivant, le processus d'acquisition de nombres aléatoires est coupé en tant que méthode. Vous avez créé et remplacé une méthode stub dans votre code de test.

Code de production


public class GetRandomElementFromList {
	public <T> T choice(List<T> elements) {
		if (elements.size() == 0) {
			return null;
		}
		int idx = nextInt() % elements.size();
		return elements.get(idx);
	}

	int nextInt() {
		return new Random().nextInt();
	}
}

Code de test


public class GetRandomElementFromListTest {
	private List<String> elements = new ArrayList<>();
	
	@Before
	public void setUp() {
		elements.add("A");
		elements.add("B");
	}

	@Test
renvoie A avec le choix public void() {
		GetRandomElementFromList sut = new GetRandomElementFromList() {
			@Override
			int nextInt() {
				return 0;
			}
		};
		assertThat(sut.choice(elements), is("A"));
	}
	
	@Test
Renvoie B avec le choix public void() {
		GetRandomElementFromList sut = new GetRandomElementFromList() {
			@Override
			int nextInt() {
				return 1;
			}
		};
		assertThat(sut.choice(elements), is("B"));
	}
}

Stub lançant une exception

Il existe de nombreux cas dans lesquels il est difficile de déclencher une exception, telle qu'une erreur de connexion à la base de données. Même dans un tel cas, une méthode qui lève une exception En le coupant en petits morceaux, il est possible de passer par talon.

Spy: une maquette qui surveille les appels d'objets

Le test unitaire de base vérifie la valeur d'entrée et la valeur de sortie comme la méthode assertThat. Par conséquent, il est facile de tester un objet dont la cible de test renvoie une valeur de retour et ne conserve pas son état. D'autre part, le cas de test lorsque l'état de la méthode sans valeur de retour ou l'objet cible change La vérification devient difficile. La vérification devient encore plus difficile lorsque l'état des objets dépendants change.

Un exemple typique d'effets secondaires sur les objets dépendants est Il y a vérification du contenu écrit sur la sortie standard et l'enregistreur. (Parce que l'état des objets dépendants (sortie standard, etc.) change en fonction de l'exécution de la cible de test) Dans ce cas, écrivez un enregistreur ou un objet qui effectue une sortie standard. Il remplace par un objet appelé espion et surveille le traitement caché. L'espion enveloppe et implémente l'objet d'origine.

Voici un exemple d'implémentation de code de test pour vérifier la méthode qui génère les journaux.

Code de production


public class OutputLog {

    Logger logger = Logger.getLogger(OutputLog.class.getName());
    
    public void outputLog() {
        logger.info("doSomething");
    }
}

Code de test(Classe d'espionnage)


public class SpyLogger extends Logger{

    final Logger base;
    //Variable de classe pour référencer le journal du côté test séparément de la sortie du journal
    final StringBuffer log = new StringBuffer();
    
    public SpyLogger(Logger base) {
        super(base.getName(), base.getResourceBundleName());
        this.base = base;
    }
    
    //Méthode pour être un stub
    @Override
    public void info(String message) {
        base.info(message);
        //Traitement supplémentaire → Sortie vers le StringBuffer que l'espion a indépendamment
        log.append(message);
    }
}

Code de test(cas de test)


public class SpyExampleTest {
    @Test
    public void test() {
        OutputLog sut = new OutputLog();
        SpyLogger spyLogger = new SpyLogger(sut.logger);
        //Remplacez l'enregistreur testé par un enregistreur espion
        sut.logger = spyLogger;
        sut.outputLog();
        assertThat(spyLogger.log.toString(), is("doSomething"));
    }
}

Les références

Cet article a été rédigé en référence aux informations suivantes.

Recommended Posts

Implémenter le test double dans JUnit sans utiliser de bibliothèques externes
Implémenter un bouton de partage dans Rails 6 sans utiliser Gem
Testez la classe injectée sur le terrain dans le test de démarrage Spring sans utiliser le conteneur Spring
Tester les méthodes privées dans JUnit
Tester les méthodes privées dans JUnit
Implémenter un test piloté par table dans Java 14
Sortie du rapport de test JUnit dans Maven
[Spring MVC] Implémentez les paramètres dynamiques inclus dans l'URL sans utiliser Facultatif (~ Java7)
Contrôler l'ordre de test dans Junit4 avec un type d'énumération
Mapper sans utiliser de tableau en java
Implémenter des transitions de boutons à l'aide de link_to dans Rails
[Pour les débutants] J'ai essayé d'utiliser JUnit 5 avec Eclipse
JUnit 5: Comment écrire des cas de test dans enum
Implémentez la fonction de recherche de correspondance partielle sans utiliser Ransuck
Mettre en œuvre la fonction de classement par étoiles en utilisant Raty dans Rails 6
Tester le code à l'aide d'une maquette avec JUnit (centre EasyMock)