[Tests logiciels] Tests unitaires Junit

avant-propos

Il y a un cours de test de logiciels ce semestre, et le cours consiste à rédiger des rapports expérimentaux sur certains outils de test. C'est en effet une bonne opportunité. Après tout, en tant qu'ingénieur full-stack, comment pouvez-vous ne même pas connaître les tests (rires et des larmes).

Cet article décrit brièvement les tests unitaires et l'outil de test unique de Java junit, ainsi que l'utilisation simple du framework mokito. En outre, j'inclurai également d'autres articles liés aux tests dans cette série.

1. Tests unitaires

1. Qu'est-ce que les tests unitaires ?

Pour emprunter les mots de l'Encyclopédie Baidu,

Les tests unitaires font référence à l'inspection et à la vérification de la plus petite unité testable de logiciel. Pour la signification d'une unité dans les tests unitaires, de manière générale, il est nécessaire de déterminer sa signification spécifique en fonction de la situation réelle. Par exemple, une unité en langage C fait référence à une fonction, en Java, une unité fait référence à une classe, et les logiciels graphiques peuvent faire référence à une fenêtre ou à un menu. En général, une unité est le plus petit module fonctionnel défini par l'homme testé. Le test unitaire est le niveau le plus bas d'activité de test à effectuer pendant le développement de logiciels, où des unités individuelles de logiciel sont testées indépendamment du reste du programme.

Voici quelques points clés :

  • Les unités sont fabriquées par l'homme
  • Les tests unitaires sont des unités indépendantes, séparées des autres parties.

2. Pourquoi avez-vous besoin de tests unitaires ?

Permettez-moi de parler de mes propres sentiments ici. En ce qui me concerne, je n'ai pas l'habitude des tests unitaires. Je pense que les tests unitaires prendront beaucoup de temps. En même temps, je pense que cela ne vaut pas le temps passé Tous sont de simples projets crud.
Mais au fur et à mesure que les projets que je développe deviennent plus gros et que les exigences deviennent plus complexes, je constate progressivement que la qualité des projets que je fais devient de plus en plus instable, et il y a souvent des bugs étranges. Lorsqu'un bogue survient, nous devons souvent localiser le problème. Par exemple, j'ai créé une plate-forme IoT générale il y a quelque temps. Lors de l'enregistrement d'une vidéo de démonstration, j'ai entré une formule de correction (supportant quatre opérations arithmétiques), et cela fonctionnait normalement sous d'autres appareils à ce moment-là, mais il y avait une anomalie à ce moment-là. Heureusement, tout le projet a été fait par moi-même, et je connaissais quelques détails d'implémentation. Après avoir débogué pendant un certain temps, j'ai découvert qu'il s'agissait d'un problème d'algorithme. À ce moment-là, je me suis soudainement rappelé que lorsque je l'ai écrit, j'ai réalisé que l'algorithme L'implémentation ne prend pas en charge les nombres négatifs. L'opération de nombre négatif prend la forme "(0-x)", et les données téléchargées par ce périphérique se trouvent être un nombre négatif, il y a donc un problème.

Heureusement, c'est du développement full-stack, et tout est fait par moi-même, si ce projet est développé en équipe, j'estime que le temps de localisation des bugs va augmenter de façon exponentielle.

Parce que dans les tests à grande échelle tels que les tests d'intégration, la localisation des bogues prend trop de temps, nous avons donc besoin de tests unitaires pour garantir l'exactitude de chaque petit module. Bien que cela prenne plus de temps, cela en vaut la peine par rapport au temps passé sur les bogues et les corrections de bogues.

Dans le processus de développement d'un projet, il s'agit souvent de résoudre l'héritage des bogues précédents.
insérez la description de l'image ici

J'ai vu des résumés pertinents sur Internet, et ils sont très bien écrits pour être partagés - qu'est-ce que les tests unitaires exactement ? Qu'est-ce qui devrait être fait?

  • Les tests unitaires sont très importants pour la qualité de nos produits.
  • Les tests unitaires sont le type de test le plus bas de tous les tests. C'est la première et la plus importante partie. C'est le seul test qui garantit une couverture de code à 100 %. C'est le fondement et la prémisse de l'ensemble du processus de test logiciel. , Les tests unitaires empêchent le développement tardif de devenir incontrôlable en raison d'un trop grand nombre de bogues, et le rapport coût-performance des tests unitaires est le meilleur.
  • Selon les statistiques, environ 80 % des erreurs sont introduites au stade de la conception du logiciel, et le coût de la correction d'une erreur logicielle augmentera avec la progression du cycle de vie du logiciel. Plus un bogue est découvert tard, plus il est coûteux de le corriger, et il augmente de façon exponentielle.
  • En tant que codeur et acteur principal des tests unitaires, il est le seul à pouvoir produire des programmes sans défauts. Personne d'autre ne peut le faire. Spécification, optimisation et testabilité du code.
  • Refactoriser en toute confiance
  • Automatiser l'exécution trois mille fois

