Wir sind von einem anderen Test - wir testen die Datenbank auf MSTest

Testen als universelles Prinzip


Wir feiern das Jahrtausend seit fast einem Vierteljahrhundert, und das Testen beginnt gerade erst in unserem Leben ... Es ist schwer, unerfahrene Entwickler davon zu überzeugen, diese erstaunliche Technik in ihrer Arbeit zu verwenden ... Was können wir über Entwickler sagen, bloße Sterbliche, und es ist nicht immer möglich zu verstehen, dass das Testen die Grundlage ist nachhaltige systeme! Wie schwierig es ist, eine Verkäuferin davon zu überzeugen, dass das Testen eines neuen Produkts nicht bedeutet, es zu essen! Selbst erfahrene Sicherheitskräfte arbeiten offensichtlich auf die altmodische Art und Weise - sie versuchen, den Test nachzuholen und auszuwählen. Und Sie werden ihnen nicht beweisen, dass, wenn der Herr, Gott, selbst, TDD nicht für seine Arbeit missbilligt (erinnern Sie sich an die Große Sintflut), dann, wie sie sagen, Gott selbst befahl ...

Die Zahl der Scheidungen wächst - warum? Ja, egal! TDD! Erst testen - dann heiraten! Nein, leichtgläubige kleine Männer in locker sitzenden Schaffellmänteln, die sich für sexuell ausbeutende Werbung begeistern, bringen eine junge Frau direkt in die Produktion ...

Na ja, wir sind bei dir von einem anderen Test, erst testen - dann alles andere!

Ich erkenne den Tester am Gang ...


Als ich mit dem Schreiben der nächsten Code-First-Datenbank anfing, dachte ich darüber nach, warum ich meine DAL-Schicht nicht direkt anhand der in VisualStudio integrierten Tests automatisch testete.

Und ich habe es geschafft! Transparent für EntityFramework, ohne Fingerspitzengefühl unter der Decke und Betrug mit gefälschten Objekten. Wen kümmert's - VS aufdecken, wie Tester anziehen und los! (Ich kleide mich immer wie ein Tester)

Tester Kleidung
Bild

Lüge alle an, das ist eine Prüfung!


Lebenssache:
Hat an einem Projekt mit folgendem Code gearbeitet:

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

Dieser Code wurde nicht getestet, da er keine Zeit hatte - es war dringend erforderlich, neue Funktionen im Zusammenhang mit Marketing zu starten. Alles funktionierte beim manuellen Überprüfen und ich war bereits entspannt ... aber Bill Gates schlich sich unbemerkt ...

Es war ein poetischer Herbst in St. Petersburg, Schnee und Regen strichen sanft über mein Gesicht, Schmutz floss fröhlich von den Hosenbeinen herab und unbekannte Mädchen lächelten durch das verteilte Make-up, gossen vorbeifahrende Lastwagen ... Ich griff schon nach meinem Finger, um in meiner Nase herumzustochern, als es tückisch war, ohne Krieg zu erklären Im Morgengrauen schnitt Microsoft den Stacheldraht ab und veröffentlichte das Core 3.0-Update. Der Hoster wurde aktualisiert, ich habe auch aktualisiert, warum gehe ich in den alten - bin ich schlechter als ein Hoster? Ich habe alles geprüft und das Update rausgebracht ... und dann habe ich meine Augen ausgerollt! Neue Funktionalität hat nicht funktioniert! es scheint, dass ich es vorher getestet habe - was könnte passieren?

Und genau das ist passiert: Der alte Billy hat beschlossen, es aus LINQ ToLowerInvariant zu entfernen. Jetzt müssen Sie es im Voraus aufrufen und den fertigen Wert einfügen. Wenn der Code mit Tests bedeckt war, würde ich es beim Testen sofort bemerken. Es ist gut, dass ich alles selbst bemerkt habe, ich musste nicht beim Kunden schwören, weil der Tester sich schämte, rot zu werden ... Ich musste das Problem lösen und einen neuen Einsatz durchführen.

Geräte und Materialien:


Microsoft VisualStudio 2019
asp.net Core 3.1 (Ich habe es mit dem Studio installiert, wenn etwas über das Projektmenü "Andere Frameworks installieren" geliefert werden kann.)
SQL Server Express (wird mit dem Studio geliefert)
Git-Erweiterung zu Visual Studio (im Lieferumfang enthalten)

In der Regel sollte bei Komponententests jeder Test isoliert werden, und der Zustand zwischen ihnen bleibt nicht bestehen. Wir werden also Integrationstests bekommen, aber wir werden dafür MSTest verwenden. Ich hoffe, sie bringen uns nicht zur Polizei.

In mehreren Editionen habe ich die Verwendung von Mock-Objekten zum Testen der Datenbank kennengelernt.
Auf den ersten Blick ist die Idee gut, bis komplexe Interaktionen zwischen Tabellen beginnen.
Dann ist das Einrichten eines Mocks mehr als das Testen von selbst erforderlich. Tatsächlich ist es aber - Control + C - Control + V! Wir sind alle Datenbankeinschränkungen, die bereits in EF, Datenbank, DataAnnotations oder FluentAPI-Duplikaten in der Mock-Layer registriert wurden. Und das Kopieren ist so, als würde man ein Muster brechen ... ayah, Bürger, brechen ... nicht gut!

Und wenn die Scheinkonfiguration kompliziert ist und wir zum Beispiel Fehler in den Beschränkungen dort gemacht haben, stellt sich heraus, dass - der Scheintest bestanden wird, aber wird es einen Fehler auf der wirklichen Basis geben?

Das alles hat mich interessiert und ich habe beschlossen, einen neuen Ansatz zu testen.

