Nous venons d'un autre test - nous testons la base de données sur MSTest

Le test comme principe universel


Nous célébrons le millénaire depuis près d'un quart de siècle, et les tests ne font que commencer dans nos vies ... Il est difficile de convaincre les développeurs novices d'utiliser cette technique incroyable dans leur travail ... Que pouvons-nous dire des développeurs, de simples mortels, et il n'est pas toujours possible de comprendre que les tests sont la base des systèmes durables! Comme il est difficile de convaincre une vendeuse que tester un nouveau produit ne signifie pas le manger! Même les agents de sécurité expérimentés travaillent évidemment à l'ancienne - ils essaient de rattraper leur retard et de sélectionner le test. Et vous ne leur prouverez pas que si le Seigneur Dieu lui-même ne dédaigne pas d'utiliser le TDD dans son travail (souvenez-vous du Grand Déluge), alors, comme on dit, Dieu lui-même a ordonné ...

Le nombre de divorces augmente - pourquoi? Oui tout de même! TDD! Testez d'abord - puis mariez-vous! Non, les petits hommes crédules en manteau de peau de mouton ample, avides de publicités exploitant le sexe, mettent une jeune femme en production ...

Eh bien, nous sommes avec vous d'un autre test, d'abord des tests - puis tout le reste!

Je reconnais le testeur par la démarche ...


Donc, quand j'ai commencé à écrire la première base de données de code suivante, j'y ai pensé, pourquoi ne pas faire des tests automatiques de ma couche DAL directement sur les tests intégrés dans VisualStudio?

Et je l'ai fait! Transparent pour EntityFramework, sans aucun tour de passe-passe sous les couvertures et fraude avec de faux objets. Peu importe - découvrez VS, habillez-vous comme des testeurs et partez! (Je m'habille toujours comme un testeur)

Vêtements de testeur
image

Mentez à tout le monde, c'est du test!


Cas de vie:
A travaillé sur un projet avec le code suivant:

ObjectLink link = this.ObjectLinks.ToList().Where(x => x.SpotCode.ToLowerInvariant() == code.ToLowerInvariant()).SingleOrDefault(); 

Ce code n'a pas fait l'objet de tests, car il n'avait pas le temps - il était urgent de lancer de nouvelles fonctionnalités liées au marketing. Tout fonctionnait lors de la vérification manuelle, et j'étais déjà détendu ... mais Bill Gates se glissa inaperçu ...

C'était un automne poétique de Saint-Pétersbourg, de la neige et de la pluie qui striaient doucement mon visage, de la saleté coulait joyeusement des jambes du pantalon et des filles inconnues souriaient à travers le maquillage étalé, versant des camions en passant ... Je suis déjà allé chercher mon doigt dans mon nez, quand c'était perfide, sans déclarer la guerre, avant à l'aube, Microsoft a coupé les barbelés et a publié la mise à jour de base 3.0. L'hébergeur a été mis à jour, j'ai également mis à jour, pourquoi aller dans l'ancien - suis-je pire qu'un hébergeur? J'ai tout vérifié et déployé la mise à jour ... puis j'ai déployé mes yeux! La nouvelle fonctionnalité n'a pas fonctionné! il semblerait que je l'ai testé avant - que pourrait-il arriver?

Et c'est arrivé: le vieux Billy a décidé de le couper de LINQ ToLowerInvariant ... maintenant vous devez l'appeler à l'avance et insérer la valeur finie ... si le code était couvert de tests, je le remarquerais tout de suite lors du test. C'est bien que j'ai tout remarqué moi-même, je n'ai pas eu à jurer le client, car le testeur avait honte de rougir ... J'ai dû résoudre le problème et faire un nouveau déploiement.

appareils et matériaux:


