[JAVA] Écrire du code de test avec Spring Boot

Aperçu

J'ai écrit le code de test pour le traitement lié à MVC et le traitement lié à la connexion dans Spring Boot par essais et erreurs, je vais donc le résumer sous forme de mémorandum.

Si vous savez comment écrire mieux, je vous serais reconnaissant de bien vouloir m'apprendre (╹◡╹)

Le code source est disponible sur GitHub.

Préface gérable sans lecture (cliquez pour ouvrir et fermer) De nos jours, j'entends souvent qu'il vaut mieux écrire du code de test. Je me suis demandé si je devais l'écrire, mais quand je l'ai recherché, il s'est arrêté au test du niveau de calcul à quatre règles, alors comment l'écrire dans l'application réelle ...!? Je n'étais pas prêt à écrire le code de test.
Je pensais que ce serait gênant tel quel, alors j'ai sacrifié mes vacances à Obon et j'ai écrit le code de test par essais et erreurs tout en souffrant d'erreurs chez Spring Boot. Il y a encore beaucoup de points peu clairs, mais pour le moment, j'ai établi quelque chose comme un «modèle d'écriture» dans une certaine mesure, donc je voudrais le résumer sous forme de mémorandum.
Il n'y a pas beaucoup d'articles sur le code de test de Spring Boot qui sont systématiquement organisés en japonais et en anglais, donc il y a beaucoup de parties à tâtons, mais j'espère que cela vous aidera à comprendre même un peu ('ω').

Lecteurs / objectifs cibles

Cet article se concentrera sur la façon d'écrire du code de test dans Spring Boot, donc si vous répondez à ce qui suit, ce sera douloureux.

Avec les hypothèses ci-dessus, la lecture de cet article vous permettra (probablement) de:

De plus, cette fois, l'affichage à l'écran et le test de la pièce JS etc. sont exclus. À l'avenir, je prévois de couvrir également ce domaine, mais soudainement essayer de tout tester serait trop compliqué, je vais donc commencer par une petite partie. Intensifier est important.

Supplément: Dans quelle mesure le test sera-t-il effectué automatiquement (cliquez pour ouvrir et fermer) L'importance de l'écriture de code de test est souvent mentionnée, mais le remplacement des tests manuels par des tests automatisés nécessite beaucoup d'expérience et de coûts.
Dans le cas des applications métier, il y a diverses choses et choses qui rendent difficile l'intégration de tests automatisés. Cependant, même s'il n'y a pas de restrictions dans le développement personnel, tout est soudainement automatisé !! Quand je me précipite, mon cœur est presque brisé (j'ai brisé ƪ (˘⌣˘) ʃ).
Par conséquent, je pense que c'est une manière douloureuse de se concentrer sur la partie de "ce qui peut être fait par le test automatique" et d'augmenter progressivement la partie qui peut être testée automatiquement autant que possible. L'écriture du code de test nécessite une compréhension approfondie du cadre et du langage, donc je pense qu'il est préférable de rester avec eux pendant longtemps à mesure que vos compétences techniques se développent.

Revenons maintenant à la portée de ce code de test. Lorsque j'écris beaucoup d'applications avec Spring Boot, j'ai (personnellement) rencontré beaucoup de bugs dans les domaines suivants.
-La valeur attendue n'est pas transmise au modèle passé à View. -Lorsque je regarde le résultat de l'exécution du traitement de la couche Dao dans la base de données, ce n'est pas comme prévu -Le traitement selon l'autorisation de l'utilisateur ne fonctionne pas comme prévu
Ce qui précède se chevauche bien avec cette couverture. Tout d'abord, je voudrais viser à augmenter le plaisir du développement en permettant de déboguer efficacement les parties qui ont beaucoup de bugs et qui ont du mal.

Un schéma simple de la couverture ci-dessus est le suivant.

image.png

Je vais aborder la zone entre le moment où la demande est lancée et le moment où View est invité à l'afficher. Par conséquent, View est fondamentalement une bonne nuit cette fois.

environnement

Pour plus de détails, veuillez consulter GitHub pom.xml.

pom.xml(Extrait)


        <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
		<dependency>
		    <groupId>org.junit.jupiter</groupId>
		    <artifactId>junit-jupiter-api</artifactId>
		    <scope>test</scope>
		</dependency>
		<dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.dbunit/dbunit -->
		<dependency>
		    <groupId>org.dbunit</groupId>
		    <artifactId>dbunit</artifactId>
		    <version>2.5.1</version>
		    <scope>test</scope>
		</dependency>
		<!-- https://mvnrepository.com/artifact/com.github.springtestdbunit/spring-test-dbunit -->
		<dependency>
		    <groupId>com.github.springtestdbunit</groupId>
		    <artifactId>spring-test-dbunit</artifactId>
		    <version>1.3.0</version>
		    <scope>test</scope>
		</dependency>

Gamme de test

Voyons maintenant comment écrire le code de test à partir d'ici. Cependant, comme le code de test de Spring Boot est écrit en combinant certaines connaissances, si vous essayez de tout couvrir en même temps, le niveau de difficulté augmentera.

Par conséquent, je voudrais le diviser en quatre étapes.

La couverture des niveaux 1 à 3 couverts dans cet article est illustrée approximativement comme suit.

image.png

Commençons par Hello World.


Niveau 1. Testez Hello World

C'est le familier Hello World. Jetons un coup d'œil à certains processus courants tels que lorsqu'une requête est faite à "/ hello / init" ci-dessous, "hello" est renvoyé comme nom de la vue.

HelloController.java


@Controller
@RequestMapping("/hello")
public class HelloController {
	
	@RequestMapping("/init")
	private String init() {
		return "hello";
	}
}

C'est simple à faire, mais cela nécessite de nouvelles connaissances pour écrire du code de test, c'est donc une partie très importante de la prise en main du code de test Spring Boot.

Tout d'abord, le code lui-même n'est pas très long, donc pour avoir une vue d'ensemble, écrivez le code de test ci-dessous.

HelloControllerTest.java


@AutoConfigureMockMvc
@SpringBootTest(classes = DbMvcTestApplication.class)
public class HelloControllerTest {
	
	//mockMvc Mock object pour gérer les requêtes et réponses Http sans déployer sur le serveur Tomcat
	@Autowired
	private MockMvc mockMvc;
	
	//Spécifiez la vue dans la demande d'obtention et évaluez le succès / l'échec de la demande en fonction du statut http
	@Test
void init le traitement s'exécute et renvoie 200() throws Exception {
		// andDo(print())Afficher la demande / réponse avec
		this.mockMvc.perform(get("/hello/init")).andDo(print())
			.andExpect(status().isOk());
	}

Soudain, lorsque j'écrivais une application, j'ai trouvé beaucoup de choses que je ne connaissais pas. Le traitement de ce code de test est à peu près divisé en "deux" blocs, alors regardons chacun de plus près.

Annotation de classe

Les annotations données à la classe contiennent des informations importantes pour obtenir une vue d'ensemble du code de test. Je ne l'utilise généralement pas, mais si vous obtenez une bonne vue d'ensemble, la distance par rapport au code de test sera raccourcie.

Annotation AutoConfigureMockMvc

C'est une annotation pour utiliser quelque chose appelé MockMvc. Alors, qui est MockMvc?

Il s'agit de séparer le «serveur» physique de «l'application Web» créée. Cela ne sort pas très bien, alors regardons ce qui nous rend heureux en le déconnectant.