Die Idee kam wie immer von TRIZ: Ein ideales System fehlt, aber seine Funktionen werden ausgeführt . Und ich dachte, dass ich die Datenbank selbst in den Tests verwenden muss.
Und es hat irgendwie für mich geklappt. Ich möchte dies teilen, ich hoffe, jemand hilft.

Nachteile von Mock:

  • viele presets die das testen
  • Tests werden schmutzig, viel zusätzlicher Code
  • schwer zu Migrationen zu testen
  • Verhalten Sie sich gut, nur unter Aufsicht, tatsächlich kann es zu unbekannten Fehlern kommen
  • Wenn Sie die Struktur der Datenbank ändern, müssen Sie ständig in den Schein einsteigen und auch dort alles ändern

Testprofis auf echter Basis:

  • Das Programm verhält sich genauso wie auf einem Kampfserver
  • Tests sind einfacher, Sie können sie nacheinander auf die gleiche Weise erstellen, wie Daten in die Datenbank eingegeben werden
  • Wir selbst können die Sauberkeit der Datenbank anpassen, indem wir die Tests nummerieren (in MSTest werden sie alphabetisch durchgeführt).
  • Sie können die Zeit sehen, in der der Test durchgeführt wird (auf einem realen Server ist dies anders, aber zumindest die Reihenfolge ist sichtbar - 10-mal länger, 2-mal, und Sie können bereits bewerten, wie das Programm effizient funktioniert oder nicht).
  • kann gespeicherte Prozeduren testen

Es gibt gewisse Schwierigkeiten in diesem Ansatz, mit denen ich seit mehreren Tagen grabe, aber wir werden sie erfolgreich lösen, und möge mein steinernes Backend bei Ihnen sein!

Lass uns gehen!

Wir erstellen ein neues Projekt ASP.Net Core 3.1-Webanwendung (Model-View-Controller), ändern die Authentifizierung in Einzelbenutzerkonten (Benutzerkonten in der App speichern) und klicken auf Erstellen

Bild

Ab sofort speichere ich Projekt-Schnappschüsse in Git. Sie können sie herunterladen und jeden Zweig hochladen, um damit zu experimentieren

github.com/3263927/Habr_1

Snapshot: Snapshot_0_ProjectCreated

Über das Repository
Auch wenn ich alleine arbeite, benutze ich immer das Repository - es ist jetzt sehr praktisch, direkt in Visual Studio integriert, keine Befehlszeile, alles funktioniert perfekt direkt von VS aus. Sie können experimentieren und ändern, was Sie wollen, und dann können Sie immer das Commit-Rollback korrigieren oder zum alten Zweig wechseln. Spart viel Zeit und Mühe, rate ich jedem. Und integriert sich mit Github kostenlos. Es ist wahr, dass ein Typ vor ein paar Jahren alles gelöscht hat ... Für den Fall, dass ich alle Projekte in Dropbox ablege und sie einmal pro Woche aktualisiere, alle Projekte archiviere und die neuesten Versionen manuell auf Google Drive hochlade. Nun, auf dem SD-Telefon gibt es auch 120 Gigs, die plötzlich in Reserve sind ... Ein paar Flash-Laufwerke mit Kopien in der Tasche sind einfach so unsichtbar!

Zu diesem Zeitpunkt habe ich ein Repository erstellt. Jetzt muss die Arbeit geplant werden, um neue Zweige zu erstellen. In Zukunft können Sie mithilfe des Schlüsselworts Snapshot Wiederherstellungspunkte finden, wenn ein Fehler auftritt.

Ich werde direkt in VisualStudio einen neuen Zweig im Repository erstellen und ihn kurz „Schwester des Talents“ nennen (Witz, Snap_1_DataBases).

Ziel: Schaffung einer funktionierenden Verbindung und Basis .

Wir fangen an, unsere Grundlagen zu schaffen.

Ich muss sofort sagen, dass wir drei Datenbanken haben werden - einen Test (auf dem lokalen Computer), eine andere Produktion (auf dem Remote-Server) und eine andere lokale Produktion (um den Zustand der Site in der DEBUG-Konfiguration zu überprüfen).

Die Logik ist folgende:

  • Wenn wir die Site auf dem lokalen Computer ausführen und sehen möchten, wie sie funktioniert, funktioniert Habr1_Local für uns
  • Wenn wir den Code in die Produktion aufnehmen, funktioniert Habr1_Production
  • Wenn unsere Testinfrastruktur mit dem Testen beginnt, muss sie die Habr1_Test-Basis finden und ausführen

Wir haben jedoch einen Widerspruch: Es gibt nur zwei Konfigurationen, Debug und Release. Dies ist immer noch ein Problem, aber dann wird es gelöst.

Also erstellen wir ein minimal arbeitendes Programm - zunächst prüfen wir nur, ob mindestens eine Datenbank für uns funktioniert. Lassen Sie uns es schaffen ... mit den Händen von Visual Studio selbst!

Öffnen Sie die Datei appsettings.json

Es gibt solche Zeilen:

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

Dort müssen Sie die korrekten Namen der Verbindungszeichenfolgen, den Namen des Servers und den Namen der Datenbank eingeben. Ich muss gleich sagen, dass andere Anschlüsse in der Produktion verwendet werden, aber das brauchen wir jetzt nicht. Unsere Aufgabe ist es, zwei Datenbanken zu erstellen (lokal und testweise, die Produktion ist nur ein Beispiel - wir werden sie in der Release-Konfiguration verwenden. Dann kann sie durch eine funktionierende entfernte Datenbank ersetzt werden).

Warum ist das nötig?

In Visual Studio-Konfigurationen können Sie einige Einstellungen ändern, indem Sie die Konfiguration im Visual Studio-Bedienfeld ändern:

Bild

