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.
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.
Un schéma simple de la couverture ci-dessus est le suivant.
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.
Pour plus de détails, veuillez consulter GitHub pom.xml.
Spring-Boot(2.1.8)
Paramètres de démarrage
Test lié au 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>
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.
Niveau 1. Testez Hello World
Niveau 2. Tester les opérations de la base de données
Niveau 3. Demande de test POST
(Niveau 4. Testez les applications nécessitant une connexion)
Concernant le niveau 4, il est nécessaire de mobiliser toutes les connaissances du code de test repris dans cet article, et le volume sera important, je vais donc le diviser en un autre article.
La couverture des niveaux 1 à 3 couverts dans cet article est illustrée approximativement comme suit.
Commençons par 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.
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.
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.
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
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.
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)
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.
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.
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.
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
.
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.
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.
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.
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.
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.
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.
Exécutez le processus pour obtenir l'enregistrement par SELECT à partir de la base de données côté application
Vérifiez que le résultat de l'acquisition est comme prévu en regardant l'enregistrement acquis
Exécute le processus de mise à jour et de suppression des enregistrements de la base de données côté application
Comparez les enregistrements de la base de données avant et après l'application du processus et vérifiez que les résultats sont conformes aux attentes.
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.
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.
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.
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.
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
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));
}
}
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.
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.
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.
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.
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.
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é.
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 (╹◡╹)
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.
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.
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.
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:
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