Supplément: Besoin d'utiliser une maquette (cliquez pour ouvrir et fermer) L'un des avantages de l'utilisation d'une simulation est la réduction du temps d'exécution du code de test, mais la limitation de la portée de l'impact est également un facteur important.
Par exemple, lors du test du traitement de la couche de service qui accède à la couche Dao, s'il s'agit d'un processus simple, je pense que le test peut être réalisé sans trop d'effort sans utiliser de simulation. Cependant, si le code devient compliqué et que les personnes qui le font sont différentes, lorsqu'un bug survient, il est nécessaire de rechercher si la cause est dans la "couche service" ou la "couche Dao". Ce sera. Ici, si la couche Dao est simulée "de manière appropriée" et que le test de la couche de service est terminé en premier, si un problème survient lors de son remplacement par la classe réelle, la cause peut être limitée à la couche Dao. Ce sera possible.
En fait, même si vous n'utilisez pas de simulation dans le développement personnel, vous devez tout implémenter de 1 à 100 par vous-même, de sorte que vous n'aurez peut-être pas beaucoup d'occasions d'utiliser la simulation à des fins autres que de réduire le «temps d'exécution». Cependant, l'idée de limiter la plage d'influence, non limitée au code de test, devient importante, et dans le développement d'équipe, tout n'est pas créé par soi-même, donc l'idée et la gestion de la simulation se situent également au niveau de la vue d'ensemble. Je pense que tu devrais le savoir.
Cependant, je ne peux pas dire que je peux utiliser le simulacre complètement, donc si vous connaissez une meilleure façon de penser, je serais très heureux si vous pouviez me le dire. Ce qui suit est un matériel de référence. [Qu'est-ce qu'un objet Mock](https://www.itmedia.co.jp/im/articles/1111/07/news180.html) [Avantages de Mock](https://stackoverflow.com/questions/22783772/what-is-the-benefits-of-mocking-the-dependencies-in-unit-testing)

Pour le développement personnel, simulez la partie serveur pour réduire le temps d'exécution du code de test, et pour le développement d'équipe, moquez la partie dans laquelle vous n'êtes pas impliqué et écrivez le code de test après avoir limité la plage d'influence. Je pense qu'il vaut mieux continuer.

Ce qui suit est un matériel de référence.

Matériel japonais de MockMvc Test officiel de la couche Web


Annotation SpringBootTest

C'est une annotation qui semble très importante. C'est en fait une annotation très importante. Dans le test unitaire de Spring Boot, il s'agit d'une annotation qui apparaît presque toujours et qui comporte de nombreuses fonctions, je voudrais donc examiner les fonctions étape par étape.

Les deux fonctions suivantes sont importantes ici.

Tout d'abord, parlons de l'annotation ʻExtendWith`. L'annotation RunWith était souvent utilisée dans l'explication du test Spring Boot, mais l'annotation RunWith est une annotation pour Junit4 et l'annotation ExtendWith est une annotation pour Junit5.

Il est utilisé pour la mise en œuvre générale du prétraitement et du post-traitement des tests. Ensuite, SpringExtension est passé à la classe "Extension", qui est la propriété value et l'implémentation du traitement général est écrite. Il s'agit d'une histoire évolutive, donc je vais l'omettre cette fois, mais cette classe Extension joue un rôle important, par exemple, dans l'instanciation d'un ApplicationContext qui agit comme un conteneur DI. Voir Officiel pour plus de détails.

Eh bien, comme je l'ai écrit pendant longtemps, utiliser un conteneur DI avec Spring Boot n'est plus une évidence, et il est difficile d'écrire une annotation ExtendWith à chaque fois. Comme vous pouvez le voir dans la Documentation, il est naturel de l'utiliser. Si c'est le cas, vous pouvez l'inclure, donc seule l'annotation SpringBootTest est OK.

En créant une annotation qui inclut plusieurs fonctions de cette manière, il est possible de simplifier la description de la prémisse du code de test, mais cette fois nous mettrons l'accent sur la facilité de compréhension, donc pour l'intégration Les annotations sont omises.


Ensuite, jetons un coup d'œil aux paramètres ApplicationContext. ApplicationContext est souvent utilisé comme un mot, mais je pense que ce n'est pas grave si vous le considérez comme un "conteneur DI".

Il n'est pas possible de tout résoudre en passant simplement la classe principale, mais je voudrais aborder cela dans le domaine des tests au niveau de la couche Dao, qui ne nécessite pas de requête / réponse HTTP.

Supplément: Que transmettre à la propriété classes (cliquez pour ouvrir / fermer) Comme vous pouvez le voir dans la documentation (https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/context/SpringBootTest.html#classes--), la propriété "classes" La classe définie dans est celle qui a le rôle de "Configuration" et est utilisée pour réaliser le conteneur DI. Dans l'explication précédente, celui avec l'annotation SpringBootApplication a réalisé le "réglage" pour le conteneur DI.
Cependant, cette fois, HelloController ne dépend pas d'autres classes (pour autant que vous puissiez voir), donc même si vous passez "HelloController" à la propriété "classes", cela fonctionnera. Cependant, étant donné qu'il est supposé que la propriété "classes" reçoit à l'origine une classe qui a un rôle en tant que classe de paramètres, une classe avec l'annotation SpringBootApplication ou une classe de configuration qui définit les paramètres requis pour les tests est utilisée. Il vaudrait mieux l'adopter. Lorsque vous utilisez une fonction, si vous lisez au moins ce que supposent les paramètres décrits dans le document, vous serez accro à un comportement inattendu et vous perdrez. Je pense que ce sera moins. (Auto-discipline)
* En fait, si seul HelloController est défini comme WebApplicationContext, la partie Body de la réponse de MockMvc sera vide. (Le contenu n'a pas été vu, mais il semble que la cause en soit que ViewResolver et View ne sont pas instanciés.)

L'explication est devenue longue rien qu'en définissant, mais une fois que vous l'avez comprise, c'est une partie importante qui peut être utilisée lors de l'écriture d'un autre code de test, il peut donc être bon de s'asseoir et d'étudier. .. (J'ai compris que je ne comprenais pas du tout DI, donc c'était une bonne occasion de revoir Spring)

Code de test

Je suis finalement arrivé au code de test réel. C'est plus simple et plus amusant que les paramètres détaillés jusqu'à présent. Maintenant qu'il y a un écart, regardons à nouveau la partie liée au code de test.

HelloControllerTest.java(Extrait)


//Spécifiez la vue dans la demande d'obtention et évaluez le succès / l'échec de la demande en fonction du statut http
    @Test
void init le traitement s'exécute et renvoie 200() throws Exception {
        // andDo(print())Afficher la demande / réponse avec
        this.mockMvc.perform(get("/hello/init")).andDo(print())
            .andExpect(status().isOk());
    }

Le code de test ci-dessus effectue deux processus, «exécution de la demande» et «vérification de la réponse».

Les deux processus sont exécutés sur la base de l'instance de «MockMvc» et sont essentiellement décrits dans une instruction comme décrit ci-dessus. En les rassemblant en une seule phrase, il est possible de démêler ce que fait le test sous la forme d'une phrase en anglais. Voyons comment il est réellement écrit.

Pour résumer ce qui précède, le texte anglais sera le suivant. (Je ne suis pas très doué pour écrire, alors sentez-le dans l'atmosphère ƪ (˘⌣˘) ʃ)

Perform get request to [/hello/init] and print the result, and I expect that status is 200 OK.

Étant donné que le langage de programmation est écrit en anglais, il y a bien sûr l'avantage que les anglophones peuvent lire et écrire le code de test dans une structure similaire aux phrases anglaises auxquelles ils sont habitués. (Il semble qu'il n'y ait aucun moyen d'échapper à l'anglais, donc cela ne sera peut-être pas douloureux si vous prenez au moins la lecture et l'écoute telles quelles)

C'est un peu hors sujet, mais en décrivant la méthode de test et les résultats attendus dans un format facile à lire, vous pouvez facilement obtenir des informations importantes à partir du code de test afin de comprendre les spécifications du système et le code source réel. Devenir. Si vous le retournez, le code de test qui ne répond pas aux spécifications manquera le bogue et ne conduira pas à la compréhension des spécifications, donc ce sera du code inutile.

A partir d'une étape de code aussi simple, il peut être bon de prendre l'habitude d'écrire du code de test tout en étant toujours conscient de «ce qui doit être réalisé en tant que fonction dans le processus implémenté».

Après beaucoup de temps, la réponse à la demande lorsque HelloWorld est vérifié avec le code de test est la suivante. image.png

image.png

Vous devriez être en mesure de confirmer que le contenu décrit ci-dessus est satisfait.

Enfin, je peux confirmer que Hello World fonctionne correctement. Je l'ai fait.


Tester le modèle

Puisqu'il y a une limite à ce qui peut être vérifié avec le code de test HelloWorld seul, j'aimerais jeter un coup d'œil à la vérification du modèle comme autre chose pratique au niveau 1.

Le modèle ici fait référence à «l'objet Java référencé par View» et est souvent écrit dans «Model.addAttribute», etc. Il est très courant que je pensais bien mettre la valeur dans le modèle, mais ce n'était pas le cas, donc je n'ai pas eu à démarrer le serveur et à accéder à l'écran en un clic ... Ce serait très pratique si nous pouvions vérifier que cela fonctionnait.

Ci-dessous, nous verrons comment vérifier réellement le contenu du modèle avec le code de test. Le contenu est plus facile à comprendre que ceux que j'ai abordés jusqu'à présent, j'espère donc que vous maîtriserez comment l'utiliser.


Code à vérifier

Voyons d'abord le code à tester. Cela dit, ce n'est qu'une légère extension de HelloController, et ce n'est pas si difficile que ça, donc je vais tout mettre en même temps ici.

HelloController.java


@Controller
@RequestMapping("/hello")
public class HelloController {
	
	@RequestMapping("/init")
	private String init(Model model) {
		
		//Liste d'utilisateurs Tout d'abord, générez manuellement
		List<User> userList = new ArrayList<User>();

		User user = new User();
		user.setUserId(0L);
		user.setUserName("test0");

		User user2 = new User();
		user2.setUserId(1L);
		user2.setUserName("test1");
		
		userList.add(user);
		userList.add(user2);
		
		//Définissez une liste d'utilisateurs sur le formulaire et ajoutez-la au modèle pour jeter les bases de la vérification de son ajout au modèle.
		DbForm form = new DbForm();
		form.setUserList(userList);
		
		model.addAttribute("message", "hello!");// 1
		
		model.addAttribute("user", user);// 2
		
		model.addAttribute("dbForm", form);// 3
		
		return "hello";
	}

}

Dans ce qui suit, nous suivrons chaque modèle pour model.addAttribute.

① La chaîne a-t-elle été stockée dans Model?

Commençons par un exemple simple.

Dans model.addAttribute (" message "," hello! ");, Le modèle stocke simplement "message" comme clé et "hello" comme valeur.

Le code de test pour vérifier cela est le suivant.

HelloControllerTest.java(Extrait)


@Test
bonjour est passé au message modèle dans le processus d'initialisation void() throws Exception {
		this.mockMvc.perform(get("/hello/init"))
			.andExpect(model().attribute("message", "hello!"));
	}

Le code de test est également très simple, et comme vous pouvez le voir dans la partie ʻandExpect (model (). Attribute ("message", "hello!")) `, C'est presque la même chose que de bourrer le modèle réel. Vous pouvez également écrire du code de test.

image.png

Si vous regardez réellement le résultat, vous pouvez voir que la pièce Modèle contient correctement des valeurs.

S'il s'agit d'un objet simple, il peut être écrit simplement parce que ses propriétés ne sont qu'à un seul niveau. Cependant, si vous regardez de plus près les résultats, vous constaterez que la valeur de la valeur est écrite dans une chaîne de caractères douteuse. Il représente l'instance de l'objet lui-même. Bien sûr, vous souhaitez parfois vérifier qu'un objet n'est pas nul, mais la plupart du temps, vous souhaitez savoir si une propriété particulière de l'objet a la valeur attendue.

Ci-dessous, nous examinerons la validation d'un modèle contenant ces propriétés imbriquées.

(2) La valeur de la propriété userName de l'entité utilisateur est-elle intégrée dans le modèle comme prévu?

Maintenant, la vérification des objets imbriqués rend le code de test un peu plus compliqué, mais c'est beaucoup plus simple que l'interprétation des annotations, alors explorons chacune d'elles.

Ici, concernant le traitement de model.addAttribute (" user ", user);, vérifiez si la propriété "userName" de l'instance "user" est conforme (ici, la valeur "test0"). Aller.

Quoi qu'il en soit, commençons par regarder le code de test réel.

HelloControllerTest.java(Extrait)


@Test
L'entité utilisateur est stockée dans le modèle par le traitement d'initialisation de void() throws Exception {
		this.mockMvc.perform(get("/hello/init"))
			.andExpect(model()
					.attribute("user", hasProperty(
										"userName", is("test0")
										)
							)
					);
	}

Soudain, la structure a radicalement changé. Ce n'est pas le code de test de Spring Boot, mais plutôt le traitement par le framework qui gère le soi-disant "Matcher" qui vérifie la validité du test appelé "Hamcrest".

La méthode hasProperty pour valider les propriétés d'un objet est utilisée dans l'importation statique, donc la structure est exactement HasPropertyWithValue.hasProperty.

Et cette méthode a les rôles suivants. (Cité par un officiel)

Creates a matcher that matches when the examined object has a JavaBean property with the specified name whose value satisfies the specified matcher.

D'une manière ou d'une autre, une phrase ésotérique en anglais est sortie, mais je pense que cela viendra bien si vous regardez un exemple réel. assertThat(myBean, hasProperty("foo", equalTo("bar")) Cela signifie que l'objet "myBean" a une propriété appelée "foo" et que la valeur de la propriété foo est "bar". C'est exactement ce que nous voulons vérifier dans ce test. Un exemple simple de cet exemple serait ʻassertThat (user, hasProperty ("userName", is ("test0")); `. Étant donné que la valeur de retour de hasProperty appartient au type Matcher, vous pouvez également écrire des propriétés imbriquées.

La vérification des propriétés imbriquées allonge inévitablement le code et rend difficile la compréhension de la correspondance entre parenthèses, donc je pense qu'il est nécessaire de prendre certaines mesures pour le rendre plus facile à lire, comme la conception de retraits comme dans l'exemple ci-dessus.

Quoi qu'il en soit, cela permet de gérer des modèles compliqués. À titre d'exemple du modèle final, examinons le code de test de l'objet List.

Supplément: Qu'est-ce que Matcher? Matcher est sorti naturellement, mais si vous n'avez pas touché à JUnit, vous ne le connaissez peut-être pas, alors je vais y revenir brièvement en supplément. Matcher lui-même est une interface, conçue pour décrire le processus de validation dans un test (égal ou non, toutes conditions ...) dans un format simple et facile à lire.
Par exemple, la méthode "assertThat" couramment utilisée est écrite sous la forme ʻassertThat (1 + 1, is (2)); `. Il s'agit d'écrire le code de test dans un format facile à lire en anglais, similaire au processus d'exécution ci-dessus. Et ce que vous spécifiez dans le deuxième argument de la méthode assertThat représente "Matcher". Si vous suivez réellement le contenu de la méthode assertThat, ʻSi (! Matcher.matches (réel)) {Traitement quand il n'y a pas de correspondance} ` Ici, la méthode matches de l'instance de type Matcher spécifiée dans le deuxième argument est appelée. Puisque ce résultat est un booléen, je pense que c'est une bonne idée d'interpréter grossièrement Mathcer comme une valeur booléenne pour stocker le succès ou l'échec de la vérification.
Même s'il s'agit d'une propriété imbriquée, en fin de compte, il ne s'agit que de vérifier si la valeur de la propriété spécifique au niveau inférieur est égale à ce qui était attendu, il peut donc être étonnamment clair si vous pensez simplement et que vous n'êtes pas très préparé. ..

Référence

③ La liste emballée dans Model contient-elle les propriétés attendues?

Regardons le modèle de modèle final, qui est imbriqué et a une structure de liste. Comme exemple courant de model.addAttribute (" dbForm ", form);, vérifiez que la "liste des utilisateurs dans l'objet Form" correspond à ce que vous attendez. À titre d'exemple, écrivez d'abord le code ci-dessous.

HelloControllerTest.java(Extrait)



	//Pour les éléments de liste, accédez à la liste dans n'importe quel ordre avec hasItem et vérifiez s'il existe un élément dont la propriété spécifiée a la valeur spécifiée.
	//Rendre le test vert uniquement s'il existe
	@Test
La liste des utilisateurs est stockée sous la forme de modèle par le traitement d'initialisation du vide() throws Exception {
		
		this.mockMvc.perform(get("/hello/init"))
			.andExpect(model().attribute("dbForm", hasProperty(
					"userList", hasItem(
						hasProperty(
								"userName", is("test1")
						)
					)
			)));
	}

Une nouvelle méthode appelée «hasItem» est apparue. Officiel Il est dit qu'il ne peut être utilisé que pour les objets au format liste. Je suis. Et la méthode utilisée ici a Matcher comme argument.

En d'autres termes, en gros, nous vérifions qu'il y en a au moins un qui satisfait le Matcher passé en argument dans la liste à exécuter par la méthode hasItem. Dans cet exemple, nous voulons vérifier que pour chaque élément utilisateur de la liste d'utilisateurs, il y en a au moins un avec la propriété "userName" définie sur "test1".

Puisque la liste de l'exemple a un petit nombre d'éléments tels que "2", il est possible d'examiner tout le contenu, mais dans l'application réelle, une liste contenant des centaines ou des milliers d'éléments est souvent transmise. .. Il est difficile de vérifier tout cela, bien que cela puisse être codé. Dans un tel cas, il semble que la fiabilité puisse être garantie dans une certaine mesure s'il est possible de vérifier si les éléments situés près du début, du milieu et de la fin répondent aux spécifications. Par conséquent, au lieu de vérifier tous les éléments de la liste, il suffit de vérifier certains d'entre eux en tant qu'éléments représentatifs, donc je pense qu'il serait préférable de fournir des cas de test avec la méthode hasItem.


Soit dit en passant, il est devenu assez long car il était mélangé à diverses explications supplémentaires, mais cela complète la vérification du code de test de niveau 1. En complétant le niveau 1, vous pouvez faire ce qui suit:

Ensuite, au niveau 2, je voudrais examiner la vérification de la base de données, qui est le cœur de l'application.

Niveau 2. Tester les opérations de la base de données

Dans le test unitaire avec Spring Boot, la base de données est vérifiée à l'aide de ce qu'on appelle "DbUnit". Si vous l'écrivez comme ça, même si Spring Boot seul est complet, vous devrez étudier plus ... mais je pense que c'est suffisant pour apprendre à utiliser DbUnit facilement. L'important est de savoir "quel genre de travail sera plus facile" en combinant DbUnit et Spring Boot.

Si vous écrivez soudainement sur DbUnit dans un désordre, l'image sera difficile à comprendre, alors suivons d'abord le flux de la façon dont les opérations de base de données sont remplacées des tests manuels aux tests automatisés.

Test manuel

Tout d'abord, pensez à tester manuellement les opérations de votre base de données. Je pense que le test se déroulera dans le flux suivant.

Les avantages et les inconvénients des tests manuels sont laissés ici, mais nous nous concentrerons ici sur la «reproductibilité des tests». Si vous développez en équipe, le résultat de SELECT changera d'un moment à l'autre, et le traitement UPDATE et DELETE nécessitera une préparation préalable si vous souhaitez remplir les mêmes conditions. Si le test exécuté n'est pas reproductible, il sera difficile de déterminer si une dégradation s'est effectivement produite ou non lorsque des tests répétés tels que le refactoring et les tests de régression sont effectués.

Maintenant, imaginons un peu plus sur les tests manuels.

Construction manuelle de l'état de test

Afin d'assurer la reproductibilité, j'ai essayé d'incorporer le processus suivant dans le test manuel ci-dessus.

Cela garantissait la reproductibilité du test. Si tel est le cas, vous pouvez tester en toute confiance ... !! Cela peut ne pas être possible s'il s'agit d'une petite application, mais chaque fois que vous effectuez une petite vérification, vous pouvez sauvegarder la base de données entière, supprimer la base de données entière, restaurer la base de données entière, et ainsi de suite. Cela prendra énormément de temps.

Même si vous créez un état de test reproductible, s'il faut beaucoup de temps pour exécuter le test lui-même ou renvoyer le résultat, le test ne sera pas exécuté au bon moment. Je voulais juste déboguer un peu, mais si je n'étais pas assez bon, je devrais attendre des dizaines de minutes avant que les résultats ne reviennent, et si je n'étais pas assez bon, cela serait reporté et je reviendrais au test manuel des ticks.


Jusqu'à présent, tester les opérations de la base de données tout en garantissant la reproductibilité peut sembler une tâche ardue. Cependant, en combinant Spring Boot et DbUnit, vous pouvez exécuter le test ci-dessus en appuyant simplement sur un bouton, bien qu'une certaine préparation soit nécessaire.

Examinons maintenant le code de test en utilisant Spring Boot et DbUnit comme sujet principal du niveau 2.

Code à vérifier

Examinons d'abord le processus d'obtention du résultat avec la route royale SELECT. Le code de la couche Dao à vérifier est décrit ci-dessous.

UserDao.java(Extrait)


     /**
	 *Obtenir tous les enregistrements utilisateur de la base de données
	 *Cette fois, pour les tests, le processus a été simplifié.
	 * @return Liste des entités utilisateur
	 */
	public List<User> findAllUser() {
		
		QueryBuilder query = new QueryBuilder();
		
		query.append("select user_id, user_name from tm_user");
		
		return findResultList(query.createQuery(User.class, getEm()));
	}

Différents processus sont écrits, mais il convient de noter les deux points suivants.

Le processus de sélection des enregistrements de la base de données continuera à se reproduire, la première étape consiste donc à vérifier «nombre d'enregistrements dans la base de données = taille de la liste».

J'utiliserai DbUnit immédiatement pour la vérification, mais une certaine préparation est nécessaire avant d'écrire le code de test réel. Il y a beaucoup de choses à faire en préparation, mais une fois que vous le maîtrisez, vous pouvez en faire une routine pour les tests de fonctionnement de la base de données ultérieurs, alors j'aimerais y regarder de plus près.

Je souhaite gérer les données avec CSV

Tout d'abord, nous poserons les bases de la gestion des enregistrements de base de données dans des fichiers. En tant que fonction standard de DbUnit, les paramètres de transaction d'enregistrement, etc. sont décrits dans un fichier XML, mais cette fois, nous gérerons les enregistrements avec CSV. Il y a plusieurs raisons, mais en résumé, le plus important est que vous pouvez écrire simplement.

Je vais prendre quelques étapes ci-dessous, mais elles sont toutes simples, donc je pense que vous pouvez les comprendre intuitivement dans une certaine mesure sans aller trop loin dans DbUnit lui-même.

Donc, tout d'abord, nous allons créer une classe pour utiliser le fichier CSV pour les tests.

CsvDataSetLoader

CsvDataSetLoader.class


public class CsvDataSetLoader extends AbstractDataSetLoader{
	
	@Override
	protected IDataSet createDataSet(Resource resource) throws Exception {
		return new CsvURLDataSet(resource.getURL());
	}
}

Ce qui suit est une explication supplémentaire des éléments importants.

Littéralement une classe abstraite pour lire un ensemble de données. L'ensemble de données signifie ici «un ensemble de tables». Cette classe abstraite étant une classe d'implémentation de l'interface DataSetLoader, la classe à créer sera de type "DataSetLoader". En d'autres termes, si vous regardez la classe créée au niveau de la classe, c'est aussi simple que de décrire l'information "Ceci est une classe pour lire un ensemble de données".

Encore une fois, comme son nom l'indique, c'est une méthode d'usine pour créer des ensembles de données. L'objet "resouce" de type Ressource passé en argument a des informations et un comportement pour accéder au "fichier réel". Dans le test proprement dit, l'objet ressource se présente sous la forme de stockage du chemin du fichier CSV à traiter.

In Official Cette classe construit un IDataSet à partir d'une URL de base contenant des fichiers CSV Comme vous pouvez le voir, en obtenant le fichier CSV réel basé sur l'objet de ressource ci-dessus et en le convertissant en objet de jeu de données, DbUnit peut le traiter.

Certains traitements ont été écrits, mais comme son nom l'indique, cette classe sert à lire le fichier CSV réel et à le rendre disponible pour tester les opérations de la base de données.

Une fois écrit, il peut être utilisé lors du test des opérations de base de données à l'aide de fichiers CSV dans d'autres applications, donc ici, si vous pouvez obtenir un aperçu de ce que représente chaque processus, le problème est. Je pense que non.


Maintenant que le cours de lecture de CSV est terminé, créons un fichier CSV à lire réellement. Voir GitHub pour un exemple de fichier.

Il existe différentes façons de créer le fichier CSV lui-même, mais personnellement, je recommande d'utiliser DBeaver pour extraire le CSV de l'enregistrement, car il peut être utilisé tel quel. Voici quelques éléments à garder à l'esprit lors de la création d'un fichier CSV.

Vous devez également faire attention à l'endroit où vous placez vos fichiers CSV. (Je l'ai mis dans un endroit étrange et je suis devenu accro.) Fondamentalement, il sera placé sous src / test / resources. Référence

La structure spécifique des fichiers / dossiers est la suivante.

image.png

Vous pouvez voir qu'il existe un fichier CSV qui ressemble à un nom de table sous src / test / resources / testData. Et à côté se trouve un fichier texte inconnu appelé table-ordering.txt. Cela évite les contraintes de clé externe et spécifie l'ordre dans lequel les tables de la base de données sont lues. La méthode d'écriture spécifique est la suivante.

table-ordering.txt


TableA
TableB
TableC

Code de test

Maintenant que nous sommes enfin prêts, nous pouvons entrer dans le code de test. Le nombre d'annotations augmentera soudainement, mais si vous surmontez ce problème, la plage dans laquelle vous pouvez écrire des tests augmentera considérablement, je ferai donc de mon mieux.

DBSelectTest.java


@DbUnitConfiguration(dataSetLoader = CsvDataSetLoader.class)
@TestExecutionListeners({
	  DependencyInjectionTestExecutionListener.class,
	  TransactionalTestExecutionListener.class,
	  DbUnitTestExecutionListener.class
	})
@SpringBootTest(classes = {DaoTestApplication.class}, webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class DBSelectTest {
	
	@Autowired
	private UserDao userDao;
	
	//En définissant la valeur de DatabaseSetup sur le chemin du fichier CSV, "table"-ordering.Voir txt "
	//Créer un groupe de tables de test en créant une table séquentielle
	//À ce stade, le chemin de la valeur est "src"/test/À partir de sous "ressources", le nom de fichier du fichier CSV est
	//Correspond au nom de la table
	//
	//Aussi,@En ajoutant l'annotation Transactionnelle, vous pouvez annuler la transaction lorsque le test est terminé.
	//Peut être testé sans polluer l'environnement
	@Test
	@DatabaseSetup(value = "/testData/")
	@Transactional
	public void contextLoads() throws Exception {
		List<User> userList = userDao.findAllUser();
		
		//Dao a-t-il réussi à récupérer les enregistrements de la table?
		assertThat(userList.size(), is(2));
	}

}

Annotation de classe

Pour avoir une vue d'ensemble, commençons par les annotations données à la classe.

DbUnitConfiguration

Comme vous pouvez le lire, il s'agit d'une annotation permettant de définir divers paramètres de DbUnit. En spécifiant le CsvDataSetLoader créé ci-dessus dans la propriété" dataSetLoader ", le fichier CSV peut être lu. Il semble qu'il existe divers autres paramètres, mais à ce stade je pense qu'il n'y a aucun problème à reconnaître qu'il est utilisé pour charger CSV.

TestExecutionListeners

C'est la partie la plus difficile de ce code de test. Je pense que cette annotation devrait être suffisante pour vous donner un aperçu et organiser ce qui doit être transmis à la propriété. En résumé, il s'agit de charger le nécessaire du TestExecutionListener, qui définit le traitement à effectuer avant et après l'exécution du code de test.

La description de chaque auditeur est grossièrement résumée dans Officiel. Ici, je décrirai brièvement ceux qui sont souvent utilisés.

Spécifiez lors de l'utilisation de DI dans le code de test. En spécifiant, il est possible d'injecter la classe cible de test à partir du conteneur DI avec Autowired etc.

Spécifiez lors de la définition de la transaction pour l'opération de base de données. Après avoir exploité la base de données, il est fondamental de remettre la base de données à son état d'origine, c'est donc fondamentalement essentiel pour les tests qui gèrent la base de données.

Spécifiez lors de la définition de l'état de la base de données avant et après le test avec l'annotation décrite plus loin. Comme son nom l'indique, lorsque vous utilisez DbUnit, ajoutez-le essentiellement.

Supplément: DirtiesContext et paramètres par défaut de l'auditeur Je ne l'ai pas présenté ici, mais vous verrez souvent un auditeur appelé `DirtiesContextTestExecutionListener`. Il a le rôle d'un gestionnaire pour le DirtiesContext. DirtiesContext sert à invalider le cache du contexte utilisé pour exécuter le code de test.
Si vous activez DirtiesContext, le code de test prendra beaucoup de temps à s'exécuter car le contexte n'est pas mis en cache test par test. Il semble être utilisé lorsque le code de test change le contexte et affecte les tests ultérieurs, mais il semble peu probable qu'il soit utilisé au niveau de base, vous n'avez donc pas à vous en soucier. Bien sûr, si vous choisissez de ne pas l'utiliser, vous devez clarifier "pourquoi ne pas l'utiliser", donc je pense qu'il vaut mieux garder le concept bas. [Référence](https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#testcontext-ctx-management-caching)
Ensuite, l'histoire change et c'est un supplément sur le paramètre par défaut de l'auditeur. Au premier coup d'œil sur la formule, je pensais que certains étaient activés par défaut et je devrais juste passer ce que je voulais ajouter. Cependant, il n'est pas réellement activé, et quand je lis le fonctionnaire se demandant ce que c'est ..., il semble que le paramètre par défaut n'est valide que s'il n'est pas spécifié dans l'annotation `TestExecutionListeners`. L'exemple ici est spécifié pour activer l'écouteur pour DbUnit, mais dans ce cas, même s'il est écrit dans le paramètre par défaut, il ne fonctionnera que si vous transmettez ce dont vous avez besoin. .. [Référence](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/context/TestContextBootstrapper.html#getTestExecutionListeners--)

SpringBootTest

Eh bien, c'est la deuxième apparition. Ici, une nouvelle propriété webEnvironment est passée. «MOCK» est défini comme valeur par défaut, et il semble que vous créez quelque chose appelé «environnement de servlet simulé». Les détails n'ont pas été écrits officiellement, mais pour autant que la sortie de la console soit vue, cela semble représenter le processus de génération du DispatcherServlet pour les tests utilisé par MockMvc.

Puisqu'il n'est pas nécessaire d'échanger la demande et la réponse avec le serveur dans la couche Dao seule, définissez "NONE" comme valeur de propriété. Cela coupe le processus de création d'un contexte pour MockMvc, ce qui rend le test un peu plus rapide.

Et bien que l'ordre ait été inversé, ce qui est passé à la propriété classes a également changé. Cela ne signifie pas que le processus a changé de manière significative, cela restreint simplement la portée des analyses de composants. Si vous regardez le code réel, il vous viendra.

DaoTestApplication.java


@EntityScan("app.db.entity")
@SpringBootApplication(scanBasePackages = "app.db.dao")
public class DaoTestApplication {

	public static void main(String[] args) {
		SpringApplication.run(DaoTestApplication.class, args);
	}

}

La cible à lire est limitée à "couche Dao" et "entité gérée par Dao". Au fur et à mesure que l'échelle de l'application augmente, le temps de chargement du package augmente également, ce qui le rend plus long. Étant donné que le traitement lié aux opérations de la base de données est fréquemment modifié et que nous voulons exécuter le code de test tout le temps, nous avons minimisé la plage de lecture pour raccourcir le temps autant que possible.

Avec ce genre d'ingéniosité, le test sera complété en environ un tour de cou après l'étirement. Si vous voulez plus de vitesse, vous devrez jouer avec la classe Config et jouer avec EntityManager, mais ce n'est pas si lent à tester, alors n'allez pas aussi loin.

Les réglages ci-dessus peuvent être effectués rapidement, et l'effet peut être obtenu tel quel, donc je pense que cela suffit au stade de base.


Annotation de méthode

Il s'agit à nouveau d'annotations, mais c'est plus simple que les annotations au niveau de la classe, donc je pense que cela vous viendra à l'esprit.

DatabaseSetup

Il s'agit d'une annotation qui définit «l'état initial» de la base de données. Si vous spécifiez le répertoire où se trouve le fichier CSV dans la propriété value, les valeurs seront regroupées dans la table de base de données en fonction du fichier CSV. Vous pouvez également créer différents états en changeant de répertoire.

Cela vous permet de recréer l'état de la table lorsque vous souhaitez commencer le test, sans avoir à INSÉRER manuellement dans la table. Je vous remercie.

Transactional

C'est un outil familier qui est souvent utilisé dans le développement d'applications réelles. Normalement, une méthode avec cette annotation se comporte comme une validation si elle fonctionne normalement, comme une restauration si elle fait quelque chose d'inattendu, et ainsi de suite. Cependant, dans le cas du code de test, si vous réécrivez la base de données à chaque fois que vous testez, la reproductibilité du test sera perdue, donc par défaut, il est annulé chaque fois que la méthode est exécutée.
En combinant les deux annotations ci-dessus, vous pouvez sauvegarder manuellement la base de données, extraire les enregistrements des fichiers, les remettre à la fin, etc.

Lorsque vous exécutez réellement le test, vous pouvez obtenir la liste des entités utilisateur avec la méthode de Dao et vérifier que vous obtenez le résultat attendu (taille de la liste = nombre d'enregistrements).


En vérifiant le processus SELECT, nous avons couvert dans une certaine mesure les bases du code de test pour les opérations de base de données, je voudrais donc jeter un coup d'œil à d'autres processus à la fois.

Supplément: Où doit être défini EntityManager? C'est un peu différent du test lui-même, mais d'après mon expérience personnelle, l'EntityManager était souvent nul et le test ne fonctionnait pas. J'étais conscient que si je déclarais EntityManager dans le champ de la classe Dao et ajoutais l'annotation `PersistenceContext`, cela fonctionnerait pour le moment, donc je ne pourrais pas le résoudre, et j'en étais accro.
Le mouvement réel est qu'un bean appelé `LocalContainerEntityManagerFactoryBean` initialise EntityManagerFactory et crée un EntityManager par DI. Pour le moment, un conteneur DI est requis, mais au printemps, ApplicationContext semble jouer ce rôle.
Comme cela fonctionne comme ci-dessus, même si vous obtenez EntityManager avec l'annotation Autowired ou PersistenceContext dans la classe de test et que vous la transmettez à la classe cible de test, cela fonctionnera pour le moment. Cependant, cela rendrait l'opération réelle sur l'application différente de l'opération sur le code de test, donc je pense personnellement qu'il est préférable de l'utiliser sous la forme suivante.
-Créer une classe de base Dao et définir EntityManager dans le champ de classe de base. (À ce stade, ajoutez l'annotation PersistenceContext) -Dans la classe de base, seul le getter d'EntityManager est exposé, et dans la classe dérivée, seul le getter est restreint. -Les méthodes de base utilisant EntityManager (fusion, persistance, etc.) sont définies du côté de la classe de base. Il n'y a pas beaucoup d'informations sur EntityManager, et j'étais accro à essayer de mal l'écrire, donc je pense qu'il vaut mieux laisser Spring au début. S'il reste coincé dans le code de test et que la mise en œuvre ne se déroule pas du tout, cela peut ne pas être bon pour la santé mentale, je vais donc l'écrire sous forme de mémorandum. [Référence](https://builder.japan.zdnet.com/sp_oracle/35067018/)
* Lorsque vous utilisez Repository, EntityManager est complètement masqué, vous n'avez donc probablement pas besoin d'en être conscient.
[Classe de base Dao](https://github.com/a-pompom/SpringBoot-MVC-Test/blob/master/dbMVCTest/src/main/java/app/db/dao/BaseDao.java) [Classe dérivée de Dao](https://github.com/a-pompom/SpringBoot-MVC-Test/blob/master/dbMVCTest/src/main/java/app/db/dao/UserDao.java)

Tester le traitement CRUD de la base de données

Du traitement CRUD, nous avons pu vérifier SELECT, alors regardons le traitement de mise à jour / création restant. Je pense que vous pouvez comprendre le plan avec les connaissances que vous avez acquises jusqu'à présent, de sorte que le code de test réel est décrit ci-dessous.

CRUDDaoTest.java(Extrait)


//Traitement pour refléter l'état après l'exécution de la méthode de test dans la base de données
	//Normalement, le traitement de la mise à jour est synchronisé avec la base de données lorsque la transaction est validée,
	//Synchroniser explicitement car il ne s'engage pas dans le processus de test
	@AfterEach
	void tearDown() {
		userDao.getEm().flush();
	}
	
	/**
	 *Vérifiez si le processus de création crée un nouvel enregistrement
	 *Vérifiez que la base de données a été réécrite comme prévu par l'entité en la comparant avec la base de données attendue
	 */
	@Test
	@DatabaseSetup(value = "/CRUD/setUp/forCreate")
	@ExpectedDatabase(value = "/CRUD/create/", assertionMode=DatabaseAssertionMode.NON_STRICT)
Un nouvel utilisateur est créé avec la méthode void create() {
		User user = new User();
		user.setUserName("test3");
		
		userDao.saveOrUpdate(user);
	}
	
	/**
	 *Vérifiez si l'enregistrement existant est mis à jour par le processus de mise à jour
	 *Vérifiez que la base de données a été réécrite comme prévu par l'entité en la comparant avec la base de données attendue
	 */
	@Test
	@DatabaseSetup(value = "/CRUD/setUp/")
	@ExpectedDatabase(value = "/CRUD/update/", assertionMode=DatabaseAssertionMode.NON_STRICT)
L'utilisateur 1 peut être réécrit avec la méthode de mise à jour void() {
		User user = new User();
		user.setUserId(1L);
		user.setUserName("test1mod");
		
		userDao.saveOrUpdate(user);
	}
	
	/**
	 *Vérifiez si l'enregistrement est supprimé par le processus de suppression
	 *Préparez un DB avant et après le traitement, et vérifiez la validité en comparant si le résultat attendu sera obtenu après la suppression.
	 */
	@Test
	@DatabaseSetup(value = "/CRUD/setUp/")
	@ExpectedDatabase(value = "/CRUD/delete/", assertionMode=DatabaseAssertionMode.NON_STRICT)
L'utilisateur 1 peut être supprimé avec la méthode de suppression d'annulation() {
		userDao.delete(1);
	}

Maintenant que nous en avons de nouveaux, jetons un coup d'œil à chacun d'eux. En outre, il y a quelques mises en garde concernant le code de test pour le traitement CURD des opérations de base de données, alors jetons un coup d'œil à celles-ci également.

AfterEach

Ceci est une annotation pour JUnit5 et décrit le processus que vous souhaitez insérer après l'exécution de chaque méthode de test. Ici, la méthode flush d'EntityManager est explicitement appelée. La méthode flush effectue le travail pour synchroniser les entités dans le contexte de persistance avec les enregistrements de la base de données. Normalement, cette méthode est automatiquement appelée lorsque la transaction est validée, sans même en avoir conscience. Référence

Cependant, dans ce code de test, vous devez RollBack pour restaurer la base de données une fois l'opération de base de données terminée. Ensuite, la méthode flush ne sera pas appelée, de sorte que le résultat attendu de la méthode de test ne sera pas reflété dans la base de données et le test ne passera pas. Il existe plusieurs façons de le gérer, mais il semble préférable d'appeler explicitement la méthode flush lorsque chaque méthode valide une transaction, c'est-à-dire lorsque le processus est terminé, tout comme exécuter une application.

À partir de ce qui précède, en appelant la méthode flush après avoir exécuté chaque méthode de test, le résultat attendu peut être vérifié correctement.

ExpectedDatabase

Il est utilisé en même temps que l'annotation DatabaseSetup, et comme son nom l'indique, il sert à vérifier l'état de la base de données après l'exécution de la méthode de test. Comme pour l'annotation DatabaseSetup, spécifiez le répertoire dans lequel le fichier CSV qui décrit l'état de la table du résultat attendu est stocké dans la valeur value. De plus, une propriété appelée "assertionMode" est définie, mais en définissant "NON_STRICT" ici, seules les colonnes spécifiées dans le fichier CSV seront vérifiées, pas toutes les colonnes.

Annotation transactionnelle

Dans cette classe de test, l '«annotation transactionnelle» est ajoutée au niveau de la classe. La définition de cette annotation au niveau de la classe revient à ajouter une annotation à toutes les méthodes de la classe. Certains tests Controller ne nécessitent pas de contrôle de transaction, mais si vous essayez de définir chaque méthode à chaque fois, des omissions se produiront, il est donc préférable de toutes les définir au niveau de la classe.


Vous avez maintenant une certaine compréhension du traitement requis pour le traitement CRUD. À la fin du niveau 2, voici quelques éléments à garder à l'esprit lors du test des opérations de base de données (car j'y suis accro). J'espère que cela vous sera utile.

À propos de la méthode de description du processus de mise à jour / suppression

Le processus ci-dessus est un processus de réécriture d'un enregistrement existant. Je pense qu'il peut traiter plusieurs enregistrements, mais dans la plupart des cas, les applications Web cibleront un enregistrement. À ce stade, des informations clés sont nécessaires pour clarifier la cible de traitement.

Je pense qu'il y a plusieurs façons de le faire, mais je pense que c'est simple et facile d'écrire "spécifier l'ID dans l'enregistrement du fichier CSV". Le point à noter ici est le concept de logique pour réaliser le test. Comme cela semble être un peu long, je l'ai ajouté dans le supplément, alors jetez un œil si vous êtes intéressé.

Supplément: Comment mettre en œuvre le test En passant, en ce qui concerne la méthode ci-dessus d'acquisition des "informations clés", préparez une classe utilitaire pour le test pour faciliter l'écriture d'un test, ou ajoutez une méthode au code côté application pour le rendre plus facile à tester. Vous ne pouvez rien faire, mais ce n'est pas recommandé.
Tout d'abord, si vous êtes coincé à écrire un test sur la création d'une classe utilitaire pour la rendre plus facile à écrire, vous ne devriez pas penser à `comment réussir le test`. Vous devriez penser à «pouvez-vous l'écrire plus simplement»? Si vous écrivez une classe de méthode de test à partir d'un simple niveau de traitement CRUD, il est facile d'imaginer à quoi ressemblerait votre code de test dans une application de niveau de travail.
En outre, on dit souvent que le code qui est facile à écrire des tests est un bon code. Ce n'est pas une histoire qui devrait être écrite en considérant la réussite du test côté application, mais il faut éviter que "les modules individuels soient étroitement couplés et qu'il devienne difficile de résoudre les dépendances au moment du test". Chose. Non seulement une telle implémentation est difficile à tester, mais elle élargit également la portée de l'influence lors de son expansion ou de sa modification, conduisant à la création de bogues inattendus.
Si vous vous concentrez sur le code de test, il sera plus proche du code de test que de l'implémentation, et vice versa, c'est une partie difficile à remarquer au début, mais il peut être bon de faire une pause et de revenir à l'essentiel. ne pas. Je l'ai écrit comme ça, mais je suis accro à l'écriture du code de test moi-même, donc je vais l'écrire comme une auto-discipline.

À propos de la valeur d'identification du processus de création

Prenons le cas de l'enregistrement d'un nouvel enregistrement dans la base de données avec une méthode de test. Par exemple, si l'ID est numéroté automatiquement et que l'ID est attribué à l'enregistrement de configuration, une duplication de clé peut se produire.

Dans ce cas, si vous définissez l'ID, l'enregistrement du jeu de résultats est également numéroté ..., mais il est plus sûr de ne pas contrôler la valeur qui est automatiquement numérotée.

En guise de solution, si vous souhaitez vérifier la génération d'un nouvel enregistrement, je pense qu'il est préférable de poursuivre la politique d'utilisation d'un fichier CSV excluant l'ID et de vérifier uniquement la colonne de contenu sans l'ID impliqué.


Beaucoup de nouveautés sont apparues au niveau 2. Cependant, si vous vous y habituez dans une certaine mesure, vous pouvez écrire de manière nette, et surtout, démarrer le serveur comme un test manuel, accéder à la page, la traiter et aller voir la base de données ... Je pense que c'est une partie très bénéfique car elle peut être vérifiée sans le faire. Par conséquent, même si vous maîtrisez la plage jusqu'au niveau 2, l'efficacité de la correction des défauts pendant le développement sera grandement améliorée.

En complétant le niveau 2, vous devriez être capable de:


À propos, Spring Boot est un cadre de création d'applications Web, il est donc courant d'utiliser des requêtes POST lors de l'exploitation réelle de la base de données. Par conséquent, au niveau 3, nous aimerions nous pencher sur la validation des requêtes POST. Le niveau augmentera, mais c'est quelque chose que vous pouvez pleinement comprendre avec vos connaissances jusqu'à présent, donc je serais heureux si vous pouviez me suivre jusqu'au bout (╹◡╹)

Niveau 3. Demande de test POST

Ensuite, regardons le code de test pour valider les requêtes POST. Comme il serait trop long de mettre l'intégralité du code à vérifier, je voudrais me concentrer sur le code de test en ne décrivant ici que le contour de l'application.

Au niveau 3, nous utiliserons la liste TODO comme sujet. C'est un traitement simple qui peut effectuer un traitement CRUD simple commun tel que le suivant.

image.png

Il y aura de nouvelles connaissances sur les demandes POST, mais si vous avez les connaissances jusqu'à présent, vous pouvez les comprendre, alors veuillez jeter un œil au code de test après un examen complet de cet article. Le code de test réel est indiqué ci-dessous. C'est un peu plus long, mais la plupart est compréhensible ... Je suis content.

TodoControllerTest.java


@DbUnitConfiguration(dataSetLoader = CsvDataSetLoader.class)
@TestExecutionListeners({
	  DependencyInjectionTestExecutionListener.class,
	  TransactionalTestExecutionListener.class,
	  DbUnitTestExecutionListener.class
	})
@AutoConfigureMockMvc
@SpringBootTest(classes = {DbMvcTestApplication.class})
@Transactional
public class TodoControllerTest {
	
	//mockMvc Mock object pour gérer les requêtes et réponses Http sans déployer sur le serveur Tomcat
	@Autowired
	private MockMvc mockMvc;
	
	@Autowired
	private TodoDao todoDao;
	
	@AfterEach
	void tearDown() {
		todoDao.getEm().flush();
	}
	
	/**
	 *Vérifiez que la vue est renvoyée correctement
	 * @throws Exception
	 */
	@Test
Todo est passé en tant que vue dans le traitement d'initialisation du vide() throws Exception {
		this.mockMvc.perform(get("/todo/init"))
			.andExpect(status().isOk())
			.andExpect(view().name("todo"));
	}
	
	/**
	 *Vérifiez que l'enregistrement acquis de DB est défini dans le modèle
	 *Cette fois, ce n'est pas un processus compliqué, donc si un enregistrement de la base de données est passé au modèle, il est considéré comme fonctionnant normalement.
	 * 
	 * @throws Exception
	 */
	@Test
	@DatabaseSetup(value = "/TODO/setUp/")
Le processus void init transmet une tâche existante au modèle() throws Exception {
		
		//Avec mockMvc/todo/Envoyez une demande d'obtention à "init"
		this.mockMvc.perform(get("/todo/init"))
		//Les enregistrements DB sont transmis au modèle sous forme de liste
			.andExpect(model().attribute("todoForm", hasProperty(
					"todoList", hasItem(
							hasProperty(
									"task", is("task1")
							)
					)
			)));
	}
	
	/**
	 *Vérifiez si de nouveaux enregistrements sont enregistrés dans le DB à partir de l'entrée à l'écran
	 * @throws Exception
	 */
	@Test
	@DatabaseSetup(value = "/TODO/setUp/create")
	@ExpectedDatabase(value = "/TODO/create/", assertionMode=DatabaseAssertionMode.NON_STRICT)
Une nouvelle tâche est enregistrée dans la base de données par le traitement de sauvegarde annulée() throws Exception {
		
		this.mockMvc.perform(post("/todo/save")
			.contentType(MediaType.APPLICATION_FORM_URLENCODED)
			.param("newTask", "newTask"));
		
	}
	
	/**
	 *Vérifiez si les enregistrements existants sont mis à jour par saisie d'écran
	 *Comme les informations d'écran ne sont pas utilisées cette fois, il n'est pas possible d'obtenir l'ID qui est automatiquement numéroté.
	 *Par conséquent, cette fois, spécifiez la cible de mise à jour manuellement.
	 *En gros, l'ordre de la liste n'est pas garanti, il semble donc nécessaire de la trier au moment de SELECT.
	 * @throws Exception
	 */
	@Test
	@DatabaseSetup(value = "/TODO/setUp/")
	@ExpectedDatabase(value = "/TODO/update/", assertionMode=DatabaseAssertionMode.NON_STRICT)
Le processus de mise à jour annulée met à jour la tâche existante() throws Exception{
		
		//"À faire" avec mockMvc/Envoyez une demande de message à «mettre à jour»
		long updateTargetId = 3L;
		int updateTargetIndex = 2;
		
		this.mockMvc.perform(post("/todo/update/" + updateTargetIndex + "/" + updateTargetId)
				.param("todoList[" + updateTargetIndex + "].task", "task3mod")
				.contentType(MediaType.APPLICATION_FORM_URLENCODED)
				);
		
	}
	/**
	 *Vérifiez si la tâche sélectionnée à l'écran est supprimée
	 * @throws Exception
	 */
	@Test
	@DatabaseSetup(value = "/TODO/setUp/")
	@ExpectedDatabase(value = "/TODO/delete/", assertionMode=DatabaseAssertionMode.NON_STRICT)
Le processus de suppression annulée supprime la tâche existante() throws Exception {
		long deleteTargetId = 3L;
		
		this.mockMvc.perform(post("/todo/delete/" + deleteTargetId)
				.contentType(MediaType.APPLICATION_FORM_URLENCODED)
				);
		
	}
}

Dans ce qui suit, nous décrirons les nouvelles méthodes liées aux requêtes POST et les points à noter lors du traitement des requêtes POST. Je ne pense pas que ce soit effrayant parce que vous ne faites que définir des paramètres et faire des demandes.

Paramètres POST

Un point notable du code de test de la requête POST est de savoir comment définir les paramètres passés à la requête. Cependant, si vous comprenez dans une certaine mesure la requête POST, vous pouvez la définir de manière intuitive. Pour les requêtes POST, nous le recommandons car il est clairement écrit en MDN.

À propos, dans le code de test utilisant MockMvc, la partie où la requête GET a été effectuée par la méthode perform au niveau 1 sera remplacée par la requête POST. Après cela, vous pouvez passer des paramètres au format clé-valeur avec la méthode param, vous pouvez donc simplement faire une demande en fonction du formulaire passé dans la demande réelle. Vous pouvez également appeler la méthode param dans une requête GET, mais dans ce cas, elle sera envoyée en tant que paramètre de requête. Ici, la demande POST est soumise en fonction du formulaire, de sorte que les paramètres sont stockés dans le corps de la demande.

De plus, bien que le contentType fonctionne sans le spécifier, je pense qu'il est préférable de le définir de manière à ce qu'il soit aussi proche que possible de la demande POST réelle.


Points à garder à l'esprit lors du test des requêtes POST

Cette fois, nous vérifions principalement si la base de données est correctement mise à jour par la requête POST. À ce stade, le problème est ce qui est traité par la requête POST. Je voudrais jeter un rapide coup d'œil à chaque processus CRUD.

Lors de la création d'un nouvel enregistrement, les paramètres du nouvel enregistrement sont indépendants de la table, vous ne devriez donc pas vous en préoccuper.

Cette fois, la plage de vérification est jusqu'à ce que le nom de la vue soit passé à View, donc c'est OK si vous pouvez vérifier l'objet passé à Model. Il n'y a donc rien à craindre ici non plus ...

Étant donné que l'ID à supprimer est déterminé par le chemin de la demande, il est nécessaire d'indiquer clairement la cible de suppression lors de la création d'une demande avec MockMvc. Ces conventions doivent être évitées dans la "mise en œuvre", mais je pense que vous ne devez pas trop vous inquiéter dans le code de test. En premier lieu, le code de test définit l'état de la base de données comme «fixe», donc plutôt que de supposer des changements et des extensions, vous devriez vous concentrer sur la partie «si une sortie constante est toujours obtenue à partir d'une entrée constante». La force du code de test est que vous pouvez toujours obtenir le même résultat quel que soit le nombre de fois que vous l'exécutez, donc je pense personnellement que vous devriez réfléchir à la façon de l'écrire séparément de l'implémentation.

La même chose peut être dite pour le processus de mise à jour. Cependant, lors de la mise à jour, si vous souhaitez cibler un enregistrement dans la liste des entités, vous devez prendre certaines mesures telles que la séparation de la cible d'édition de la liste et le stockage dans une entité de mise à jour distincte. Dans ce processus de mise à jour, l'ID de l'entité d'index de la liste est fixe, mais l'ordre de la liste n'est fondamentalement pas garanti, donc dans l'application de niveau métier, sous la forme ci-dessus, "toujours Il est nécessaire de créer un état où le même résultat peut être obtenu à partir de la même entrée. J'aimerais écrire à ce sujet lorsque je m'habituerai au code de test d'écran (désir).


Bien qu'il ait été étiqueté comme niveau 3, la plupart d'entre eux étaient sous la forme d'un examen complet jusqu'à présent, donc je comprends ... je comprends ... !! Je serais très heureux si vous pouviez (:) En comprenant le code de test de niveau 3, vous pouvez:


Résumé

Cela fait plus longtemps que prévu, mais je peux maintenant voir comment écrire un code de test simple pour le traitement CRUD. Lors de l'écriture de la partie implémentation, il est nécessaire d'être conscient de choses dont vous ne seriez normalement pas conscient, et je pense qu'il y avait des parties difficiles. Cependant, l'écriture de code de test approfondira votre compréhension du framework et du langage, rationalisera le développement et fournira de nombreux avantages.

Et le meilleur de tous, la joie de réussir un test est quelque chose que vous ne pouvez pas apprécier avec la seule implémentation, et une fois que vous avez surmonté le premier obstacle, écrire du code de test devient très amusant.

Grâce à cet article, je pourrais peut-être écrire du code de test avec Spring Boot ...? Je vous serais reconnaissant si vous pouviez réfléchir. Je suis encore immature sur le code de test, donc j'aimerais voir plus d'explications sur le code de test.

Recommended Posts

Écrire du code de test avec Spring Boot
Écrivons un code de test pour la fonction de connexion avec Spring Boot
Testez le contrôleur avec Mock MVC dans Spring Boot
Exécuter un projet Spring Boot avec VS Code
Définir le paramètre contextuel dans Spring Boot
Multi-projets Spring Boot 2 avec Gradle
Écrire du code de type Java8 en Java8
Changements majeurs dans Spring Boot 1.5
NoHttpResponseException dans Spring Boot + WireMock
Comment écrire un test unitaire pour Spring Boot 2
[Compatible JUnit 5] Ecrire un test en utilisant JUnit 5 avec Spring boot 2.2, 2.3
[JUnit 5] Ecrivez un test de validation avec Spring Boot! [Test de paramétrage]
Testez la classe injectée sur le terrain dans le test de démarrage Spring sans utiliser le conteneur Spring
Spring Boot Hello World dans Eclipse
Développement d'applications Spring Boot dans Eclipse
Points de révision du code de l'application Spring Boot
Environnement Java Spring dans vs Code
Programmation Spring Boot avec VS Code
Implémenter l'API REST avec Spring Boot
Qu'est-ce que @Autowired dans Spring Boot?
Utiliser DBUnit pour le test Spring Boot
Implémenter l'application Spring Boot dans Gradle
Comment utiliser Thymeleaf avec Spring Boot
Exemple de code qui utilise le moteur de modèle Moustache avec Spring Boot
Exemple de code pour le test unitaire d'un contrôleur Spring Boot avec MockMvc
[Spring Boot] Jusqu'à ce que @Autowired soit exécuté dans la classe de test [JUnit5]
Introduire RSpec et écrire le code de test unitaire
Lancer un (ancien) projet Spring Boot avec IntelliJ
Créer une image Spring Boot + Docker avec Gradle
Priorité d'accès aux fichiers statiques dans Spring Boot
Sortie du journal Spring Boot au format json
Mémorandum de téléchargement de fichier local avec Spring Boot
Effectuer un test de confirmation de transaction avec Spring Boot
Essayez d'utiliser Spring Boot avec VS Code
Créer un projet Java Spring Boot avec IntelliJ
Desserrer la vérification de la syntaxe de Thymeleaf dans Spring Boot
Le test Spring Boot @WebMvcTest active la sécurité par défaut de Spring Security
[Entraine toi! ] Affichez Hello World avec Spring Boot
Utiliser la méthode de requête DynamoDB avec Spring Boot
Pour écrire des données de réponse directement dans Spring
Écrire du code difficile à tester
DI SessionScope Bean dans le filtre Spring Boot 2
Test de validation de classe de formulaire avec Spring Boot
Modifier le délai d'expiration de la session dans Spring Boot
Défi Spring Boot
Forme de botte de printemps
Spring Boot Rappelez-vous
gae + botte à ressort
Cookie SameSite dans Spring Boot (Spring Web MVC + Tomcat)
À propos de la conception de Spring Boot et de l'environnement de test unitaire
Traitement asynchrone avec exécution régulière dans Spring Boot
Sortie des journaux de demande et de réponse avec Spring Boot
JUnit 5: Comment écrire des cas de test dans enum
Écrivez rapidement un test RestController avec Spring Boot + Spock
Utiliser le filtre de servlet avec Spring Boot [compatible Spring Boot 1.x, 2.x]
Comment ajouter un chemin de classe dans Spring Boot
Conseils Java - Créez un projet Spring Boot avec Gradle
Comment écrire du code de test avec la certification de base
Comment se lier avec un fichier de propriétés dans Spring Boot