Im Allgemeinen deklariert die Entwicklungsumgebung einfach eine DEBUG-Konstante, die dann an einer beliebigen Stelle im Code gelesen werden kann und erkennt, in welcher Konfiguration wir uns gerade befinden und welche aktiv ist.

Remote-Debugger ist beispielsweise beim Colocation-Hosting nicht immer verfügbar. Wir können unseren asp.net-Server lokal ausführen und eine Verbindung zur Datenbank mit dem Remote-Server herstellen. Außerdem können wir alle Fehler anzeigen, die in der Produktion aufgetreten sind. Hier in der Release-Konfiguration werden wir dies tun und die Debug-Konfiguration wird mit unserer lokalen Datenbank funktionieren. Aus Sicherheitsgründen werden wir die Konfiguration nicht testen, um nicht versehentlich Daten zu löschen, und die Konfiguration nicht zu ändern

Also fangen wir an, die Verbindungszeichenfolge zu ändern.

Servername - Sie können ihn in der Registerkartenansicht sehen -> SQL Server-Objekt-Explorer

Bild

(Ich werde den Namen meines Computers mit Bedacht löschen, ansonsten werden Sie mich nach IP berechnen und etwas eingeben).

Also habe ich diese (localdb) \ ProjectsV13. Ich weiß nicht, warum ich während der Installation mein SQL aufgerufen habe.
Dies bedeutet, dass unsere Verbindungszeichenfolge wird

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

Möglicherweise haben Sie es anders, aber nur ProjectV13. Der Rest sollte so gelassen werden.
Ändern Sie DefaultConnection in Habr1_Local

Es stellt sich so heraus:

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

Jetzt müssen Sie zur Datei Startup.cs gehen und dort DefaultConnection durch Habr1_Local ersetzen:

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

verwandelt sich in

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

Wir starten unser Projekt in der Debug-Konfiguration, der Browser öffnet sich, wir sehen die erste Seite, klicken auf den Login-Button, geben dort eine gültige E-Mail ein (es werden keine Briefe verschickt, es validiert nur das Format) und klicken auf Login - und wir sehen diesen Bildschirm:

Bild

Klicken Sie auf "Migration anwenden". Wir warten, bis rechts neben der blauen Schaltfläche, die die Migration abgeschlossen hat, eine Bestätigung angezeigt wird. Sie müssen die Seite aktualisieren und auf "Bestätigen" klicken, um die Daten erneut zu senden. Anschließend wird der Bildschirm "Anmeldung fehlgeschlagen" angezeigt:

Bild

Wenn ein solcher Bildschirm sichtbar ist, ist alles in Ordnung - nachdem die Anforderung bestanden und ein ungültiger Anmeldeversuch zurückgegeben wurde, ist kein Datenbankfehler aufgetreten, aber es wurde einfach kein solcher Benutzer gefunden.

Ein bisschen über Migration:
Dies ist ein komplexes Werkzeug und es ist kaum notwendig, es in diesem Artikel zu berühren, aber Sie können ein wenig berühren.
Beispielsweise müssen Sie den Status der Datenbank auf einen bestimmten Status ändern. Sie können hierfür einen Datenbank-Snapshot oder mehrere Snapshots für jeden Status erstellen und diese Images programmgesteuert und mit speziellen Befehlen aktivieren / deaktivieren.

Oder wenn Sie etwas auf dem lokalen Computer entwickeln und dann den neuen Status der Datenbank auf dem Server mit dem lokalen synchronisieren müssen, aktualisieren Sie den Produktionsserver auf den Status Ihrer lokalen Datenbank und führen Sie dies automatisch aus - die Migration kann ebenfalls angewendet werden. Das ist eigentlich dieser blaue Knopf. Die Basis weiß, dass sich ihr Status vom Status des Codes unterscheidet, und versucht, diese Status zu synchronisieren. Dazu wird in der Datenbank eine spezielle Tabelle mit dem verschlüsselten Zustand der Datenbankstruktur angelegt.

Leider wird dieses Tool dadurch kompliziert, dass es keine visuelle Oberfläche dafür gibt und Sie über die Befehlszeile damit arbeiten müssen. Migrationen sind praktisch, wenn Sie Zustände über Git übergeben müssen, indem Sie einfach solche Datenbank-Snapshots als C # -Dateien erstellen. Dieses Tool birgt jedoch eine Gefahr: Wenn es falsch konfiguriert ist, kann es Daten in der Datenbank löschen. Sie sollten es daher mit Vorsicht verwenden.

Überprüfen der Verfügbarkeit der Datenbank - Visual Studio sollte diese erstellt haben

Bild

Wenn keine Datenbank vorhanden ist, ist ein Fehler aufgetreten. Entweder wurde der SQL-Server nicht installiert, oder es wurde etwas anderes installiert, etwa ein Scherz darüber, wie es wäre, wenn die Programmierer Ärzte wären: „Doktor, mein Bein tut weh ... - na ja, nicht Ich weiß, ich habe das gleiche Bein und nichts tut weh! “

An dieser Stelle mache ich zwei weitere Verbindungszeichenfolgen, appsettings.json nimmt diese Form an:

 "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" }, 

Ich mache ein Commit und füge den folgenden Snapshot in das Repository ein:

Snapshot: Snap_1_DataBases

Erstellen Sie einen neuen Zweig, Snap_2_Configurations

Ziel: Arbeitskonfigurationen erstellen

Beim Wechseln von Konfigurationen können wir von überall im Programm aus berücksichtigen, welche Konfiguration aktuell ist (in der Tat funktioniert sie in View nicht - wir müssen eine spezielle Funktion ausführen, dies ist jedoch nicht wichtig):

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

Öffnen Sie die Datei Startup.cs und konvertieren Sie die ConfigureServices-Methode in diese:

 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(); } 