Microsoft VisualStudio 2019
asp.net Core 3.1 (je l'ai installé avec le studio, si quelque chose peut être livré via le menu de projet d'installation d'autres frameworks)
SQL Server Express (fourni avec le studio)
Extension Git vers Visual Studio (inclus)

En règle générale, dans les tests unitaires, chaque test doit être isolé et l'état entre eux ne persiste pas. Nous obtiendrons donc des tests d'intégration, mais nous utiliserons MSTest pour cela. J'espère qu'ils ne nous emmènent pas à la police pour ça.

Dans plusieurs éditions, j'ai rencontré l'utilisation d'objets Mock pour tester la base de données.
À première vue, l'idée est bonne jusqu'à ce que des interactions complexes entre les tables commencent.
Ensuite, la configuration de la simulation prendra plus que le test lui-même. Mais en fait c'est - Control + C - Control + V! nous sommes tous des contraintes de base de données qui ont déjà été enregistrées dans EF, base de données, DataAnnotations ou FluentAPI en double dans la couche maquette. Et copier, c'est un peu comme casser un modèle ... ayah, citoyen, casser ... pas bon!

Et si la configuration fictive est compliquée, et par exemple, nous avons fait des erreurs dans les restrictions, il s'avère que - le test fictif passera, mais y aura-t-il une erreur sur la base réelle?

Tout cela m'a intéressé et j'ai décidé de tester une nouvelle approche.

L'idée est venue, comme toujours, de TRIZ: un système idéal est celui qui est absent, mais ses fonctions sont remplies . Et j'ai pensé que je devais utiliser la base de données elle-même dans les tests.
Et ça a fonctionné pour moi. Je veux partager cela, j'espère que quelqu'un vous aidera.

Inconvénients de la simulation:

  • de nombreux presets qui testent
  • les tests se salissent, beaucoup de code supplémentaire
  • des migrations difficiles à tester
  • bien se comporter que sous surveillance, sur une base réelle peut produire des erreurs inconnues
  • lorsque vous changez la structure de la base de données, vous devez constamment monter dans la maquette et tout changer là aussi

Avantages du test sur une base réelle:

  • le programme se comporte exactement comme sur un serveur de combat
  • les tests sont plus simples, vous pouvez les construire l'un après l'autre de la même manière que les données sont remplies dans la base de données
  • nous pouvons nous-mêmes ajuster la propreté de la base de données en numérotant les tests (dans MSTest ils sont effectués par ordre alphabétique)
  • vous pouvez voir le temps pendant lequel le test est effectué (sur un vrai serveur, il sera différent, mais au moins l'ordre est visible - 10 fois plus long, 2 fois, et vous pouvez déjà évaluer comment le programme fonctionne, efficacement ou non)
  • peut tester les procédures stockées

Il y a certaines difficultés dans cette approche, avec laquelle je creuse depuis plusieurs jours, mais nous allons les résoudre avec succès, et que mon backend de pierre soit avec vous!

C'est parti!

Nous créons un nouveau projet d'application Web ASP.Net Core 3.1 (Model-View-Controller), changez l'authentification en comptes d'utilisateurs individuels (stockez les comptes d'utilisateurs dans l'application) et cliquez sur créer

image

À partir de maintenant, je vais enregistrer des instantanés de projet dans git, vous pouvez le télécharger et télécharger chaque branche afin de l'expérimenter

github.com/3263927/Habr_1

Instantané: Snapshot_0_ProjectCreated

À propos du référentiel
Même lorsque je travaille seul, j'utilise toujours le référentiel - il est devenu très pratique, intégré à Visual Studio, pas de ligne de commande, tout fonctionne parfaitement directement depuis VS. Vous pouvez expérimenter et modifier tout ce que vous voulez, puis vous pouvez toujours corriger le rollback de validation ou basculer vers l'ancienne branche. Gain de temps et d'efforts, je conseille à tous. Et s'intègre à github gratuitement. Il y a quelques années, il y avait un mec qui a tout supprimé ... Donc, au cas où, je place tous les projets dans Dropbox et les mets à jour une fois par semaine, ainsi que l'archivage de tous les projets et le téléchargement manuel des dernières versions sur Google Drive. Eh bien, sur le téléphone SD, il y a 120 concerts, là aussi, en réserve, tout d'un coup, tout à coup ... Un couple de lecteurs flash avec des copies dans leurs poches sont tellement invisibles!

À ce stade, j'ai créé un référentiel. Je dois donc planifier le travail pour créer de nouvelles branches. À l'avenir, en utilisant le mot-clé Snapshot, il sera possible de trouver des points de récupération en cas de problème.

Je vais créer une nouvelle branche dans le référentiel directement à partir de VisualStudio, et je l'appellerai "Sister of Talent" pour faire court (blague, Snap_1_DataBases).

objectif: créer une connexion et une base de travail .

Nous commençons à créer nos bases.

Je dois dire tout de suite que nous aurons 3 bases de données - un test (sur la machine locale), une autre production (sur le serveur distant, fonctionnant) et une autre locale (pour vérifier la santé du site dans la configuration DEBUG).

La logique est la suivante:

  • si nous voulons exécuter le site sur la machine locale et voir comment cela fonctionne, alors Habr1_Local fonctionnera pour nous
  • si nous mettons le code en production, Habr1_Production fonctionnera
  • lorsque notre infrastructure de test commence à tester, elle doit trouver la base Habr1_Test et l'exécuter

Cependant, nous avons une contradiction - il n'y a que deux configurations, Debug et Release. C'est toujours un problème, mais alors il sera résolu.

Donc, nous créons un programme fonctionnant au minimum - pour commencer, nous vérifions simplement si au moins une base de données fonctionne pour nous. Créons-le ... avec les mains de Visual Studio lui-même!

Ouvrez le fichier appsettings.json

Il existe de telles lignes:

 "ConnectionStrings": { "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-WebApp-[- ,   ];Trusted_Connection=True;MultipleActiveResultSets=true" }, 

Là, vous devrez entrer les noms corrects des chaînes de connexion, le nom du serveur et le nom de la base de données. Je dois dire tout de suite que d'autres connexions sont utilisées en production, mais maintenant nous n'en avons plus besoin. Notre tâche consiste à créer deux bases de données (locale et test, la production n'est qu'un exemple - nous l'utiliserons dans la configuration de la version. Ensuite, elle pourra être remplacée par une base de données distante fonctionnelle).

Pourquoi est-ce nécessaire?

Les configurations de Visual Studio vous permettent de modifier certains paramètres en changeant la configuration dans le panneau de Visual Studio:

image

En général, l'environnement de développement déclare simplement une constante DEBUG, qui peut ensuite être lue de n'importe où dans le code et compris dans quelle configuration nous sommes actuellement, laquelle est active.

Le débogueur distant n'est pas toujours disponible, par exemple, sur l'hébergement de colocation. Nous pouvons exécuter notre serveur asp.net localement, et nous connecter à la base de données à distance, et nous pouvons voir toutes les erreurs qui étaient en production. Ici, dans la configuration de la version, nous le ferons, et la configuration de débogage fonctionnera avec notre base de données locale. Et nous ne ferons pas de test de configuration, pour des raisons de sécurité - afin de ne pas effacer accidentellement des données, en oubliant de changer de configuration

Donc, nous commençons à changer la chaîne de connexion.

Nom du serveur - vous pouvez le voir sur l'onglet -> Explorateur d'objets SQL Server

image

(Je vais effacer prudemment le nom de mon ordinateur, sinon vous allez me calculer par IP et taper quelque chose).

J'ai donc ceci (localdb) \ ProjectsV13. Je ne sais pas pourquoi, lors de l'installation, j'ai appelé mon SQL.
Cela signifie que notre chaîne de connexion devient

 "DefaultConnection": "Server=(localdb)\\ProjectsV13;Database=Habr1_Local; Trusted_Connection=True;MultipleActiveResultSets=true" 

Vous pouvez l'avoir différemment, mais uniquement ProjectV13. Le reste devrait être laissé comme ça.
Remplacez DefaultConnection par Habr1_Local

Il se présente comme ceci:

  "ConnectionStrings": { "Habr1_Local": "Server=(localdb)\\ProjectsV13;Database=Habr1_Local;Trusted_Connection=True;MultipleActiveResultSets=true" }, 

Maintenant, vous devez aller dans le fichier Startup.cs et y remplacer DefaultConnection avec Habr1_Local:

 services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); 

se transforme en

 services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Habr1_Local"))); 

Nous commençons notre projet dans la configuration de débogage, le navigateur s'ouvre, nous voyons la première page, cliquez sur le bouton Connexion, entrez n'importe quel e-mail valide (il ne vous enverra pas de lettres, il valide simplement le format) et cliquez sur Connexion - et nous voyons cet écran:

image

Cliquez sur Appliquer la migration, nous attendons lorsque la confirmation apparaît à droite du bouton bleu que la migration a réussi et que vous devez actualiser la page, mettre à jour, cliquer sur confirmer pour renvoyer les données et obtenir un écran indiquant Échec de la connexion:

image

Si un tel écran est visible, alors tout est OK - une fois que la demande a passé et renvoyé une tentative de connexion invalide, il n'y a pas eu d'erreur de base de données, mais il n'a tout simplement pas trouvé un tel utilisateur.

Un peu sur la migration:
C'est un outil complexe et il n'est guère nécessaire de le toucher dans cet article, mais vous pouvez le toucher un peu.
Par exemple, vous devez changer l'état de la base de données en un état spécifique - vous pouvez créer un instantané de base de données pour cela, ou créer plusieurs instantanés pour chaque état, et activer / désactiver ces images à la fois par programme et avec des commandes spéciales.

Ou lorsque vous développez quelque chose sur la machine locale, puis vous devez synchroniser le nouvel état de la base de données sur le serveur avec le local, mettre à jour le serveur de production à l'état de votre base de données locale et le faire automatiquement - la migration peut également être appliquée. Il s'agit en fait de ce bouton bleu. La base sait que son état est différent de l'état du code et essaie de synchroniser ces états. Pour ce faire, une table spéciale est créée dans la base de données avec l'état codé de la structure de la base de données.

Malheureusement, cet outil est compliqué par le fait qu'il n'y a pas d'interface visuelle pour lui, et vous devez travailler avec lui à partir de la ligne de commande. Les migrations sont pratiques à utiliser lorsque vous devez passer des états via Git - simplement en créant de tels instantanés de base de données sous forme de fichiers C #. Mais cet outil présente un danger: s'il est mal configuré, il peut effacer les données de la base de données, vous devez donc l'utiliser avec prudence.

Vérification de la disponibilité de la base de données - Visual Studio aurait dû la créer

image

S'il n'y a pas de base de données, alors quelque chose s'est mal passé - soit le serveur SQL n'a pas été installé, soit autre chose, en général, comme dans une blague sur la façon dont les programmeurs étaient médecins: «docteur, ma jambe me fait mal ... - enfin, pas Je sais, j'ai la même jambe et rien ne fait mal! »

À ce stade, je crée deux chaînes de connexion supplémentaires, appsettings.json prend cette forme:

 "ConnectionStrings": { "Habr1_Local": "Server=(localdb)\\ProjectsV13;Database=Habr1_Local;Trusted_Connection=True;MultipleActiveResultSets=true", "Habr1_Test": "Server=(localdb)\\ProjectsV13;Database=Habr1_Test;Trusted_Connection=True;MultipleActiveResultSets=true", "Habr1_Production": "Server=(localdb)\\ProjectsV13;Database=Habr1_Production;Trusted_Connection=True;MultipleActiveResultSets=true" }, 

Je fais un commit et je place l'instantané suivant dans le dépôt:

Instantané: Snap_1_DataBases

Créez une nouvelle branche, Snap_2_Configurations

objectif: créer des configurations de travail

Lors du changement de configuration, nous pouvons considérer de n'importe où dans le programme quelle configuration est actuelle (en fait, pas depuis n'importe laquelle, cela ne fonctionnera pas depuis View - nous devons créer une fonction spéciale, mais ce n'est pas important pour ce projet):

 #if DEBUG  DEBUG #else  RELEASE ( DEBUG   ) #endif 

Ouvrez le fichier Startup.cs et convertissez la méthode ConfigureServices en ceci:

 public void ConfigureServices(IServiceCollection services) { String ConnStr = ""; #if DEBUG ConnStr = "Habr1_Local"; #else ConnStr = "Habr1_Production"; #endif services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer( Configuration.GetConnectionString(ConnStr))); services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true) .AddEntityFrameworkStores<ApplicationDbContext>(); services.AddControllersWithViews(); services.AddRazorPages(); } 

Comme vous pouvez le voir, nous avons remplacé Habr1_Local par une variable, et maintenant selon la configuration, connectionstring sera soit Habr1_Local ou Habr1_Production

Vous pouvez maintenant démarrer le projet et vérifier comment les bases de données sont créées en fonction de la configuration

Nous sélectionnons DEBUG sur le panneau, démarrons, connectez-vous, appliquez la migration, vérifiez que la base de données a été créée (Habr1_Local)

Nous arrêtons le projet, sélectionnons la configuration Release, démarrons, connectons-nous, appliquons la migration, vérifions que la base de données a été créée - nous avons 2 bases.

C'est fait!

Instantané: Snap_3_HabrDB

Objectif: créer un projet de base de données distinct, qui peut ensuite être utilisé dans différents projets

Pourquoi un projet séparé?

Les projets individuels présentent plusieurs avantages:

  • Ils peuvent être utilisés dans d'autres projets.
  • Ils ne recompilent pas s'il n'y a aucun changement, ce qui signifie que le temps de compilation total est réduit
  • Les projets individuels sont plus faciles à tester.

Donc, le bouton droit de la solution - ajouter -> nouveau dossier de solution, nommez-le DB.
Ensuite, accédez directement au dossier créé - ajoutez un nouveau projet -> .net standard, nom HabrDB.
Pour une raison quelconque, je l'ai créé en tant que .net standard 2.0, je dois le changer en 2.1
(lors de la création, il offre un chemin physique, laissez-le se trouver dans le dossier DB et physiquement aussi).

Cela ressemble à ceci pour moi:

image

Donc, nous avons un ApplicationDBContext dans le projet, et nous en avons créé un autre? Vont-ils entrer en conflit les uns avec les autres? Nous allons maintenant nous lier d'amitié avec eux. Nous aurons deux contextes différents pour la même base de données, qui ne se recouperont pas via Entity Framework. Nous leur donnerons un nom de schéma différent: l'un restera dbo par défaut, et l'autre sera «habr».

Si vous connectez de tels contextes à la racine de la composition, ils peuvent être recyclés dans d'autres projets de manière presque transparente. (Par exemple, le contexte de l'entrepôt et le contexte de l'employé)

Et encore un moment architectural, parfois vous devez ajouter certaines de vos propriétés à l'utilisateur, cela ne s'applique pas directement au sujet de l'article, mais nous le ferons juste pour savoir comment le faire. De plus, nous pourrons créer et supprimer des contextes indépendamment les uns des autres. Une bonne idée est de séparer la table de sécurité des données personnelles et de la crypter au niveau de la base de données (nous ne le ferons pas dans ce projet, mais en général, cela est parfois requis, y compris par la loi).

Oui, et il est plus facile de tester de cette façon, vous ne pouvez pas créer toutes les tables à la fois, mais uniquement celles qui sont nécessaires pour tester le contexte donné.

Je crée un nouveau cliché de projet - étape 4.

Les objectifs de cette phase sont:

  • changer l'utilisateur standard en avancé
  • changer d'utilisateur dans le fichier Srartup.cs
  • changer d'utilisateur dans LoginPartial et ViewImports
  • créer une nouvelle migration pour créer automatiquement une base de données dans un nouveau format

Ainsi, nous transférons la classe ApplicationDBContext du projet WebApp vers HabrDB.

Ce n'est pas portable, juste copié. Nous le supprimons de WebApp, l'ouvrons du projet HabrDB et changeons son espace de noms en HabrDB, beaucoup d'erreurs apparaissent.

Oui, dans ce projet, il n'y a pas de packages nécessaires, maintenant nous les livrerons.

Grâce à nuget, dans le projet HabrDB, vous devez installer Microsoft.AspNetCore.Identity.EntityFrameworkCore.

image

On clique sur l'ampoule et elle nous propose d'installer les dernières versions.

Le fichier final SecurityDBContext (il doit également être renommé) prend cette forme:

 using System; using System.Collections.Generic; using System.Text; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; namespace HabrDB { public class SecurityDBContext : IdentityDbContext { public SecurityDBContext(DbContextOptions<SecurityDBContext> options) : base(options) { } } } 

Après l'assemblage, nous traitons soigneusement le fichier avec des erreurs, et à juste titre - nous avons supprimé ApplicationDBContext et l'avons remplacé par SecurityDBContext. Vous devez maintenant remplacer tous les liens vers ApplicationDBContext dans l'ensemble du projet par SecurityDBContext.

Après cela, des triangles jaunes sont apparus dans mon projet sur les références de WebApp, ce qui indique que certains liens ne fonctionnent pas correctement. J'ai nettoyé le projet (build -> solution propre), fermé le projet, supprimé tous les répertoires Debug, Release, Obj et Bin du dossier du projet, après quoi j'ai rouvert le projet, pendant un certain temps j'ai cherché les liens nécessaires sur Internet et les ai chargés, ainsi que des triangles disparu - alors tout va bien.

Supprimez maintenant le dossier Data du projet WebApp, supprimez nos bases de données (Habr1_Local et Habr1_Production) dans la fenêtre SQL Server Object Explorer et exécutez le projet. Nous essayons de nous connecter - et maintenant, au lieu de l'offre d'appliquer la migration, une erreur se produit.

image

C'est vrai, nous avons supprimé le dossier Data où se trouvaient toutes les migrations et maintenant le framework ne sait plus quoi faire. Mais c'était tellement cool?! Pourquoi?! Ensuite, ce que nous allons maintenant développer la classe d'utilisateurs.

Ajoutez un nouveau fichier au projet HabrDB:
ApplicationUser.cs

Nous héritons de IdentityUser

 using Microsoft.AspNetCore.Identity; using System; using System.Collections.Generic; using System.Text; namespace HabrDB { public class ApplicationUser:IdentityUser { public String NickName { get; set; } public DateTime BirthDate { get; set; } public String PassportNumber { get; set; } } } 

Dans le fichier SecurityDBContext de l'en-tête de classe, ajoutez:

 public class SecurityDBContext : IdentityDbContext<ApplicationUser> 

Cela est nécessaire pour qu'EntityFramework sache que maintenant, lors de la création de la base de données, vous devez utiliser le modèle utilisateur avancé de la classe AppllicationUser au lieu du modèle standard.

La méthode ConfigureServices du fichier Startup.cs prend la forme:

 public void ConfigureServices(IServiceCollection services) { String ConnStr = ""; #if DEBUG ConnStr = "Habr1_Local"; #else ConnStr = "Habr1_Production"; #endif services.AddDbContext<SecurityDBContext>(options => options.UseSqlServer( Configuration.GetConnectionString(ConnStr))); services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true) .AddEntityFrameworkStores<SecurityDBContext>(); services.AddControllersWithViews(); services.AddRazorPages(); } 

(Remplacez IdentityUser par ApplicationUser)

Dans le fichier _ViewImports.cshtml, ajoutez la ligne

en utilisant HabrDB

Maintenant, toutes les vues verront notre projet avec la base et vous n'aurez pas besoin d'écrire en utilisant HabrDB au début des vues.

Dans le fichier _LoginPartial.cshtml, remplacez tous IdentityUser par ApplicationUser.

à l'aide de Microsoft.AspNetCore.Identity
injecter SignInManager SignInManager
injecter UserManager UserManager

Juste au cas où, nous compilons le projet afin de savoir que nous n'avons aucune erreur et que nous n'avons oublié de remplacer quoi que ce soit.

Faites maintenant la migration.

Vous devez ouvrir la console du gestionnaire de packages, sélectionner le projet DB / HabrDB et y écrire

 add-migration initial 

Voici ce que j'ai obtenu:

image

Le papa Migrations est apparu dans le projet HabrDB et il contient des fichiers qui nous permettront de créer notre base de données automatiquement, mais maintenant avec nos champs supplémentaires - NickName, BirthDate, PassportNumber.

Essayons comment cela fonctionne - exécutons et essayons de nous connecter (ne vous enregistrez pas, vous devrez y saisir des mots de passe complexes):

image

On m'a proposé de faire une migration et j'ai accepté - c'est notre base:

image

Avec cette étape, tout

Instantané: Snap_4_Security est prêt

Créez le cinquième plan.

Objectifs:

  • faire fonctionner la chaîne de connexion de test
  • créer un projet de test de base de données
  • faire le projet de test créer une base et tester quelque chose d'utile

Nous faisons un clic droit sur le papa DB, créons un nouveau projet - MSTest .net core.
Renommez le seul fichier de ce projet en DBTest et pensez ...
De plus, ce sera difficile.

Le problème

Comment pouvons-nous créer un contexte qui est garanti pour communiquer avec la base de données de test, c'est-à-dire utiliser ConnectionString non seulement à partir d'un autre projet (WebApp), mais aussi se connecter d'une manière ou d'une autre avec les configurations Release / Debug? .. Peut créer une nouvelle configuration, Test par exemple ?

Non, c'est une perte de données potentielle - nous oublions en quelque sorte que nous avons oublié de passer de la configuration de test, cliquez sur le bouton Exécuter tous les tests - et là, la suppression de la base de données entière ... non, cette option ne fonctionne pas ...

Nous allons donc clairement créer un contexte de test!

Ouvrez le fichier HabrDBContext et changez son contenu en:

 using System; using System.IO; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; namespace HabrDB { public class HabrDBContext:DbContext { public String ConnectionString = ""; public IConfigurationRoot Configuration { get; set; } public HabrDBContext CreateTestContext() { DirectoryInfo info = new DirectoryInfo(Directory.GetCurrentDirectory()); DirectoryInfo temp = info.Parent.Parent.Parent.Parent; String CurDir = Path.Combine(temp.ToString(), "WebApp"); String ConnStr = "Habr1_Test"; Configuration = new ConfigurationBuilder().SetBasePath(CurDir).AddJsonFile("appsettings.json").Build(); var builder = new DbContextOptionsBuilder<HabrDBContext>(); var connectionString = Configuration.GetConnectionString(ConnStr); builder.UseSqlServer(connectionString); ConnectionString = connectionString; return this; } } } 

Via Nuget, ajoutez les bibliothèques suivantes à la base de données:

 Microsoft.AspNetCore.Identity.EntityFrameworkCore Microsoft.EntityFrameworkCore Microsoft.EntityFrameworkCore.SqlServer Microsoft.Extensions.Configuration.FileExtensions Microsoft.Extensions.Configuration.Json 

La méthode CreateTestContext ne peut tout simplement renvoyer qu'une chaîne de connexion nommée Habr1_Test. Vous devez le prendre dans un autre projet. Afin de ne pas connecter le projet par référence car nous n'avons besoin que d'un seul paramètre, nous demandons au constructeur de créer des options basées sur la chaîne de connexion donnée, pour cela, nous parcourons la chaîne de répertoires du répertoire où le projet de test est compilé jusqu'au répertoire racine du projet, collectons le chemin d'accès au projet WebApp à partir des pièces, veuillez ajouter pour nous le fichier de configuration appsettings.json où nos paramètres sont stockés, et compilez-le dans la configuration. Plus loin de cette configuration, nous prenons connectionString et nous nous en souvenons (nous en aurons besoin plus tard).

Dans les grands projets, vous pouvez déplacer les paramètres vers un projet distinct avec des chaînes, une DLL ou un objet pour accéder aux données de paramètres avec la mise en cache. Pour l'instant, une approche plus simple suffira.

Pourquoi

Vous pouvez mettre un ConnectionString de test directement dans le projet de test et initialiser la base de données avec lui, vous pouvez créer un ConnectionString de test dans la base de données. Mais alors il y aura une chose désagréable: toutes les chaînes de connexion seront stockées à différents endroits. Et par moi-même, je sais que ma tête est pleine de trous et je peux oublier de changer quelque chose lorsque, par exemple, le nom de la base de données ou du serveur change, et que j'ai toutes les chaînes de connexion en un seul endroit, je le fais. Maintenant, en modifiant le fichier de configuration appsettings.json, vous pouvez gérer toutes les connexions.

Essayons maintenant de faire quelque chose d'utile avec la base, par exemple, de créer quelque chose.

Dans le projet HabrDB, j'ai créé le dossier DBClasses, il n'y aura que des classes de table de base de données. Nous travaillons d'abord sur le code, si je peux imaginer ce que je fais, c'est généralement plus pratique pour moi.

Créez un tableau comme celui-ci:

 using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text; namespace HabrDB.DBClasses { [Table("Phones", Schema ="Habr")] public class Phone { [Key] public int Id { get; set; } public String Model { get; set; } public DateTime DayZero { get; set; } } } 

Pour la beauté, que ma classe soit appelée «téléphone» et la table de la base de données au pluriel - «téléphones». Par conséquent, j'indique dans l'attribut Table comment nommer ma table et dans quel espace de noms elle doit se trouver dans la base de données (en SQL, cela s'appelle Schéma).

Maintenant, il y a une autre question d'esthétique: ici, nous aurons un tas de méthodes d'infrastructure différentes dans la classe de base de données, puis d'autres tables différentes et tout cela dans un tas - vous pouvez créer des classes partielles.

Ajoutez le mot partiel après la classe de mots dans le fichier HabrDBContext.cs - comme ceci:

 public partial class HabrDBContext:DbContext 

Créez une copie du fichier HabrDBContext.cs - simplement CTRL + C - CTRL + V sur le fichier, créez-en une copie, changez le nom du fichier source en HabrDBContext_Infrastructure.cs et le nouveau en HabrDBContext_Data.cs

Dans le nouveau, écrivez:

 using HabrDB.DBClasses; using Microsoft.EntityFrameworkCore; namespace HabrDB { public partial class HabrDBContext:DbContext { public DbSet<Phone> Phones { get; set; } } } 

Maintenant, c'est beau - nous travaillons avec des données à un endroit, avec des infrastructures à un autre. Les fichiers sont différents, mais la classe est la même - l'environnement lui-même les assemblera de plusieurs en un lors de la construction du projet.

Eh bien, essayez-le!

Remplacez le code dans notre seule classe de test par:

 using HabrDB; using HabrDB.DBClasses; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; using System.Linq; namespace DBTest { [TestClass] public class DBTest { [TestMethod] public void TestMethod1() { String PhoneName = "Nokia"; DateTime now = DateTime.Now; HabrDBContext db = new HabrDBContext().CreateTestContext(); db.Database.EnsureCreated(); List<Phone> Phones = db.Phones.ToList(); Assert.AreEqual(0, Phones.Count); Phone ph = new Phone(); ph.Model = PhoneName; ph.DayZero = now; db.Phones.Add(ph); db.SaveChanges(); Phone ph1 = db.Phones.Single(); Assert.AreEqual(PhoneName, ph1.Model); Assert.AreEqual(now, ph1.DayZero); } } } 

Cliquez sur le bouton de lecture dans l'onglet Explorateur de tests (ou Test -> Exécuter tous les tests) et ...

image

Erreur! voici ceux dessus.

Nous lisons ce qu'ils nous écrivent:

 Message: Test method DBTest.DBTest.TestMethod1 threw exception: System.InvalidOperationException: No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext. 

Une sorte de merde en anglais ...
D'accord, nous allons agir au hasard!

Cette fonction peut-elle être copiée dans le fichier HabrDBContext_Infrastructure.cs? Essayons!

 protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { String ConnStr = ""; if (Configuration == null) { #if DEBUG ConnStr = "Habr1_Local"; #else ConnStr= "Habr1_Production"; #endif Configuration = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json").Build(); ConnectionString = Configuration.GetConnectionString(ConnStr); } optionsBuilder.UseSqlServer(ConnectionString); } 

Nous commençons ...

C'était de la chance !!!
Une nouvelle base a été créée et en elle - notre table!

image

Pourquoi

Si vous placez quelques points d'arrêt sur les fonctions OnConfiguring et CreateTestContext, vous verrez que la méthode CreateTestContext est appelée en premier et enregistre la chaîne de connexion dans l'objet ConnectionString. Tout semblerait OK. Mais alors quelqu'un essaie d'appeler OnConfiguring ... qui est-ce? Voyons la pile des appels - oui, c'est la ligne db.Database.EnsureCreated () du test! le fait est que nous n'avons pas encore de base en tant que telle - sa méthode EnsureCreated la crée. Mais cette méthode ne prend plus de paramètres et le contexte doit en quelque sorte être préservé entre les appels au constructeur et EnsureCreated. De plus, lorsque nous utilisons ce contexte à partir du projet lui-même (pas dans les tests, mais sur le site Web par exemple), toutes sortes de middlware, DI et autres mécanismes accrocheurs essaieront également de l'appeler, nous allons donc tout prévoir à l'avance - celui qui appelle notre base de données ,s'il veut appeler OnConfiguring - il aura une telle opportunité. Nous avons tout prévu.

Relancez le test - et ...
Encore une fois, une erreur?
La base de données est déjà là, les données sont ... qu'est-ce qui ne va pas?
Une base est un objet persistant qui reste, même si nous redémarrons notre projet ... et maintenant la partie vraiment difficile commence. Comment supprimer tous ces tableaux? Comment supprimer des données, réinitialiser tous les index?

Snapshot: Snap_5_ContextCrafting est prêt.

Tout d'abord, écrivez DAL - Data Access Layer.

Créez une copie du fichier HabrDBContext_Data.cs, ​​appelez-le HabrDBContext_DAL.cs
et écrivez-le:

 using HabrDB.DBClasses; using Microsoft.EntityFrameworkCore; using System.Collections.Generic; using System.Threading.Tasks; namespace HabrDB { public partial class HabrDBContext:DbContext { public async Task<int> AddPhone(Phone ph) { this.Phones.Add(ph); int res = await this.SaveChangesAsync(); return res; } public async Task<List<Phone>> GetAllPhones() { List<Phone> phones = await this.Phones.ToListAsync(); return phones; } } } 

Il s'agit d'un wrapper sur nos données. Je ne voudrais pas, si je change quelque chose dans les interfaces pour accéder à la base de données, gérer la recherche tout au long du projet où je l'ai appelée. Par conséquent, nous allons créer une couche d'abstraction - Data Access Layer. Il sera l'intermédiaire entre les mécanismes de base et MVC du site ou d'autres mécanismes. Et - une coïncidence? - nous avons immédiatement des objets de test! Une autre raison d'utiliser des tests est qu'ils provoquent des solutions à faible connectivité.
Changez le code de fonction dans les tests - et pour un, changez son nom. Maintenant, nous savons ce que nous testons!
Ajout d'un téléphone!

Nous ne ferons pas le référentiel, pour un projet de démonstration c'est une solution redondante.
Normalement, vous pouvez tout connecter via DI dans startup.cs, mais pour le projet de démonstration, cela est trop déroutant, alors laissons-le comme ça

Nouveau code de fonction de test:

 [TestMethod] public void AddPhone_Test() { String PhoneName = "Nokia"; DateTime now = DateTime.Now; HabrDBContext db = new HabrDBContext().CreateTestContext(); db.Database.EnsureCreated(); List<Phone> Phones = db.GetAllPhones().Result; Assert.AreEqual(0, Phones.Count); Phone ph = new Phone(); ph.Model = PhoneName; ph.DayZero = now; db.AddPhone(ph); Phone ph1 = db.Phones.Single(); Assert.AreEqual(PhoneName, ph1.Model); Assert.AreEqual(now, ph1.DayZero); } 

Nous allons supprimer la ligne avec le téléphone de la base de données de test et relancer le test - cela devrait fonctionner.
Si cela ne fonctionne pas, vous avez toujours une jambe différente. Tout est vert avec moi:

image

qu'est-ce que db.GetAllPhones (). Résultat? Le fait est que nos fonctions DAL sont asynchrones. Mais la méthode de test elle-même est ordinaire, et donc attendre ne peut pas y être appelé. Essayons de supprimer les données, de rendre la méthode asynchrone et de voir ce qui se passe.

Notre fonction est devenue une tâche asynchrone - sinon le test ne démarre tout simplement pas, et partout où il y a un appel à des méthodes asynchrones - vous devez attendre avant elles

 [TestMethod] public async Task AddPhone_Test() { String PhoneName = "Nokia"; DateTime now = DateTime.Now; HabrDBContext db = new HabrDBContext().CreateTestContext(); db.Database.EnsureCreated(); List<Phone> Phones = await db.GetAllPhones(); Assert.AreEqual(0, Phones.Count); Phone ph = new Phone(); ph.Model = PhoneName; ph.DayZero = now; await db.AddPhone(ph); Phone ph1 = db.Phones.Single(); Assert.AreEqual(PhoneName, ph1.Model); Assert.AreEqual(now, ph1.DayZero); } 

Il est important que toutes les fonctions de test dans lesquelles il y a des appels asynchrones renvoient Task et non nulles - sinon les tests ne démarreront pas ou n'attendront pas le retour des données asynchrones et continueront et il y aura une erreur.

Donc, ça marche plus ou moins.

Snapshot: Snap_6_Dal

Et quoi, est-il nécessaire de supprimer les données manuellement? Bien sûr que non!

Nous avons besoin d'une fonction pour supprimer des tables de la base de données ...

Ce serait cool d'utiliser db.Database.EnsureDeleted ... et c'est vraiment possible!

Mais mieux n'est pas nécessaire ... le fait est que dans ce projet nos bases ne sont pas connectées avec des mots de passe. Et si la base de données est associée à un mot de passe, vous devrez le créer séparément - via SQL Management Studio, et lorsque vous le supprimerez de db.Database.EnsureDeleted, il sera supprimé avec tous les mots de passe, accès, droits d'utilisateur et lorsque le framework essaiera de le créer la prochaine fois, alors il n'y aura tout simplement pas accès à la base de données, vous devrez tout configurer à nouveau.

Ceci est le premier.

La seconde est qu'il vaut mieux ne pas faire trop de travail, et donc les tests de base de données prendront plus de temps que les fonctions habituelles qui ne sont pas liées à des mécanismes externes, et il est préférable d'optimiser le temps d'exécution des tests de toutes les manières possibles.

Et le troisième: peut-être dans un test, nous devrons supprimer une table ou plusieurs fois et la recréer, tout en laissant les autres tables intactes.

Essayons d'appeler la fonction db.Database.EnsureDeleted, appuyez sur le crochet et voyons ce qu'il accepte, et s'il a des surcharges ...

image

Oui, pas beaucoup ...
eh bien, ok, écrivons la nôtre.

Ajoutez immédiatement un nouveau projet à la solution (dans la solution elle-même) sur la solution avec le bouton droit, ajoutez -> .net standard C # et appelez-le Extensions. Vérifiez qu'il s'agissait de la version 2.1.

Ensuite, vous devez renommer le seul fichier du projet Extensions en DBContextExtensions.cs et y mettre le code suivant:

 using Microsoft.EntityFrameworkCore; using System; using System.Linq; using Microsoft.EntityFrameworkCore.Infrastructure; namespace Extensions { public static class DBContextExtensions { public static int EnsureDeleted<TEntity>(this DatabaseFacade db, DbSet<TEntity> set) where TEntity : class { TableDescription Table = GetTableName(set); int res = 0; try { res = db.ExecuteSqlRaw($"DROP TABLE [{Table.Schema}].[{Table.TableName}];"); } catch (Exception) { } return res; } public static TableDescription GetTableName<T>(this DbSet<T> dbSet) where T : class { var dbContext = dbSet.GetDbContext(); var model = dbContext.Model; var entityTypes = model.GetEntityTypes(); var entityType = entityTypes.First(t => t.ClrType == typeof(T)); var tableNameAnnotation = entityType.GetAnnotation("Relational:TableName"); var tableSchemaAnnotation = entityType.GetAnnotation("Relational:Schema"); var tableName = tableNameAnnotation.Value.ToString(); var schemaName = tableSchemaAnnotation.Value.ToString(); return new TableDescription { Schema = schemaName, TableName = tableName }; } public static DbContext GetDbContext<T>(this DbSet<T> dbSet) where T : class { var infrastructure = dbSet as IInfrastructure<IServiceProvider>; var serviceProvider = infrastructure.Instance; var currentDbContext = serviceProvider.GetService(typeof(ICurrentDbContext)) as ICurrentDbContext; return currentDbContext.Context; } } public class TableDescription { public String Schema { get; set; } public String TableName { get; set; } } } 

Ajoutez Microsoft.EntityFrameworkCore à partir de Nuget.
Et un autre package, Microsoft.EntityFrameworkCore.Relational

Extensions, est un mécanisme très pratique. En l'utilisant, vous pouvez ajouter des fonctionnalités supplémentaires à l'objet de classe, ce que nous avons fait - le mot-clé this avant le type du premier paramètre

 public static int EnsureDeleted<TEntity>(this DatabaseFacade db, DbSet<TEntity> set) where TEntity : class 

indique un type extensible. Cela signifie qu'après avoir écrit cela, l'objet DatabaseFacade aura une nouvelle méthode - EnsureDeleted avec un paramètre, qui sera notre fonction que nous venons d'écrire! où TEntity: classe à la fin signifie que TEntity a une contrainte - une restriction d'utilisation, et si nous essayons de la généraliser non pas par une classe, mais par autre chose, alors il y aura une erreur de compilation.

Je prévois votre question logique - pourquoi est-ce?

Ensuite, la fonction GetTableName qui est appelée à partir d'EnsureDeleted a besoin de cette restriction.
Pourquoi a-t-elle besoin de cette restriction?

Alors, quelle fonction GetDbContext, qui est appelée à partir de GetTableName, a également besoin de cette restriction ...

Et pourquoi a-t-elle besoin de cette restriction ??? !!! vous demandez dans des tons élevés et vous aurez raison ...

Le dbSet que nous essayons d'étendre avec la méthode GetDbContext dans la ligne

 Public static DbContext GetDbContext<T>(this DbSet<T> dbSet) where T : class 

, nécessite que T soit un type de référence, et la classe n'est que l'un d'entre eux ...

Ainsi, toutes ces difficultés - pour une petite mais très utile fonctionnalité - ne transmettent pas de valeurs de chaîne en tant que paramètre à la méthode EnsureDeleted. Parce que j'ai une mauvaise mémoire. Je veux que DBContext me dise quelles tables il a, et à partir de cet ensemble de données, il calcule déjà le contexte, l'amène au contexte actuel via le fournisseur de services, puis il prend le modèle de ce contexte, en saisit des types, puis il recherche le type avec ces types le même que l'ensemble de données (c'est déjà dans GetTableName), puis à travers des annotations, il prend le nom de la table et du schéma, le transmet déjà à EnsureDeleted et là - bon sang! encore une fois, il n'y a pas de fonction comme removetable ou quelque chose comme ça, vous devez construire du syrniki SQL à partir de chaînes ... enfin, au moins, ça a fonctionné!

Et l'exception se trouve dans la fonction EnsureDeleted afin que vous ne puissiez pas penser à la procédure de suppression des tables, s'il y a des données connexes, juste après la suppression des tables liées, essayez de supprimer à nouveau la table et ne vous embêtez pas.

Ajouter la méthode aux tests (à la fin)

 [TestMethod] public void DeleteTable_Test() { HabrDBContext db = new HabrDBContext().CreateTestContext(); db.Database.EnsureDeleted(db.Phones); } 

Courez, vérifiez, expirez ...
Courez à nouveau - devrait fonctionner.

Désormais, chaque fois que vous exécutez des tests, les tables nécessaires sont créées, puis supprimées.

Instantané: Snap_7_Extensions

à de nouveaux horizons hémorroïdes

Mais il y a un problème - l'objet HabrDBContext n'est pas persistant avec nous (il est créé dans chaque méthode de test). (Eh bien, oui, l'idée Kagbe des tests unitaires doit être isolée. Et nous essayons de la casser! C'est bien que la police ne voie pas ...)

Autrement dit, dans chaque méthode de test, le contexte est recréé, et nous ne pouvons pas partager cet objet entre les fonctions afin qu'il soit commun à tous et créé une fois pour toutes les épreuves. N'est-ce pas? Eh bien, nous allons écrire dans chaque fonction.

 HabrDBContext db = new HabrDBContext().CreateTestContext(); db.Database.EnsureCreated(); 

et appeler la dernière méthode

 HabrDBContext db = new HabrDBContext().CreateTestContext(); db.Database.EnsureDeleted(db.Phones); 

et quelques autres tableaux ...

Ce n'est pas très beau, alors nous allons réfléchir à la façon de résoudre ce problème ... puisque nous nous habillons comme des testeurs, nous devons faire correspondre l'image - nous chercherons une solution!

Les classes ont une fonctionnalité intéressante: les membres statiques.

C'est comme si vous imaginez un dessin et un objet créé à partir de ce dessin, il s'avère qu'un membre statique est un membre qui n'est pas inhérent à l'objet créé à partir du dessin, mais à ce dessin lui-même.

C'est déjà intéressant ...
Essayons maintenant!

Notre classe de test s'appelle DBTest, créez un objet statique pour cela - db

 using HabrDB; using HabrDB.DBClasses; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Extensions; namespace DBTest { [TestClass] public class DBTest { public static HabrDBContext db; [TestMethod] public void AA0_init() { db = new HabrDBContext().CreateTestContext(); db.Database.EnsureCreated(); } [TestMethod] public async Task AddPhone_Test() { String PhoneName = "Nokia"; DateTime now = DateTime.Now; List<Phone> Phones = await db.GetAllPhones(); Assert.AreEqual(0, Phones.Count); Phone ph = new Phone(); ph.Model = PhoneName; ph.DayZero = now; await db.AddPhone(ph); Phone ph1 = db.Phones.Single(); Assert.AreEqual(PhoneName, ph1.Model); Assert.AreEqual(now, ph1.DayZero); } [TestMethod] public void DeleteTable_Test() { db.Database.EnsureDeleted(db.Phones); } } } 

Ça marche!

Mais quelque chose est stupide de numéroter des méthodes comme ça.
Il y a une solution!
Attributs de test spéciaux!
Dans le projet de test, créez une nouvelle classe appelée DBTestBase et héritez de notre classe DBTest.
De DBTest, vous devez tout supprimer sauf:

 using HabrDB; using HabrDB.DBClasses; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Extensions; namespace DBTest { [TestClass] public class DBTest:DBTestBase { [TestMethod] public async Task AddPhone_Test() { String PhoneName = "Nokia"; DateTime now = DateTime.Now; List<Phone> Phones = await db.GetAllPhones(); Assert.AreEqual(0, Phones.Count); Phone ph = new Phone(); ph.Model = PhoneName; ph.DayZero = now; await db.AddPhone(ph); Phone ph1 = db.Phones.Single(); Assert.AreEqual(PhoneName, ph1.Model); Assert.AreEqual(now, ph1.DayZero); } [ClassCleanup] public static void DeleteTable() { db.Database.EnsureDeleted(db.Phones); } } } 

Le contenu de la classe DBTestBase:

 using HabrDB; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace DBTest { [TestClass] public class DBTestBase { public static HabrDBContext db{ get; set; } /// <summary> /// Executes once before the test run. (Optional) /// </summary> /// <param name="context"></param> [AssemblyInitialize] public static void AssemblyInit(TestContext context) { db = new HabrDBContext().CreateTestContext(); db.Database.EnsureCreated(); } /// <summary> /// Executes before this class creation /// </summary> /// <param name="context"></param> [ClassInitialize] public static void TestFixtureSetup(TestContext context) { } /// <summary> /// Executes Before each test /// </summary> [TestInitialize] public void Setup() { } /// <summary> /// Executes once after the test run /// </summary> [AssemblyCleanup] public static void AssemblyCleanup() { } /// <summary> /// Runs once after all tests in this class are executed. /// Not guaranteed that it executes instantly after all tests from the class. /// </summary> [ClassCleanup] public static void TestFixtureTearDown() { } /// <summary> /// Executes after each test /// </summary> [TestCleanup] public void TearDown() { //db.Database.EnsureDeleted();//don`t call! delete database instead of tables! } } } 

Maintenant, tout est clair! Notre base de données est créée en tant que partie statique de la classe DBTestBase, et est donc accessible à toutes les classes héritées d'elle. Cela signifie que la base de données n'est créée qu'une seule fois - lors du démarrage des tests.

Ajoutez l'attribut [ClassCleanup] à n'importe quelle méthode de n'importe quelle classe - et obtenez un test "plus propre" - une telle fonction qui fera quelque chose une fois tous les tests de cette classe terminés.

Par exemple, il supprimera toutes les tables créées dans ce test sans toucher à la base de données elle-même.

Étant donné que toutes ces fonctions spéciales sont exclues des métriques, nous pouvons voir le temps de nettoyage pour lequel nos fonctions fonctionnent - nous ajoutons quelques objets à gauche et nous constatons une forte augmentation du temps de travail de la fonction de sélection - cela signifie que quelque chose ne va pas avec nos fonctions DAL, et si vous testez une fonction dans une méthode de test, il sera immédiatement clair où se trouve le problème.

Vous pouvez également créer des listes de lecture à partir de fonctions.

Cela est nécessaire pour, par exemple, appeler la fonction de préparation des données (uniquement à des fins de test).

Ensuite, la fonction DAL met à jour ces données, puis une autre fonction DAL, qui, par exemple, réfléchit ou affiche un rapport à partir de ces données, puis supprime ces données.

Ainsi, nous pouvons imiter certaines actions des utilisateurs à partir de processus métier réels.

La principale chose à retenir est qu'en dehors de ces attributs de test, les tests sont appelés par ordre alphabétique.

Par conséquent, si vous avez besoin d'une sorte de séquence statique, vous devez l'appeler ainsi:

T1_AddPhone_Test,
T2_RemovePhone_Test,
etc. ...

Eh bien, maintenant, vous ne pouvez pas vous soucier de notre base de données - tout sera testé dans son intégralité!
Et maintenant il est temps de dormir! J'y ai encore une fille non testée ...

Test réussi! Salut tout le monde!

projet de référentiel git: https://github.com/3263927/Habr_1

Écrivez dans les commentaires si ce sujet est intéressant, je rédigerai un article sur le test d'identité, les rôles, les revendications, l'authentification 3D et l'écriture de mon TypeFilterAttribute (car le standard met en cache et si vous supprimez une personne du rôle, il aura toujours ce rôle jusqu'à ce qu'il se marie. Et LE MARIAGE NE SERA PAS AVANT D'ÊTRE TESTÉ!: /)

Je peux déjà, j'ai réussi le test
image

Source: https://habr.com/ru/post/fr481474/


All Articles