
Types de tests
Qu'est-ce qu'un essai ? Selon Wikipedia : "Les tests de logiciels impliquent l'exécution d'un composant logiciel ou d'un composant système pour évaluer une ou plusieurs propriétés d'intérêt." En d'autres termes, il s'agit de vérifier l'exactitude de notre système dans certaines situations. Eh bien, voyons quels types de tests il existe en général :- Tests unitaires — Tests dont le but est de vérifier chaque module du système séparément. Ces essais doivent s'appliquer aux plus petites parties atomiques du système, par exemple les modules.
- Tests du système — Tests de haut niveau pour vérifier le fonctionnement d'une plus grande partie de l'application ou du système dans son ensemble.
- Test de régression — Test utilisé pour vérifier si de nouvelles fonctionnalités ou corrections de bogues affectent les fonctionnalités existantes de l'application ou introduisent d'anciens bogues.
- Tests fonctionnels — Vérifier si une partie de l'application satisfait aux exigences énoncées dans les spécifications, les user stories, etc.
Types de tests fonctionnels :
- Tests en boîte blanche — Vérifier si une partie de l'application satisfait aux exigences tout en connaissant l'implémentation interne du système ;
- Test boîte noire — Vérifier si une partie de l'application satisfait aux exigences sans connaître l'implémentation interne du système.
- Tests de performance — Tests écrits pour déterminer comment le système ou une partie du système fonctionne sous une certaine charge.
- Test de charge — Tests conçus pour vérifier la stabilité du système sous des charges standard et pour trouver la charge maximale à laquelle l'application fonctionne toujours correctement.
- Tests de résistance — Tests conçus pour vérifier les performances de l'application sous des charges non standard et pour déterminer la charge maximale avant la défaillance du système.
- Tests de sécurité - Tests utilisés pour vérifier la sécurité du système (contre les pirates, les virus, l'accès non autorisé aux données confidentielles et autres attaques délicieuses).
- Test de localisation — Tests de localisation de l'application.
- Tests d'utilisabilité - Tests visant à vérifier l'utilisabilité, la compréhensibilité, l'attractivité et la facilité d'apprentissage.

- Unité — Cette section fait référence aux tests unitaires, qui sont appliqués dans différentes couches de l'application. Ils testent la plus petite unité divisible de la logique d'application. Par exemple, des classes, mais le plus souvent des méthodes. Ces tests essaient généralement autant que possible d'isoler ce qui est testé de toute logique externe. Autrement dit, ils essaient de créer l'illusion que le reste de l'application fonctionne comme prévu.
Il devrait toujours y avoir beaucoup de ces tests (plus que tout autre type), car ils testent de petits morceaux et sont très légers, ne consommant pas beaucoup de ressources (c'est-à-dire de RAM et de temps).
- Intégration — Cette section fait référence aux tests d'intégration. Ce test vérifie les plus gros morceaux du système. Autrement dit, soit il combine plusieurs éléments de logique (plusieurs méthodes ou classes), soit il vérifie l'exactitude de l'interaction avec un composant externe. Ces tests sont généralement plus petits que les tests unitaires car ils sont plus lourds.
Un exemple de test d'intégration pourrait être la connexion à une base de données et la vérification de l'exactitude du fonctionnement des méthodes pour travailler avec elle.
- UI — Cette section fait référence aux tests qui vérifient le fonctionnement de l'interface utilisateur. Ils impliquent la logique à tous les niveaux de l'application, c'est pourquoi ils sont aussi appelés tests de bout en bout. En règle générale, ils sont beaucoup moins nombreux, car ils sont les plus encombrants et doivent vérifier les chemins (utilisés) les plus nécessaires.
Dans l'image ci-dessus, nous voyons que les différentes parties du triangle varient en taille : à peu près les mêmes proportions existent dans le nombre de différents types de tests dans le travail réel.
Aujourd'hui, nous allons nous intéresser de plus près aux tests les plus courants, les tests unitaires, puisque tout développeur Java qui se respecte devrait pouvoir les utiliser à un niveau basique.
Concepts clés des tests unitaires
La couverture de test (couverture de code) est l'une des principales mesures de la qualité des tests d'une application. C'est le pourcentage du code qui est couvert par les tests (0-100%). En pratique, beaucoup poursuivent ce pourcentage comme objectif. C'est quelque chose avec lequel je ne suis pas d'accord, car cela signifie que les tests commencent à être appliqués là où ils ne sont pas nécessaires. Par exemple, supposons que nous ayons des opérations CRUD standard (créer/obtenir/mettre à jour/supprimer) dans notre service sans logique supplémentaire. Ces méthodes sont purement des intermédiaires qui délèguent le travail à la couche travaillant avec le référentiel. Dans cette situation, nous n'avons rien à tester, sauf peut-être si la méthode donnée appelle une méthode DAO, mais c'est une blague. Des outils complémentaires sont généralement utilisés pour évaluer la couverture des tests : JaCoCo, Cobertura, Clover, Emma, etc. Pour une étude plus détaillée de ce sujet,- Couverture du code sur le support
- Test de boîte noire : un didacticiel approfondi avec des exemples et des techniques

- Nous écrivons notre test.
- Nous effectuons le test. Sans surprise, cela échoue, car nous n'avons pas encore implémenté la logique requise.
- Ajoutez le code qui fait passer le test (nous relançons le test).
- Nous refactorisons le code.
Étapes du test
Un test comporte trois étapes :- Spécifiez les données de test (appareils).
- Exercez le code sous test (appelez la méthode testée).
- Vérifiez les résultats et comparez avec les résultats attendus.

Environnements de test
Alors, maintenant au point. Il existe plusieurs environnements de test (frameworks) disponibles pour Java. Les plus populaires d'entre eux sont JUnit et TestNG. Pour notre examen ici, nous utilisons :
- @Test indique que la méthode est un test (essentiellement, une méthode marquée avec cette annotation est un test unitaire).
- @Before signifie une méthode qui sera exécutée avant chaque test. Par exemple, pour remplir une classe avec des données de test, lire des données d'entrée, etc.
- @After est utilisé pour marquer une méthode qui sera appelée après chaque test (par exemple pour effacer des données ou restaurer des valeurs par défaut).
- @BeforeClass est placé au-dessus d'une méthode, analogue à @Before. Mais une telle méthode n'est appelée qu'une seule fois avant tous les tests pour la classe donnée et doit donc être statique. Il est utilisé pour effectuer des opérations plus gourmandes en ressources, telles que la création d'une base de données de test.
- @AfterClass est l'opposé de @BeforeClass : il est exécuté une fois pour la classe donnée, mais seulement après tous les tests. Il est utilisé, par exemple, pour effacer des ressources persistantes ou se déconnecter d'une base de données.
- @Ignore indique qu'une méthode est désactivée et sera ignorée pendant l'exécution du test global. Ceci est utilisé dans diverses situations, par exemple, si la méthode de base a été modifiée et que le test n'a pas encore été retravaillé pour s'adapter aux modifications. Dans de tels cas, il est également souhaitable d'ajouter une description, c'est-à-dire @Ignore("Some description").
- @Test(expected = Exception.class) est utilisé pour les tests négatifs. Ce sont des tests qui vérifient le comportement de la méthode en cas d'erreur, c'est-à-dire que le test s'attend à ce que la méthode lève une sorte d'exception. Une telle méthode est indiquée par l'annotation @Test, mais avec une indication de l'erreur à intercepter.
- @Test(timeout = 100) vérifie que la méthode est exécutée en 100 millisecondes maximum.
- @Mock est utilisé au-dessus d'un champ pour attribuer un objet fictif (il ne s'agit pas d'une annotation JUnit, mais plutôt de Mockito). Au besoin, nous définissons le comportement du mock pour une situation spécifique directement dans la méthode de test.
- @RunWith(MockitoJUnitRunner.class) est placé au-dessus d'une classe. Cette annotation indique à JUnit d'invoquer les tests de la classe. Il existe différents exécuteurs, notamment ceux-ci : MockitoJUnitRunner, JUnitPlatform et SpringRunner. Dans JUnit 5, l'annotation @RunWith a été remplacée par l'annotation @ExtendWith plus puissante.
- assertEquals(Object expects, Object actuals) — vérifie si les objets passés sont égaux.
- assertTrue(boolean flag) — vérifie si la valeur passée est vraie.
- assertFalse(boolean flag) — vérifie si la valeur passée est fausse.
- assertNull(Object object) — vérifie si l'objet passé est null.
- assertSame(Object firstObject, Object secondObject) — vérifie si les valeurs transmises font référence au même objet.
- assertThat(T t, Matcher
matcher) — Vérifie si t satisfait la condition spécifiée dans matcher.
Tester en pratique
Examinons maintenant le matériel ci-dessus dans un exemple spécifique. Nous allons tester la méthode de mise à jour d'un service. Nous ne considérerons pas la couche DAO, puisque nous utilisons la valeur par défaut. Ajoutons un starter pour les tests :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.2.2.RELEASE</version>
<scope>test</scope>
</dependency>
Et ici, nous avons la classe de service :
@Service
@RequiredArgsConstructor
public class RobotServiceImpl implements RobotService {
private final RobotDAO robotDAO;
@Override
public Robot update(Long id, Robot robot) {
Robot found = robotDAO.findById(id);
return robotDAO.update(Robot.builder()
.id(id)
.name(robot.getName() != null ? robot.getName() : found.getName())
.cpu(robot.getCpu() != null ? robot.getCpu() : found.getCpu())
.producer(robot.getProducer() != null ? robot.getProducer() : found.getProducer())
.build());
}
}
Ligne 8 — extrayez l'objet mis à jour de la base de données. Lignes 9 à 14 — créez un objet via le constructeur. Si l'objet entrant a un champ, définissez-le. Sinon, nous laisserons ce qui est dans la base de données. Regardez maintenant notre test:
@RunWith(MockitoJUnitRunner.class)
public class RobotServiceImplTest {
@Mock
private RobotDAO robotDAO;
private RobotServiceImpl robotService;
private static Robot testRobot;
@BeforeClass
public static void prepareTestData() {
testRobot = Robot
.builder()
.id(123L)
.name("testRobotMolly")
.cpu("Intel Core i7-9700K")
.producer("China")
.build();
}
@Before
public void init() {
robotService = new RobotServiceImpl(robotDAO);
}
Ligne 1 — notre Runner. Ligne 4 — nous isolons le service de la couche DAO en lui substituant un mock. Ligne 11 - nous définissons une entité de test (celle que nous utiliserons comme cobaye) pour la classe. Ligne 22 - nous définissons l'objet de service, c'est ce que nous allons tester.
@Test
public void updateTest() {
when(robotDAO.findById(any(Long.class))).thenReturn(testRobot);
when(robotDAO.update(any(Robot.class))).then(returnsFirstArg());
Robot robotForUpdate = Robot
.builder()
.name("Vally")
.cpu("AMD Ryzen 7 2700X")
.build();
Robot resultRobot = robotService.update(123L, robotForUpdate);
assertNotNull(resultRobot);
assertSame(resultRobot.getId(),testRobot.getId());
assertThat(resultRobot.getName()).isEqualTo(robotForUpdate.getName());
assertTrue(resultRobot.getCpu().equals(robotForUpdate.getCpu()));
assertEquals(resultRobot.getProducer(),testRobot.getProducer());
}
Ici, nous voyons que le test a trois divisions claires : Lignes 3-9 — spécifiant les luminaires. Ligne 11 - exécution du code testé. Lignes 13-17 — vérification des résultats. Plus en détail : Lignes 3-4 : définissez le comportement de la simulation DAO. Ligne 5 - définissez l'instance que nous mettrons à jour en plus de notre standard. Ligne 11 — utilisez la méthode et prenez l'instance résultante. Ligne 13 — vérifiez qu'elle n'est pas nulle. Ligne 14 - comparez l'ID du résultat et les arguments de méthode donnés. Ligne 15 — vérifiez si le nom a été mis à jour. Ligne 16 — voir le résultat CPU. Ligne 17 — nous n'avons pas spécifié ce champ dans l'instance, il devrait donc rester le même. Nous vérifions cette condition ici. Exécutons-le :
GO TO FULL VERSION