Wie Sie sehen, haben wir Habr1_Local durch eine Variable ersetzt. Abhängig von der Konfiguration lautet der Verbindungsstring entweder Habr1_Local oder Habr1_Production

Jetzt können Sie das Projekt starten und überprüfen, wie die Datenbanken je nach Konfiguration erstellt werden

Wir wählen DEBUG im Panel aus, starten, melden uns an, wenden die Migration an und prüfen, ob die Datenbank erstellt wurde (Habr1_Local)

Wir stoppen das Projekt, wählen die Release-Konfiguration aus, starten, melden uns an, wenden die Migration an und prüfen, ob die Datenbank erstellt wurde - wir haben 2 Basen.

Fertig

Schnappschuss: Snap_3_HabrDB

Zweck: Erstellen eines separaten Datenbankprojekts, das dann in verschiedenen Projekten verwendet werden kann

Warum ein eigenes Projekt?

Einzelne Projekte haben mehrere Vorteile:

  • Sie können in anderen Projekten verwendet werden.
  • Sie werden nicht erneut kompiliert, wenn keine Änderungen vorgenommen wurden, was bedeutet, dass sich die Gesamtkompilierungszeit verringert
  • Einzelne Projekte lassen sich leichter testen.

Also, die rechte Taste auf der Lösung - Hinzufügen -> Neuer Lösungsordner, nennen Sie es DB.
Dann rechts in den erstellten Ordner - neues Projekt hinzufügen -> .net Standard, Name HabrDB.
Aus irgendeinem Grund habe ich es als .net Standard 2.0 erstellt, ich muss es auf 2.1 ändern
(Wenn Sie einen physischen Pfad erstellen, lassen Sie ihn im DB-Ordner und auch physisch liegen.)

Für mich sieht es so aus:

Bild

Wir haben also einen ApplicationDBContext im Projekt und einen eigenen erstellt? Werden sie miteinander in Konflikt stehen? Jetzt werden wir uns mit ihnen anfreunden. Wir werden zwei verschiedene Kontexte zu derselben Datenbank haben, die sich nicht durch das Entity Framework schneiden. Wir werden ihnen einen anderen Schemanamen geben: Einer bleibt standardmäßig dbo und der andere ist "habr".

Wenn Sie solche Kontexte über die Wurzel der Komposition verbinden, können sie in anderen Projekten fast transparent wiederverwendet werden. (Z. B. Lagerkontext und Mitarbeiterkontext)

Und noch ein architektonischer Moment, manchmal müssen Sie dem Benutzer einige Ihrer Eigenschaften hinzufügen. Dies gilt nicht direkt für das Thema des Artikels, aber wir werden es nur tun, um zu wissen, wie es geht. Außerdem können wir unabhängig voneinander Kontexte erstellen und löschen. Eine gute Idee ist es, die Sicherheitstabelle mit personenbezogenen Daten zu trennen und auf Datenbankebene zu verschlüsseln (dies wird in diesem Projekt nicht durchgeführt, ist jedoch in der Regel manchmal erforderlich, auch gesetzlich).

Ja, und es ist einfacher, auf diese Weise zu testen, Sie können nicht alle Tabellen auf einmal erstellen, sondern nur die, die zum Testen des angegebenen Kontexts erforderlich sind.

Ich erstelle einen neuen Projektschnappschuss - Phase 4.

Die Ziele dieser Phase sind:

  • Ändern Sie den Standardbenutzer in "Erweitert"
  • Benutzer in Srartup.cs-Datei ändern
  • Benutzer in LoginPartial und ViewImports ändern
  • Erstellen Sie eine neue Migration, um automatisch eine Datenbank in einem neuen Format zu erstellen

Daher übertragen wir die ApplicationDBContext-Klasse aus dem WebApp-Projekt nach HabrDB.

Es ist nicht portabel, nur kopiert. Wir entfernen es aus WebApp, öffnen es aus dem HabrDB-Projekt und ändern seinen Namespace in HabrDB. Es treten viele Fehler auf.

Ja, in diesem Projekt gibt es keine notwendigen Pakete, jetzt werden wir sie liefern.

Über Nuget müssen Sie im HabrDB-Projekt Microsoft.AspNetCore.Identity.EntityFrameworkCore installieren.

Bild

Wir klicken auf die Glühbirne und sie bietet uns an, die neuesten Versionen zu installieren.

Die endgültige SecurityDBContext-Datei (muss ebenfalls umbenannt werden) hat folgende Form:

 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) { } } } 

Nach der Assembly werden die fehlerhaften Dateien sorgfältig verarbeitet. Zu Recht haben wir ApplicationDBContext gelöscht und durch SecurityDBContext ersetzt. Jetzt müssen Sie alle Links zu ApplicationDBContext im gesamten Projekt durch SecurityDBContext ersetzen.

Danach tauchten in meinem Projekt gelbe Dreiecke auf Referenzen von WebApp auf, die darauf hinweisen, dass einige Links fehlerhaft funktionieren. Ich habe das Projekt gesäubert (build -> clean solution), das Projekt geschlossen, alle Debug-, Release-, Obj- und Bin-Verzeichnisse aus dem Projektordner gelöscht, danach das Projekt erneut geöffnet, einige Zeit nach den erforderlichen Links im Internet gesucht und diese und Dreiecke geladen verschwunden - dann ist alles in Ordnung.

Löschen Sie nun den Datenordner aus dem WebApp-Projekt, löschen Sie unsere Datenbanken (Habr1_Local und Habr1_Production) im Fenster des SQL Server-Objekt-Explorers und führen Sie das Projekt aus. Wir versuchen uns einzuloggen - und jetzt gibt es anstelle des Angebots zur Migration einen Fehler.

Bild