2. Junit

1. Qu'est-ce que junit

JUnit est un framework de test unitaire pour le langage Java. Fondée par Kent Beck et Erich Gamma, elle est devenue la plus réussie de la famille xUnit de sUnits dérivée de Kent Beck. JUnit possède son propre écosystème d'extensions JUnit.
À l'heure actuelle, junit est devenu junit5, qui a beaucoup changé par rapport à junit4. JUnit5 se compose de plusieurs modules différents issus de trois sous-projets différents.
JUnit 5=JUnit Platform+JUnit Jupiter+JUnit Vintage

Voir le site officiel pour plus de détails

Remarque : junit est fondamentalement le courant dominant des tests unitaires Java, et la plupart des projets Java ont aujourd'hui junit

2. Concept Junit - affirmation

Les étudiants qui viennent d'être exposés aux tests unitaires se demanderont certainement ce que signifie la méthode assert et ce qu'est une assertion lors de l'apprentissage de junit. Quand je suis entré en contact pour la première fois, c'était comme ça, je me demandais à quoi servait l'affirmation.

En fait, les assertions sont en fait des fonctions d'assistance, qui sont utilisées pour nous aider à déterminer si la méthode testée fonctionne comme prévu. Habituellement, ces fonctions d'assistance sont appelées assertions.

3. Utilisation simple de Junit

La démo suivante est un projet maven

①Importer les dépendances

<dependencies>
    <!-- ... -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.8.1</version>
        <scope>test</scope>
    </dependency>
    <!-- ... -->
</dependencies>
<build>
    <plugins>
        <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.22.2</version>
        </plugin>
        <plugin>
            <artifactId>maven-failsafe-plugin</artifactId>
            <version>2.22.2</version>
        </plugin>
    </plugins>
</build>

②Rédiger des cas de test

Voici un exemple tiré du site officiel

import static org.junit.jupiter.api.Assertions.assertEquals;

import example.util.Calculator;

import org.junit.jupiter.api.Test;

class MyFirstJUnitJupiterTests {
    
    

    private final Calculator calculator = new Calculator();

    @Test
    void addition() {
    
    
        assertEquals(2, calculator.add(1, 1));
    }

}

L'exemple ci-dessus affirme que la valeur de retour de calculator.add(1, 1) sera égale à 2.

Il sera très pratique d'exécuter le test dans l'idée, cliquez simplement sur l'icône d'exécution.
insérez la description de l'image ici
Si ce n'est pas dans l'idée, ajoutez simplement une fonction mian pour l'exécuter.

Si l'assertion en cours d'exécution est correcte, le programme sera le suivant :
insérez la description de l'image ici
Si l'assertion est fausse, junit vous lancera une exception AssertionFailedError et vous dira ce qui s'est mal passé
insérez la description de l'image ici

4. Utilisation de junit dans l'environnement SpringBoot

Bien sûr, dans le développement réel, comme dans l'environnement SpringBoot, de nombreuses classes de code métier sont injectées dans le conteneur Spring, et il existe des dépendances entre les autres classes injectées. Il est évidemment irréaliste de créer un objet de test comme avant. . Quelle est donc la solution à ce problème ?
Permettez-moi de vous présenter l'utilisation de junit dans le projet SpringBoot+SSM.

①Importer les dépendances

Il intègre des dépendances dans SpringBoot.Si nous avons besoin de tester des dépendances liées, nous n'avons qu'à introduire les modules de test correspondants.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
    </exclusions>
</dependency>

②Rédiger des cas de test

classe d'objets de test

package com.example.demo.service;

import org.springframework.stereotype.Component;

@Component
public class Junit5Test {
    
    
    public int add(int i,int j){
    
    
        System.out.println("-----------add被执行了---------------");
        return i+j;
    }
    public int doAdd(int i,int j){
    
    
        System.out.println("------------doAdd被执行了--------------");
        //被mock的函数会先执行,且只会执行一次
        System.out.println(add(i,j));
        return add(i,j);
    }
}

cas de test

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;

//初始化一个spring的上下文,使其可以使用一些注入(junit5)。junit4会用runwith
@SpringBootTest
class Junit5TestTest {
    
    
    @Autowired
    Junit5Test junit5Test;
    //会初始化一次
    @BeforeAll
    static void init(){
    
    
        System.out.println("init");
    }
    //所有测试方法前都会执行一遍
    @BeforeEach
    void each(){
    
    
        System.out.println("each");
    }

    @Test
    void getDeviceStatistic() {
    
    
        Assertions.assertEquals(2,spyJunit5Test.doAdd(1,1));
    }
}

