当前位置:网站首页>Éléments fondamentaux des tests de base de données

Éléments fondamentaux des tests de base de données

2021-10-22 10:54:04 InfoQ

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Tout le monde sait,Dans l'industrie des essais,La simulation de bases de données et d'autres couches de persistance peut réduire l'efficacité des tests.Lors des essais, Si un composant ne fait pas partie de l'essai , Il est difficile de tester son interaction avec d'autres composants .Malheureusement,L'industrie se concentre uniquement sur les tests au niveau fonctionnel,Peu de gens ont été formés à d'autres types de tests.Cet article corrige ce problème en introduisant le concept de test de base de données. Ces techniques s'appliquent également à d'autres types de mécanismes de persistance , Comme appeler un micro - service ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Pour savoir comment tester la base de données ,On commence.“Oublie ça.” Quelques concepts liés aux tests unitaires et aux tests d'intégration . Pour être franc , Aujourd'hui, la définition de ces termes s'écarte de leur sens original .Alors..., Dans le reste de l'article , Nous ne les utiliserons plus ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Le but essentiel des tests est de générer des informations . Un cas d'essai devrait générer des informations sur ce qui est testé après l'exécution , Ces informations sont inconnues. . Plus d'informations sont générées, mieux c'est .Donc,,Nous avons tendance à“ Un cas d'essai devrait, dans la mesure du possible, fournir les affirmations nécessaires pour prouver un fait ”,Au lieu de“ Un cas d'essai ne fournit qu'une seule assertion ”."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Un autre point problématique est “ Tous les tests doivent être indépendants ”. Les gens interprètent souvent ce point de vue à tort , Pensez que chaque test devrait être utilisé Mock, Chaque fonction que vous testez doit être isolée de ses dépendances . Mais ça n'a aucun sens ,Parce que dans un environnement de production, Ces fonctions ne peuvent être isolées de leurs dépendances .Au contraire., Vous devriez essayer autant que possible dans un environnement de production , Pour découvrir le plus de problèmes possible ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"“ Tous les tests doivent être indépendants ” Ce qui veut vraiment dire , Chaque test peut être exécuté indépendamment des autres .Ou,En d'autres termes,, Vous pouvez suivre n'importe quel ordre 、 Exécuter chaque test ou ensemble de tests à tout moment ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Beaucoup de gens compliquent les choses en testant . Ils font tous les tests ( Même chaque cas d'essai individuel ) La base de données a été entièrement reconstruite avant . Cela pose des problèmes ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Tout d'abord,, Les tests sont lents. . Il faut du temps pour créer une nouvelle base de données et remplir les données , C'est souvent la cause directe du ralentissement des tests de base de données , Et cela, à son tour, rend les gens réticents à effectuer des tests , Je ne prépare même pas ce genre de test ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Un autre problème concerne le nombre d'enregistrements dans la base de données . Quand il n'y a qu'un seul code dans la base de données , Certains codes fonctionnent bien , Mais quand il y a des centaines d'enregistrements, ils échouent .Dans certains cas, Comme l'absence d'une requête WHEREClause, Il suffit de deux enregistrements pour que le test échoue ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Donc,, Nous devons écrire des tests côté base de données . Peu importe quand , Vous devriez tous effectuer des tests avec une copie des données de l'environnement de production , Et les regarder tous réussir ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"“"},{"type":"link","attrs":{"href":"https:\/\/grauenwolf.github.io\/DotNet-ORM-Cookbook\/index.htm","title":"","type":null},"content":[{"type":"text","text":".NET ORM Cookbook"}]},{"type":"text","text":"” Voici un bon exemple .Ce projet a1600 Plusieurs cas d'essai côté base de données , Ils peuvent être exécutés dans n'importe quel ordre . Pour comprendre les principes , Nous allons construire quelques CRUD Des tests pour expliquer ces concepts ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" La question suivante est celle de la cohérence .Les gens disent souvent, Chaque test doit être parfaitement cohérent ,C'est - à - dire, Chaque fois que vous effectuez le même test, vous devriez obtenir les mêmes résultats . Par souci de cohérence , Les données d'essai générées dans le temps ou au hasard ne peuvent pas être utilisées , Et ne peut pas être affecté par l'environnement ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Lors de la mise à l'essai de la base de données , C'est impossible . Parce qu'il y a toujours des problèmes imprévisibles , Comme les problèmes de connexion au réseau 、 Problème de disque 、Données anciennes,Attendez un peu!."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Mais cela ne veut pas dire que les tests qui n'ont pas cette cohérence ne sont pas fiables . Bien que certaines propriétés puissent être incohérentes , Mais le test renvoie le même résultat la plupart du temps . Les échecs aléatoires vous permettent de savoir dans quelles circonstances l'application se comporte ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Attention!: Tous les "},{"type":"link","attrs":{"href":"https:\/\/github.com\/Grauenwolf\/TestingWithDatabases\/tree\/main\/TestingWithDatabases","title":"","type":null},"content":[{"type":"text","text":"Exemple"}]},{"type":"text","text":" Peut être GitHubOn le trouve.."}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Créer un enregistrement"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Notre premier test a été de créer un enregistrement .Par souci de simplicité,Nous avons choisiEmployeeClassificationCatégorie, Il n'a que quatre champs :"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"int EmployeeClassificationKey \nstring? EmployeeClassificationName \nbool IsEmployee \nbool IsExempt\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Lors de la vérification du schéma de base de données ,Nous avons découvertEmployeeClassificationKey Est un champ numérique auto - généré , Alors laisse tomber .EmployeeClassificationName Il y a une contrainte d'unicité , C'est l'endroit où beaucoup de gens ont des ennuis ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"[TestMethod]\npublic async Task Example1_Create()\n{\n var repo = CreateEmployeeClassificationRepository();\n var row = new EmployeeClassification()\n {\n EmployeeClassificationName = \"Test classification\",\n };\n await repo.CreateAsync(row);\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Ce test n'est pas reproductible , Parce que la deuxième fois que je l'ai lancé , Le même nom existe déjà .Pour résoudre ce problème, Nous avons ajouté une distinction , Comme un horodatage ou GUID."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"[TestMethod]\npublic async Task Example2_Create()\n{\n var repo = CreateEmployeeClassificationRepository();\n var row = new EmployeeClassification()\n {\n EmployeeClassificationName = \"Test \" + DateTime.Now.Ticks,\n };\n await repo.CreateAsync(row);\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Ce test ne teste pas vraiment quelque chose .Nous savons que,CreateAsynAucune exception n'a été lancée, Mais ça pourrait être une méthode vide . Pour que le test soit complet , Nous devons ajouter l'opération de lecture ."}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":" Créer et lire des enregistrements "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Lors de la création et de la lecture des tests , Nous avons d'abord veillé à ce que la base de données puisse lire non 0La clé de.Et puis, On lit les enregistrements avec cette clé , Et vérifier que les champs d'enregistrement lus à partir de la base de données sont les mêmes que ceux originaux ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"[TestMethod]\npublic async Task Example3_Create_And_Read()\n{\n var repo = CreateEmployeeClassificationRepository();\n var row = new EmployeeClassification()\n {\n EmployeeClassificationName = \"Test \" + DateTime.Now.Ticks,\n };\n var key = await repo.CreateAsync(row);\n Assert.IsTrue(key != 0);\n\n var echo = await repo.GetByKeyAsync(key);\n Assert.AreEqual(key, echo.EmployeeClassificationKey);\n Assert.AreEqual(row.EmployeeClassificationName, echo.EmployeeClassificationName);\n Assert.AreEqual(row.IsEmployee, echo.IsEmployee);\n Assert.AreEqual(row.IsExempt, echo.IsExempt);\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Attention!: Quand aucun enregistrement n'a été lu RepositoryNe lance pas d'exception,Alors..., Ajouter avant l'affirmation au niveau de l'attribut Assert.IsNotNull, Mieux saisir les cas d'échec des tests ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Trop d'affirmations peuvent causer des problèmes .Tout d'abord,, Si une affirmation échoue , Tu ne sais pas lequel .IsEmployeeEtIsExemptTous.BooleanType, Donc vous n'avez aucun moyen d'utiliser l'information contextuelle pour déterminer lequel a échoué . Vous pouvez résoudre ce problème en ajoutant plus d'informations , Si le cadre d'essai le supporte ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Deuxièmement,, Difficile à diagnostiquer . Si plusieurs affirmations échouent , Seul le premier a été capturé , Les informations suivantes ont été perdues .Pour résoudre ce problème,Nous avons utiliséAssertionScopeObjet. Toutes les affirmations qui y sont liées sont regroupées ,Inusing Le bloc de code a finalement été signalé uniformément .AssertionScope Des exemples de mise en œuvre de "},{"type":"link","attrs":{"href":"https:\/\/github.com\/Grauenwolf\/TestingWithDatabases\/blob\/main\/TestingWithDatabases\/AssertionScope.cs","title":"","type":null},"content":[{"type":"text","text":"GitHub"}]},{"type":"text","text":"On le trouve.. Pour des créations plus complexes ,Peut envisager d'utiliser"},{"type":"link","attrs":{"href":"https:\/\/fluentassertions.com\/introduction#assertion-scopes","title":"","type":null},"content":[{"type":"text","text":"Type de fluxAssertionScope"}]},{"type":"text","text":"OuNUnitDe"},{"type":"link","attrs":{"href":"https:\/\/docs.nunit.org\/articles\/nunit\/writing-tests\/assertions\/multiple-asserts.html","title":"","type":null},"content":[{"type":"text","text":"Assert.Multiple"}]},{"type":"text","text":"."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"[TestMethod]\npublic async Task Example4_Create_And_Read()\n{\n var repo = CreateEmployeeClassificationRepository();\n var row = new EmployeeClassification()\n {\n EmployeeClassificationName = \"Test \" + DateTime.Now.Ticks,\n };\n var key = await repo.CreateAsync(row);\n Assert.IsTrue(key != 0, \"New key wasn't created or returned\");\n\n var echo = await repo.GetByKeyAsync(key);\n\n using (var scope = new AssertionScope(stepName))\n {\n scope.AreEqual(expected.EmployeeClassificationKey, actual.EmployeeClassificationKey, \"EmployeeClassificationKey\");\n scope.AreEqual(expected.EmployeeClassificationName, actual.EmployeeClassificationName, \"EmployeeClassificationName\");\n scope.AreEqual(expected.IsEmployee, actual.IsEmployee, \"IsEmployee\");\n scope.AreEqual(expected.IsExempt, actual.IsExempt, \"IsExempt\");\n } \n} \n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Avec de plus en plus de cas d'essai , Ça va devenir un travail ennuyeux et répétitif , Nous avons donc besoin d'une méthode auxiliaire ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"row.EmployeeClassificationKey = key;\nPropertiesAreEqual(row, echo); \n\nstatic void PropertiesAreEqual(EmployeeClassification expected, EmployeeClassification actual, string? stepName = null)\n{\n Assert.IsNotNull(actual, $\"Actual value for step {stepName} is null.\");\n Assert.IsNotNull(expected, $\"Expected value for step {stepName} is null.\");\n\n using (var scope = new AssertionScope(stepName))\n {\n scope.AreEqual(expected.EmployeeClassificationKey, actual.EmployeeClassificationKey, \"EmployeeClassificationKey\");\n scope.AreEqual(expected.EmployeeClassificationName, actual.EmployeeClassificationName, \"EmployeeClassificationName\");\n scope.AreEqual(expected.IsEmployee, actual.IsEmployee, \"IsEmployee\");\n scope.AreEqual(expected.IsExempt, actual.IsExempt, \"IsExempt\");\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Vous n'avez pas besoin d'écrire cette méthode manuellement ,Utilisation directe"},{"type":"link","attrs":{"href":"https:\/\/github.com\/GregFinzer\/Compare-Net-Objects","title":"","type":null},"content":[{"type":"text","text":"CompareNETObjects"}]},{"type":"text","text":"Bibliothèque."}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Création、 Mise à jour et lecture des enregistrements "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Nous allons mettre à jour les enregistrements pour les tests suivants , Impliquant une opération de création et deux opérations de lecture ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"[TestMethod]\npublic async Task Example5_Create_And_Update()\n{\n var repo = CreateEmployeeClassificationRepository();\n var version1 = new EmployeeClassification()\n {\n EmployeeClassificationName = \"Test \" + DateTime.Now.Ticks,\n };\n var key = await repo.CreateAsync(version1);\n Assert.IsTrue(key != 0, \"New key wasn't created or returned\");\n version1.EmployeeClassificationKey = key;\n\n var version2 = await repo.GetByKeyAsync(key);\n PropertiesAreEqual(version1, version2, \"After created\");\n\n version2.EmployeeClassificationName = \"Modified \" + DateTime.Now.Ticks;\n await repo.UpdateAsync(version2);\n\n var version3 = await repo.GetByKeyAsync(key);\n PropertiesAreEqual(version2, version3, \"After update\");\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Pour savoir pourquoi l'opération de comparaison a échoué ,Nous donnonsPropertiesAreEqual La méthode ajoute stepNameParamètres."}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":" Créer et supprimer des enregistrements "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Jusqu'à présent, Nous avons couvert CRUDDeC、REtU,C'est presqueDC'est. Dans le test de suppression , Nous lisons encore les données deux fois .Mais,Nous utiliseronsRepositoryUne autre façon, Retour lorsque l'enregistrement ne peut être trouvé null.Si votreRepository Il n'y a pas de moyen ,Voir par.7Exemples."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"[TestMethod]\npublic async Task Example6_Create_And_Delete()\n{\n var repo = CreateEmployeeClassificationRepository();\n var version1 = new EmployeeClassification()\n {\n EmployeeClassificationName = \"Test \" + DateTime.Now.Ticks,\n };\n var key = await repo.CreateAsync(version1);\n Assert.IsTrue(key != 0, \"New key wasn't created or returned\");\n version1.EmployeeClassificationKey = key;\n\n var version2 = await repo.GetByKeyOrNullAsync(key);\n Assert.IsNotNull(version2, \"Record wasn't created\");\n PropertiesAreEqual(version1, version2, \"After created\");\n\n await repo.DeleteByKeyAsync(key);\n\n var version3 = await repo.GetByKeyOrNullAsync(key);\n Assert.IsNull(version3, \"Record wasn't deleted\");\n}\n[TestMethod]\npublic async Task Example7_Create_And_Delete()\n{\n var repo = CreateEmployeeClassificationRepository();\n var version1 = new EmployeeClassification()\n {\n EmployeeClassificationName = \"Test \" + DateTime.Now.Ticks,\n };\n var key = await repo.CreateAsync(version1);\n Assert.IsTrue(key != 0, \"New key wasn't created or returned\");\n version1.EmployeeClassificationKey = key;\n\n var version2 = await repo.GetByKeyAsync(key);\n PropertiesAreEqual(version1, version2, \"After created\");\n\n await repo.DeleteByKeyAsync(key);\n\n try\n {\n await repo.GetByKeyAsync(key);\n Assert.Fail(\"Expected an exception. Record wasn't deleted\");\n }\n catch (MissingDataException)\n {\n \/\/Expected\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Si votre base de données utilise Soft delete , Vous devez également vérifier si l'enregistrement correspondant a été mis à jour pour supprimer l'étiquette . Pour ce faire, on peut utiliser les lignes de code suivantes ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"var version4 = await GetEmployeeClassificationIgnoringDeletedFlag(key);\nAssert.IsNotNull(version4, \"Record was hard deleted\");\nAssert.IsTrue(version4.IsDeleted);\n"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":" Amélioration de la création d'enregistrements "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Dans le premier test , Les colonnes de données optionnelles utilisent toujours les valeurs par défaut . Ceci peut être résolu par des tests basés sur les données . L'exemple suivant s'adresse à  MSTest, Mais d'autres cadres d'essai traditionnels ont quelque chose de similaire ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"[TestMethod]\n[DataTestMethod, EmployeeClassificationSource]\npublic async Task Example9_Create_And_Read(bool isExempt, bool isEmployee)\n{\n var repo = CreateEmployeeClassificationRepository();\n var row = new EmployeeClassification()\n {\n EmployeeClassificationName = \"Test \" + DateTime.Now.Ticks,\n IsExempt = isExempt,\n IsEmployee = isEmployee\n };\n var key = await repo.CreateAsync(row);\n Assert.IsTrue(key > 0);\n Debug.WriteLine(\"EmployeeClassificationName: \" + key);\n\n var echo = await repo.GetByKeyAsync(key);\n Assert.AreEqual(key, echo.EmployeeClassificationKey);\n Assert.AreEqual(row.EmployeeClassificationName, echo.EmployeeClassificationName);\n Assert.AreEqual(row.IsEmployee, echo.IsEmployee);\n Assert.AreEqual(row.IsExempt, echo.IsExempt);\n}\n\npublic class EmployeeClassificationSourceAttribute : Attribute, ITestDataSource\n{\n public IEnumerable GetData(MethodInfo methodInfo)\n {\n for (var isExempt = 0; isExempt < 2; isExempt++)\n for (var isEmployee = 0; isEmployee < 2; isEmployee++)\n yield return new object[] { isExempt == 1, isEmployee == 1 };\n }\n\n public string GetDisplayName(MethodInfo methodInfo, object[] data)\n {\n return $\"IsExempt = {data[0]}, IsEmployee = {data[1]}\";\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Maintenant, Nous pouvons créer plusieurs enregistrements pour un seul test , Besoin de la capacité de voir quels enregistrements ont été créés dans la base de données .InMSTestMoyenne,On peut utiliserDebug.WriteLinePour enregistrer. Si vous utilisez un autre cadre de test , Vous pouvez vous référer à leur documentation , Trouver la méthode appropriée ."}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Enregistrement du filtre"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Jusqu'à présent, il n'y a eu qu'un seul enregistrement ,Mais certainsRepository La méthode renvoie plusieurs enregistrements , Cela pose des défis supplémentaires ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Dans les tests suivants , On cherche IsEmployee = trueEtIsExempt = falseLes dossiers de. Nous devons préparer à l'avance les enregistrements appariés et non appariés dans la base de données ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Nous avons besoin de deux affirmations ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":" Assertion renvoie l'enregistrement correspondant que nous avons inséré à l'avance ."}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":" Assertion ne renvoie pas d'enregistrement non apparié ."}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Notez la deuxième affirmation . Il ne s'agit pas seulement de vérifier que nos nouveaux enregistrements non appariés ne seront pas retournés , Vérifiez également les autres enregistrements non appariés , Il s'agit de documents préexistants ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"[TestMethod]\npublic async Task Example10_Filtered_Read()\n{\n var repo = CreateEmployeeClassificationRepository();\n\n var matchingSource = new List();\n for (var i = 0; i < 10; i++)\n {\n var row = new EmployeeClassification()\n {\n EmployeeClassificationName = \"Test \" + DateTime.Now.Ticks + \"_A\" + i,\n IsEmployee = true,\n IsExempt = false\n };\n matchingSource.Add(row);\n }\n\n var nonMatchingSource = new List();\n for (var i = 0; i < 10; i++)\n {\n var row = new EmployeeClassification()\n {\n EmployeeClassificationName = \"Test \" + DateTime.Now.Ticks + \"_B\" + i,\n IsEmployee = false,\n IsExempt = false\n };\n nonMatchingSource.Add(row);\n }\n for (var i = 0; i < 10; i++)\n {\n var row = new EmployeeClassification()\n {\n EmployeeClassificationName = \"Test \" + DateTime.Now.Ticks + \"_C\" + i,\n IsEmployee = true,\n IsExempt = true\n };\n nonMatchingSource.Add(row);\n }\n await repo.CreateBatchAsync(matchingSource);\n await repo.CreateBatchAsync(nonMatchingSource);\n\n var results = await repo.FindWithFilterAsync(isEmployee: true, isExempt: false);\n\n foreach (var expected in matchingSource)\n Assert.IsTrue(results.Any(x => x.EmployeeClassificationName == expected.EmployeeClassificationName));\n\n var nonMatchingRecords = results.Where(x => x.IsEmployee == false || x.IsExempt == true).ToList();\n Assert.IsTrue(nonMatchingRecords.Count == 0,\n $\"Found unexpected row(s) with the following keys \" +\n string.Join(\", \", nonMatchingRecords.Take(10).Select(x => x.EmployeeClassificationKey)));\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Nous ne vérifions pas le nombre d'enregistrements . Sauf si vous filtrez les données en fonction de valeurs uniques dans le test ,Sinon, Si d'autres tests utilisent la même base de données ,Il y aura un problème. Ces problèmes apparaissent généralement dans les bases de données fragmentées , Ou lors de l'exécution parallèle d'un cas d'essai ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Au fil du temps,Vous verrez, Le nombre d'enregistrements de données retournés continuera d'augmenter . Lorsque l'augmentation du nombre d'enregistrements ralentit l'essai , Vous devez considérer ces opérations ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":" Réinitialiser la base de données ."}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":" Amélioration de l'index ."}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"SupprimerRepository Ces méthodes ."}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Réinitialiser la base de données est l'opération la plus rapide , Mais d'habitude, je le recommande rarement . Bien qu'il y ait beaucoup d'enregistrements dans la base de données de test , Mais par rapport à la base de données de production , Il manque encore beaucoup d'ordres de grandeur . Cela signifie que la Réinitialisation de la base de données ne cache que les problèmes de performance ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" L'amélioration de l'index a ses propres difficultés , Parce que chaque index réduit les performances d'écriture .Mais, Si tu peux supporter , L'amélioration de l'index permettra aux utilisateurs d'avoir une meilleure expérience ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Cette dernière option doit également être prise en considération , Surtout quand ces méthodes renvoient beaucoup de données .GetAll La méthode ne renvoie que quelques dizaines d'enregistrements sans problème ,Mais si vous revenez1Dix mille enregistrements, Vous ne devriez pas envisager de l'utiliser dans un environnement de production , Tu devrais l'enlever ."}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":" À propos du nettoyage "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Beaucoup de gens recommandent de supprimer les enregistrements créés à la fin du test , Quelqu'un va même mettre tout le test en une seule transaction , Assurez - vous que les enregistrements nouvellement créés sont supprimés ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"En général, Je n'encourage pas cette pratique . La base de données de test ne contient généralement pas beaucoup de données , Faire reculer une transaction ne fait que manquer l'occasion d'accumuler des données ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"En plus, Les opérations de nettoyage échouent parfois , Surtout si vous supprimez manuellement un enregistrement au lieu de faire reculer une transaction . Ce test fragile est quelque chose que nous devons éviter ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"En parlant d'affaires, Il a été suggéré d'utiliser une seule transaction pour l'ensemble du test, du début à la fin . C'est peut - être un anti - modèle sérieux , Ça vous empêche d'effectuer des tests en parallèle , Parce qu'il pourrait bloquer la base de données ( Il peut y avoir une impasse ).En plus..., Certaines bases de données (Par exemple,SQL Server) Le retour en arrière est très lent ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Cela dit,, Il n'y a pas d'erreur en ajoutant une étape de nettoyage au test , Fais attention , Ne laissez pas le temps d'essai devenir trop long ou augmenter les conditions d'échec ."}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Conclusions"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Les tests de la couche de persistance ne sont pas les mêmes que ceux des classes et des méthodes . Ces techniques ne sont pas difficiles à maîtriser , Comme toutes les autres technologies , Il faut s'entraîner pour les maîtriser . Commencez par un simple CRUD La scène commence , Et passer à des scènes complexes , Comme les tests parallèles 、Échantillonnage aléatoire、 Test de performance et Scan complet de l'ensemble de données ."}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Profil de l'auteur"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Jonathan Allen"},{"type":"text","text":"In90 Début du développement d'une clinique de santé à la fin des années MISProjets, Et les faire passer progressivement de AccessEtExcel Devenir une solution d'entreprise . Après cinq ans de développement de systèmes automatisés de négociation pour le secteur financier , Il est devenu consultant pour plusieurs projets , Y compris l'entrepôt de robots UI、 La couche intermédiaire du logiciel de recherche sur le cancer , Et la demande de Big Data d'une grande compagnie d'assurance immobilière . Dans le temps libre , Il aime étudier 16 Les arts martiaux du siècle , Et écrire des articles connexes ."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Lien vers le texte original"},{"type":"text","text":":"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/www.infoq.com\/articles\/Testing-With-Persistence-Layers\/","title":"","type":null},"content":[{"type":"text","text":"The Fundamentals of Testing with Persistence Layers"}]}]}]}

版权声明
本文为[InfoQ]所创,转载请带上原文链接,感谢
https://chowdera.com/2021/10/20211022104253091x.html

随机推荐