Richtig, wir haben den Datenordner gelöscht, in dem sich alle Migrationen befanden, und jetzt weiß das Framework nicht mehr, was zu tun ist. Aber es war so cool?! Warum? Dann werden wir jetzt die Benutzerklasse erweitern.

Fügen Sie dem HabrDB-Projekt eine neue Datei hinzu:
ApplicationUser.cs

Wir erben es von 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; } } } 

Fügen Sie in der SecurityDBContext-Datei im Klassenheader Folgendes hinzu:

 public class SecurityDBContext : IdentityDbContext<ApplicationUser> 

Dies ist erforderlich, damit EntityFramework weiß, dass Sie beim Erstellen der Datenbank das erweiterte Benutzermodell aus der AppllicationUser-Klasse anstelle des Standardmodells verwenden müssen.

Die ConfigureServices-Methode in der Datei Startup.cs hat die folgende Form:

 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(); } 

(Ersetzen Sie IdentityUser durch ApplicationUser.)

Fügen Sie in der Datei _ViewImports.cshtml die Zeile hinzu

mit HabrDB

Jetzt sehen alle Ansichten unser Projekt mit der Basis und Sie müssen zu Beginn der Ansichten nicht mehr mit HabrDB schreiben.

Ändern Sie in der Datei _LoginPartial.cshtml all IdentityUser in ApplicationUser.

Verwenden von Microsoft.AspNetCore.Identity
Injizieren Sie SignInManager SignInManager
Injizieren Sie UserManager UserManager

Für alle Fälle kompilieren wir das Projekt, um sicherzustellen, dass wir keine Fehler haben und nicht vergessen haben, irgendwo etwas zu ersetzen.

Führen Sie nun die Migration durch.

Sie müssen die Package Manager-Konsole öffnen, das DB / HabrDB-Projekt auswählen und darauf schreiben

 add-migration initial 

Folgendes habe ich bekommen:

Bild

Der Migrations-Daddy erschien im HabrDB-Projekt und es gibt Dateien, mit denen wir unsere Datenbank automatisch erstellen können, jetzt jedoch mit unseren zusätzlichen Feldern - NickName, BirthDate, PassportNumber.

Lassen Sie uns versuchen, wie es funktioniert - lassen Sie uns ausführen und versuchen, sich anzumelden (nicht registrieren, Sie müssen dort komplexe Passwörter eingeben):

Bild

Mir wurde angeboten, eine Migration durchzuführen, und ich stimmte zu - dies ist unsere Basis:

Bild

Mit dieser Phase alles

Snapshot: Snap_4_Security ist bereit

Erstellen Sie den fünften Schuss.

Ziele:

  • Testverbindungszeichenfolge zum Laufen bringen
  • Erstellen Sie ein Datenbanktestprojekt
  • Lassen Sie das Testprojekt eine Basis erstellen und etwas Nützliches testen

Wir klicken mit der rechten Maustaste auf den DB-Daddy und erstellen ein neues Projekt - MSTest .net core.
Benennen Sie die einzige Datei in diesem Projekt in DBTest um und denken Sie ...
Weiter wird es schwierig sein.

Das problem

Wie können wir einen Kontext erstellen, der garantiert mit der Testdatenbank kommuniziert, dh ConnectionString nicht nur aus einem anderen Projekt (WebApp) verwendet, sondern auch eine Verbindung mit Release- / Debug-Konfigurationen herstellt? Kann eine neue Konfiguration erstellen, z. B. Test ?

Nein, dies ist ein möglicher Datenverlust - irgendwie haben wir vergessen, dass wir vergessen haben, von der Testkonfiguration zu wechseln, auf die Schaltfläche Alle Tests ausführen zu klicken - und dort die gesamte Datenbank zu entfernen ... nein, diese Option funktioniert nicht ...

Wir erstellen also eindeutig einen Testkontext!

Öffnen Sie die HabrDBContext-Datei und ändern Sie ihren Inhalt in:

 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; } } } 

Fügen Sie über Nuget der Datenbank die folgenden Bibliotheken hinzu:

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

Die CreateTestContext-Methode kann einfach nur eine Verbindungszeichenfolge mit dem Namen Habr1_Test zurückgeben. Sie müssen es in einem anderen Projekt übernehmen. Um das Projekt nicht über einen Verweis zu verbinden, da wir nur eine Einstellung benötigen, bitten wir den Builder, Optionen basierend auf dem angegebenen connectionString zu erstellen. Dazu durchlaufen wir die Verzeichniskette von dem Verzeichnis, in dem das Testprojekt kompiliert wurde, bis zum Projektstammverzeichnis. Sammeln Sie den Pfad zum WebApp-Projekt aus den Teilen. Bitte fügen Sie für uns die Konfigurationsdatei appsettings.json hinzu, in der unsere Einstellungen gespeichert sind, und kompilieren Sie diese in die Konfiguration. Weiter entfernt von dieser Konfiguration nehmen wir connectionString und merken es uns (wir werden es später brauchen).

In großen Projekten können Sie Einstellungen mit Zeichenfolgen, einer DLL oder einem Objekt in ein separates Projekt verschieben, um mit Caching auf Einstellungsdaten zuzugreifen. Vorerst wird ein einfacherer Ansatz ausreichen.

Warum so?

Sie können einen Test ConnectionString direkt in das Testprojekt einfügen und die Datenbank damit initialisieren. Sie können einen Test ConnectionString in der Datenbank erstellen. Aber dann wird es eine unangenehme Sache geben: Alle Verbindungszeichenfolgen werden an verschiedenen Orten gespeichert. Aber ich selbst weiß, dass mein Kopf voller Löcher ist und ich kann vergessen, etwas zu ändern, wenn sich zum Beispiel der Name der Datenbank oder des Servers ändert, und damit ich alle Verbindungslinien an einem Ort habe, mache ich das. Durch Ändern der Konfigurationsdatei appsettings.json können Sie jetzt alle Verbindungen verwalten.