Si vous avez besoin du contexte SpringBoot, il vous suffit d'y ajouter une annotation @SpringBootTest.Bien sûr, dans les anciens projets, nous pouvons voir @RunWith(SpringRunner.class). La première est la méthode d'écriture de junit5, et la seconde est la méthode d'écriture de junit4.

Lorsque nous avons besoin de l'objet de test dans le conteneur à ressort, nous l'injectons simplement normalement.

@Autowired
Junit5Test junit5Test;

3. Données fictives - l'utilisation du framework mockito

1.simuler

Dans le développement réel et le test unique, notre objet de test peut avoir besoin de demander des données réseau ou de modifier la base de données, mais nous ne voulons pas que cela change. À ce stade, nous pouvons utiliser le framework mockito pour simuler les données.

La soi-disant simulation signifie que si le code que nous écrivons dépend de certains objets et que ces objets sont difficiles à créer manuellement (c'est-à-dire, ne savent pas comment initialiser, etc., tels que HttpRequest et d'autres objets), alors utilisez un virtuel objet à tester. Comme il passe dans un fichier de classe, le bloc de code statique sera toujours exécuté, mais les blocs de code du constructeur et de l'instance ne seront pas exécutés .

2. Talon d'empilage

Le soi-disant stub stub est utilisé pour fournir les données de test requises pour le test. Comme il s'agit d'un objet fictif, certaines méthodes peuvent ne pas connaître la valeur de retour, nous devons donc assumer la valeur de retour. Des réponses correspondantes peuvent être définies pour diverses interactions, c'est-à-dire pour définir la valeur de retour d'un appel de méthode, en utilisant when(…).thenReturn(…) et doReturn(…).when(…).

par exemple:


//You can mock concrete classes, not only interfaces
 LinkedList mockedList = mock(LinkedList.class);
 
 //stubbing
 when(mockedList.get(0)).thenReturn("first");
 when(mockedList.get(1)).thenThrow(new RuntimeException());
  • doReturn().when() n'a aucun effet secondaire. La méthode n'est pas exécutée pendant l'empilement.
  • when().thenReturn() a des effets secondaires, ce qui signifie que la méthode sera exécutée en premier lors de l'empilement, ce qui peut entraîner certains effets secondaires.

3. @MockBean et @SpyBean

Bien sûr, dans l'environnement SpringBoot, vous pouvez également utiliser directement les annotations @SpyBean et @MockBean pour remplacer l'objet injecté de @Autowired, afin qu'il y ait un objet virtuel.

@MockBean
Si vous n'utilisez que @MockBean, l'objet modifié sera simulé, de sorte que la méthode add() de Junit5Test n'exécutera plus les détails spécifiques, mais MockBean simulera toutes les méthodes de l'objet cible, de sorte que le test ne peut pas être réellement exécuté. , il ne peut pas être testé.

@SpyBean
et dans certains cas, nous devons exécuter de vraies méthodes, nous voulons seulement nous moquer de certaines méthodes, alors nous pouvons utiliser @SpyBean.
Le spyJunit5Test décoré avec @SpyBean est un objet réel, uniquement lorsque (spyJunit5Test.add(1,1)).thenReturn(2);, la méthode add est stub, et d'autres méthodes sont toujours appelées.

Voici un exemple

package com.example.demo.service;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;

//初始化一个spring的上下文,使其可以使用一些注入(junit5)。junit4会用runwith
@SpringBootTest
class Junit5TestTest {
    
    
//    @Autowired
//    Junit5Test junit5Test;
    //介于@Autowired和@MockBean之间的注解,当配置了when时使用mock,没有进行打桩则走正常方法
    @SpyBean
    Junit5Test spyJunit5Test;
    //完全使用mock方法,方法都不会去真正执行。当调用mockBean修饰的方法时,不会去真正执行该方法,只会返回打桩后的值,如果没有打桩时会返回默认值,比如int就返回0。
//    @MockBean
//    Junit5Test mockJunit5Test;
    //会初始化一次
    @BeforeAll
    static void init(){
    
    
        System.out.println("init");
    }
    //所有测试方法前都会执行一遍
    @BeforeEach
    void each(){
    
    
        System.out.println("each");
    }

    @Test
    void getDeviceStatistic() {
    
    
        Assertions.assertEquals(1,1);
    }
    @Test
    void testDeviceStatisticByMock() {
    
    

        //配置mock
        when(spyJunit5Test.add(1,1)).thenReturn(2);
        Assertions.assertEquals(2,spyJunit5Test.doAdd(1,1));
    }
}

Enfin, je souhaite à tous une bonne journée des programmeurs !

Je suppose que tu aimes

Origine blog.csdn.net/qq_46101869/article/details/120942569
conseillé
Classement