Versuchen wir nun, mit der Basis etwas Nützliches zu tun, zum Beispiel etwas zu erstellen.

Im HabrDB-Projekt habe ich den DBClasses-Ordner erstellt, dort werden nur Datenbanktabellenklassen vorhanden sein. Wir arbeiten zuerst den Code durch. Wenn ich mir vorstellen kann, was ich tue, ist es für mich normalerweise bequemer.

Erstellen Sie eine Tabelle wie folgt:

 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; } } } 

Lassen Sie meine Klasse der Schönheit halber "Telefon" und die Tabelle in der Datenbank im Plural "Telefone" heißen. Daher gebe ich im Tabellenattribut an, wie meine Tabelle benannt werden soll und in welchem ​​Namespace sie sich in der Datenbank befinden soll (in SQL wird dies als Schema bezeichnet).

Jetzt gibt es noch eine andere Frage der Ästhetik: Hier haben wir eine Reihe verschiedener Infrastrukturmethoden in der Datenbankklasse, und dann haben wir verschiedene Tabellen und das alles in einem Haufen - Sie können Teilklassen erstellen.

Fügen Sie das Wort partiell nach der Wortklasse in die Datei HabrDBContext.cs ein - wie folgt:

 public partial class HabrDBContext:DbContext 

Erstellen Sie eine Kopie der Datei HabrDBContext.cs - proosto STRG + C - STRG + V auf der Datei, wird eine Kopie erstellt wird, wir den ursprünglichen Dateinamen HabrDBContext_Infrastructure.cs ändern, die neuen auf HabrDBContext_Data.cs

New Schreiben:

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

Jetzt ist es schön - wir arbeiten an einem Ort mit Daten, an einem anderen mit Infrastruktur. Die Dateien sind unterschiedlich, aber die Klasse ist dieselbe - die Umgebung selbst setzt sie beim Erstellen des Projekts aus mehreren in eine zusammen.

Nun, probier es aus!

Ersetzen Sie den Code in unserer einzigen Testklasse durch:

 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); } } } 

Klicken Sie auf der Registerkarte Test Explorer (oder Test -> Alle Tests ausführen) auf die Wiedergabetaste und ...

Bild

Fehler!hier sind die auf.

Wir lesen, was sie uns schreiben:

 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. 

Irgendeine Scheiße auf Englisch ...
Okay, wir werden nach dem Zufallsprinzip handeln!

Könnte diese Funktion in die Datei HabrDBContext_Infrastructure.cs kopiert werden? Lass es uns versuchen!

 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); } 

Wir fangen an ...

Das war ein Glücksfall !!!
Eine neue Basis wurde geschaffen und darin - unser Tisch!

Bild

Warum so?

Wenn Sie den OnConfiguring- und CreateTestContext-Funktionen einige Haltepunkte zuweisen, wird zuerst die CreateTestContext-Methode aufgerufen, und die Verbindungszeichenfolge wird im ConnectionString-Objekt gespeichert. Alles scheint in Ordnung zu sein. Aber dann versucht jemand OnConfiguring anzurufen ... wer ist das? Schauen wir uns die Aufrufliste an - ja, das ist die Zeile db.Database.EnsureCreated () aus dem Test! Tatsache ist, dass wir noch keine Basis als solche haben - die EnsureCreated-Methode schafft es. Diese Methode benötigt jedoch keine Parameter mehr und der Kontext muss zwischen den Aufrufen des Konstruktors und von EnsureCreated beibehalten werden. Wenn wir diesen Kontext aus dem Projekt selbst verwenden (nicht zum Testen, sondern zum Beispiel auf der Website), versuchen darüber hinaus alle Arten von Middlware, DI und anderen eingängigen Mechanismen, ihn aufzurufen, sodass wir alles im Voraus vorhersehen - wer auch immer unsere Datenbank aufruft ,Wenn er OnConfiguring anrufen möchte, hat er eine solche Gelegenheit. Wir haben für alles gesorgt.

Führen Sie den Test erneut aus - und ...
Wieder ein Fehler?
Die Datenbank ist schon da, die Daten sind ... was ist los?
Eine Basis ist ein beständiges Objekt, das erhalten bleibt, auch wenn wir unser Projekt neu starten ... und jetzt beginnt der wirklich schwierige Teil. Wie lösche ich diese alle Tabellen? Wie lösche ich Daten, setze alle Indizes zurück?

Snapshot: Snap_5_ContextCrafting ist fertig.

Schreiben Sie zuerst DAL - Data Access Layer.

Erstellen Sie eine Kopie der Datei HabrDBContext_Data.cs, ​​rufen Sie sie HabrDBContext_DAL.cs auf
und schreiben Sie darin:

 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; } } } 

Dies ist ein Wrapper über unsere Daten. Ich würde nicht wollen, wenn ich etwas an den Schnittstellen für den Zugriff auf die Datenbank ändere, um die Suche während des gesamten Projekts, in dem ich sie aufgerufen habe, zu behandeln. Aus diesem Grund erstellen wir eine Abstraktionsschicht - die Datenzugriffsschicht. Er wird der Vermittler zwischen den Basis- und MVC-Mechanismen des Standorts oder anderen Mechanismen sein. Und - ein Zufall? - Wir haben sofort Testobjekte! Ein weiterer Grund für die Verwendung von Tests besteht darin, dass sie Lösungen mit geringer Konnektivität provozieren.
Ändern Sie den Funktionscode in den Tests - und zum einen den Namen. Jetzt wissen wir, was wir testen!
Telefon hinzufügen!

Wir werden das Repository nicht machen, für ein Demo-Projekt ist dies eine redundante Lösung.
Normalerweise können Sie alles über DI in startup.cs verbinden, aber für das Demo-Projekt ist dies zu verwirrend. Lassen wir es also so

Neuer Testfunktionscode:

 [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); } 

Wir werden die Leitung mit dem Telefon aus der Testdatenbank entfernen und den Test erneut ausführen - es sollte funktionieren.
Wenn es nicht funktioniert, haben Sie immer noch ein anderes Bein. Bei mir

Bild

ist alles grün: Was ist db.GetAllPhones (). Ergebnis? Tatsache ist, dass unsere DAL-Funktionen asynchron sind. Die Testmethode selbst ist jedoch normal und daher kann wait nicht darin aufgerufen werden. Versuchen wir, die Daten zu löschen, die Methode asynchron zu machen und zu sehen, was passiert.

Unsere Funktion ist zu einer asynchronen Aufgabe geworden. Andernfalls wird der Test nicht gestartet, und wo immer asynchrone Methoden aufgerufen werden, müssen Sie auf sie warten

 [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); } 

Es ist wichtig, dass alle Testfunktionen, bei denen es asynchrone Aufrufe gibt, Task und nicht void zurückgeben. Andernfalls werden die Tests entweder nicht gestartet oder warten nicht auf die Rückgabe asynchroner Daten und gehen weiter, und es tritt ein Fehler auf.

Also funktioniert mehr oder weniger.

Snapshot: Snap_6_Dal

Und was ist es notwendig, Daten manuell zu löschen? Natürlich nicht!

Wir brauchen eine Funktion zum Löschen von Tabellen aus der Datenbank ...

Es wäre cool, db.Database.EnsureDeleted zu verwenden ... und es ist wirklich möglich!

Aber besser ist nicht nötig ... Tatsache ist, dass in diesem Projekt unsere Basen nicht mit Passwörtern verbunden sind. Wenn die Datenbank einem Kennwort zugeordnet ist, müssen Sie es separat erstellen - über SQL Management Studio. Wenn Sie es aus db.Database.EnsureDeleted löschen, wird es zusammen mit allen Kennwörtern, Zugriffen und Benutzerrechten gelöscht. Wenn das Framework das nächste Mal versucht, es zu erstellen, dann gibt es einfach keinen zugriff auf die datenbank, du musst alles neu konfigurieren.

Dies ist der erste.

Das zweite ist, dass es besser ist, keine unnötige Arbeit zu leisten. Daher dauern die Datenbanktests länger als die üblichen Funktionen, die sich nicht auf externe Mechanismen beziehen, und es ist besser, die Testzeit auf alle möglichen Arten zu optimieren.

Und drittens: Vielleicht müssen wir in einem Test eine oder mehrere Tabellen löschen und neu erstellen, während die anderen Tabellen intakt bleiben.

Lassen Sie uns versuchen, die Funktion db.Database.EnsureDeleted aufzurufen, die Klammer zu drücken und zu sehen, was sie akzeptiert und ob sie Überladungen aufweist ...

Bild

Ja, nicht viel ... Nun
, ok, lassen Sie uns unsere eigene schreiben.

Fügen Sie der Projektmappe (in der Projektmappe selbst) mit der rechten Maustaste ein neues Projekt hinzu, fügen Sie -> .net standard C # hinzu, und nennen Sie es Extensions. Überprüfen Sie, ob es sich um die 2.1-Version handelt.

Als Nächstes müssen Sie die einzige Datei im Extensions-Projekt in DBContextExtensions.cs umbenennen und den folgenden Code dort einfügen:

 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; } } } 

Fügen Sie Microsoft.EntityFrameworkCore aus Nuget hinzu,
und ein weiteres Paket, Microsoft.EntityFrameworkCore.Relational

Extensions, ist ein sehr praktischer Mechanismus. Damit können Sie dem Klassenobjekt zusätzliche Funktionen hinzufügen, wie wir es getan haben - das this-Schlüsselwort vor dem Typ des ersten Parameters

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

Gibt einen erweiterbaren Typ an. Dies bedeutet, dass das DatabaseFacade-Objekt nach dem Schreiben eine neue Methode hat - EnsureDeleted mit einem Parameter, der unsere Funktion ist, die wir gerade geschrieben haben! Wobei TEntity: class am Ende bedeutet, dass TEntity eine Einschränkung hat - eine Einschränkung der Verwendung. Wenn wir versuchen, sie nicht durch eine Klasse, sondern durch etwas anderes zu verallgemeinern, tritt beim Kompilieren ein Fehler auf.

Ich sehe deine logische Frage voraus - warum ist das so?

Die GetTableName-Funktion, die von EnsureDeleted aufgerufen wird, benötigt diese Einschränkung.
Warum braucht sie diese Einschränkung?

Welche Funktion GetDbContext, die von GetTableName aufgerufen wird, benötigt diese Einschränkung ebenfalls ...

Und warum benötigt sie diese Einschränkung ??? !!! Sie fragen in erhöhten Tönen und Sie werden Recht haben ...

Das dbSet, das wir mit der GetDbContext-Methode in der Zeile erweitern möchten

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

erfordert, dass T ein Referenztyp ist, und Klasse ist nur einer von ihnen ... Bei

all diesen Schwierigkeiten - für ein kleines, aber sehr nützliches Feature - werden der EnsureDeleted-Methode keine Zeichenfolgenwerte als Parameter übergeben. Weil ich ein schlechtes Gedächtnis habe. Ich möchte, dass DBContext mir mitteilt, über welche Tabellen er verfügt, und aus diesem Datensatz den Kontext berechnet, ihn über den Dienstanbieter in den aktuellen Kontext bringt, dann das Modell aus diesem Kontext entnimmt, Typen daraus entnimmt und dann nach dem Typ mit diesen Typen sucht Das gleiche wie das Dataset (dies ist bereits in GetTableName enthalten), dann übergibt es durch Annotationen den Namen der Tabelle und des Schemas bereits an EnsureDeleted und dann - verdammt! wieder gibt es keine Funktion wie removetable oder so, man muss SQL-Syrniki aus Strings konstruieren ... na ja, zumindest irgendwie hat es geklappt!

Und die Ausnahme ist die Funktion EnsureDeleted, sodass Sie nicht über die Vorgehensweise zum Löschen von Tabellen nachdenken können. Wenn dort verwandte Daten vorhanden sind, versuchen Sie nach dem Löschen der verwandten Tabellen, die Tabelle erneut zu löschen, ohne sich darum zu kümmern.

Fügen Sie die Methode zu den Tests hinzu (am Ende)

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

Laufen, überprüfen, ausatmen ...
Laufen Sie noch einmal - sollte funktionieren.

Jedes Mal, wenn Sie Tests ausführen, werden die erforderlichen Tabellen erstellt und anschließend gelöscht.

Snapshot: Snap_7_Extensions

neue Hämorrhoiden Horizonte

Aber es gibt ein Problem - HabrDBContext Objekt , das wir nicht persistent sind (in jedem Testverfahren erstellt). (Nun ja, die Kagbe-Idee von Komponententests ist zu isolieren. Und wir versuchen, sie zu durchbrechen! Es ist gut, dass die Polizei nicht sieht ...)

Das heißt, bei jeder Testmethode wird der Kontext neu erstellt, und wir können dieses Objekt nicht zwischen Funktionen teilen damit es allen gemeinsam ist und einmalig für alle tests erstellt wird. Können wir nicht Okay, wir werden in jede Funktion schreiben.

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

und die letzte Methode aufrufen

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

und einige andere Tische ...

Es ist nicht sehr schön, daher werden wir uns überlegen, wie wir dieses Problem lösen können. Da wir uns wie Tester kleiden, müssen wir dem Bild entsprechen - wir werden nach einer Lösung suchen!

Klassen haben solch eine interessante Eigenschaft - statische Mitglieder.

Wenn Sie sich eine Zeichnung und ein Objekt aus dieser Zeichnung vorstellen, stellt sich heraus, dass es sich bei einem statischen Element nicht um ein Element handelt, das dem aus der Zeichnung erstellten Objekt eigen ist, sondern um diese Zeichnung selbst.

Das ist schon interessant ...
Jetzt lass es uns versuchen!

Unsere Testklasse heißt DBTest, erstellen Sie ein statisches Objekt dafür - 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); } } } 

Es funktioniert

Aber etwas ist dumm, solche Methoden zu nummerieren.
Es gibt eine Lösung!
Spezielle Testattribute!
Erstellen Sie im Testprojekt eine neue Klasse mit dem Namen DBTestBase und erben Sie die DBTest-Klasse davon.
Aus DBTest müssen Sie alles entfernen, außer:

 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); } } } 

Der Inhalt der DBTestBase-Klasse:

 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! } } } 

Jetzt ist alles klar! Unsere Datenbank wird als statischer Teil der DBTestBase-Klasse erstellt und ist daher für alle von ihr geerbten Klassen zugänglich. Dies bedeutet, dass die Datenbank nur einmal erstellt wird - beim Starten der Tests.

Fügen Sie das Attribut [ClassCleanup] zu jeder Methode einer Klasse hinzu - und führen Sie einen "saubereren" Test durch - eine Funktion, die etwas bewirkt, nachdem alle Tests dieser Klasse abgeschlossen wurden.

Beispielsweise werden alle in diesem Test erstellten Tabellen gelöscht, ohne dass die Datenbank selbst berührt wird.

Da all diese Sonderfunktionen von den Metriken ausgeschlossen sind, können wir die reine Zeit sehen, für die unsere Funktionen arbeiten - wir fügen einige verbleibende Objekte hinzu und wir sehen einen starken Anstieg der Arbeitszeit der Auswahlfunktion -, was bedeutet, dass etwas mit unseren DAL-Funktionen nicht stimmt und wenn Sie eine Funktion testen Bei einer Testmethode wird sofort klar, wo das Problem liegt.

Sie können auch Wiedergabelisten aus Funktionen erstellen.

Dies ist notwendig, um zB die Datenaufbereitungsfunktion aufzurufen (nur zum Testen).

Dann aktualisiert die DAL-Funktion diese Daten, dann eine andere DAL-Funktion, die beispielsweise etwas überlegt oder einen Bericht aus diesen Daten anzeigt, und löscht diese Daten.

So können wir einige Benutzeraktionen aus realen Geschäftsprozessen nachahmen.

Das Wichtigste ist, dass außerhalb dieser Testattribute die Tests in alphabetischer Reihenfolge aufgerufen werden.

Wenn Sie also eine statische Sequenz benötigen, sollten Sie diese wie

folgt bezeichnen : T1_AddPhone_Test,
T2_RemovePhone_Test
usw. ...

Nun, Sie können sich keine Sorgen mehr um unsere Datenbank machen - alles wird vollständig getestet!
Und jetzt ist es Zeit zu schlafen! Ich habe dort noch ein Mädchen nicht getestet ...

Erfolgreich getestet! Tschüss alle!

Git-Repository-Projekt: https://github.com/3263927/Habr_1

Schreiben Sie in die Kommentare, ob dieses Thema interessant ist. Dann schreibe ich einen Beitrag über das Testen von Identität, Rollen, Ansprüchen, 3D-Authentifizierung und das Schreiben meines TypeFilterAttribute EHE WIRD NICHT HERAUSGEHEN, BIS GEPRÜFT WERDEN !: /)

Ich kann schon, ich habe den Test bestanden
Bild

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


All Articles