diff --git a/p08_BDD_EntityFramework/ex_041_004_TestingInMemory/ReadMe.md b/p08_BDD_EntityFramework/ex_041_004_TestingInMemory/ReadMe.md index d755f32..21d841d 100644 --- a/p08_BDD_EntityFramework/ex_041_004_TestingInMemory/ReadMe.md +++ b/p08_BDD_EntityFramework/ex_041_004_TestingInMemory/ReadMe.md @@ -16,7 +16,20 @@ Pour le reste de l'exemple, celui-ci n'apporte rien de nouveau par rapport à l' ## Pourquoi autant de projets dans cet exemple ? +Quatre projets constituent cet exemple : +* **ex_041_004_TestingInMemory** est une bibliothèque de classes .NET Standard contentant le modèle, ie la classe ```Nounours``` et la classe ```NounoursContext``` +* **ex_041_004_ConsoleTests_w_SqlServer** est une application console .NET Core qui prouve le fonctionnement "normal" de la base de données (ie comme dans l'exemple ex_041_001) en exploitant la bibliothèque de classes **ex_041_004_TestingInMemory** +* **ex_041_004_UnitTests_w_InMemory** est une application de tests unitaires xUnit exploitant le fournisseur **InMemory** et la bibliothèque de classes **ex_041_004_TestingInMemory** +* **ex_041_004_UnitTests_w_SQLiteInMemory** est une application de tests unitaires xUnit exploitant le fournisseur **SQLite in memory** et la bibliothèque de classes **ex_041_004_TestingInMemory** + +Vous pouvez donc exécuter cet exemple de trois manières : +* via **ex_041_004_ConsoleTests_w_SqlServer** comme dans l'exemple ex_041_001 avec ```dotnet ef``` +* via les tests unitaires de **ex_041_004_UnitTests_w_InMemory** +* via les tests unitaires de **ex_041_004_UnitTests_w_SQLiteInMemory** + ## Comment a été construit cet exemple ? + +### bibliothèque .NET Standard ex_041_004_TestingInMemory Cet exemple est tout d'abord construit de la même manière que l'exemple *ex_041_001_ConnectionStrings*. Il ne faut pas oublier les NuGet nécessaires : * Microsoft.EntityFrameworkCore : pour le projet en général @@ -28,14 +41,14 @@ On obtient ainsi la classe ```NounoursContext``` suivante : ```csharp using Microsoft.EntityFrameworkCore; -namespace ex_041_004_InMemory +namespace ex_041_004_TestingInMemory { public class NounoursContext : DbContext { public DbSet Nounours { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder options) - => options.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=ex_041_004_InMemory.Nounours.mdf;Trusted_Connection=True;"); + => options.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=ex_041_004_TestingInMemory.Nounours.mdf;Trusted_Connection=True;"); } } ``` @@ -46,7 +59,7 @@ protected override void OnConfiguring(DbContextOptionsBuilder options) { if (!options.IsConfigured) { - options.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=ex_041_004_InMemory.Nounours.mdf;Trusted_Connection=True;"); + options.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=ex_041_004_TestingInMemory.Nounours.mdf;Trusted_Connection=True;"); } } ``` @@ -67,7 +80,83 @@ public NounoursContext(DbContextOptions options) ``` La classe ```NounoursContext``` peut donc s'utiliser comme précédemment, sans changement, et la base *SqlServer* sera utilisée ; ou alors, on pourra injecter un autre fournisseur. -## Configuration du test unitaire +### application console .NET Core ex_041_004_ConsoleTests_w_SqlServer + +L'application console .NET Core **ex_041_004_ConsoleTests_w_SqlServer** fait référence à la bibliothèque .NET Standard précédente pour pouvoir consommer ```Nounours``` et ```NounoursContext```. +Sa seule classe est donc ```Program``` et peut donc être codée de la manière suivante : +```csharp +using System; +using ex_041_004_TestingInMemory; + +namespace ex_041_004_ConsoleTests_w_SqlServer +{ + class Program + { + static void Main(string[] args) + { + Nounours chewie = new Nounours { Nom = "Chewbacca" }; + Nounours yoda = new Nounours { Nom = "Yoda" }; + Nounours ewok = new Nounours { Nom = "Ewok" }; + + using (var context = new NounoursContext()) + { + // Crée des nounours et les insère dans la base + Console.WriteLine("Creates and inserts new Nounours"); + context.Add(chewie); + context.Add(yoda); + context.Add(ewok); + context.SaveChanges(); + } + } + } +} +``` + +Pour tester cette application, n'oubliez pas les commandes comme présentées dans l'exemple ex_041_001 : pour générer l'exemple, il vous faut d'abord préparer les migrations et les tables. + * Ouvrez la *Console du Gestionnaire de package*, pour cela, dirigez-vous dans le menu *Outils*, puis *Gestionnaire de package NuGet*, puis *Console du Gestionnaire de package*. + * Dans la console que vous venez d'ouvrir, déplacez-vous dans le dossier du projet .NET Core, ici : +``` +cd .\p08_BDD_EntityFramework\ex_041_004_ConsoleTests_w_SqlServer +``` + *Note*: + si vous n'avez pas installé correctement EntityFrameworkCore, il vous faudra peut-être utiliser également : + +```dotnet tool install --global dotnet-ef``` + + * Migration : comme la classe dérivant de ```DbContext``` n'est pas dans l'application Console, nous devons préciser dans quel projet elle se trouve en ajoutant ```--project ../ex_041_004_TestingInMemory```. +``` +dotnet ef migrations add migration_ex_041_004 --project ../ex_041_004_TestingInMemory +``` + * Création de la table : comme pour la migration, il faut préciser dans quel projet se trouve l'instance de ```DbContext```. +``` +dotnet ef database update --project ../ex_041_004_TestingInMemory +``` + * Génération et exécution +Vous pouvez maintenant générer et exécuter l'exemple **ex_041_004_ConsoleTests_w_SqlServer**. + + * Comment vérifier le contenu des bases de données SQL Server et SQLite ? +Vous pouvez vérifier le contenu de votre base en utilisant l'*Explorateur d'objets SQL Server*. +* Pour cela, allez dans le menu *Affichage* puis *Explorateur d'objets SQL Server*. + + +* Déployez dans l'*Explorateur d'objets SQL Server* : + * *SQL Server*, + * puis *(localdb)\MSSQLLocalDB ...*, + * puis *Bases de données* + * puis celle portant le nom de votre migration, dans mon cas : *ex_041_004_TestingInMemory.Nounours.mdf* + * puis *Tables* + * Faites un clic droit sur la table *dbo.Nounours* puis choisissez *Afficher les données* + + + * Vous devriez maintenant pouvoir voir les données suivantes dans le tableau : + + |Id |Nom + |---|--- + |1|Chewbacca + |2|Yoda + |3|Ewok + +### Configuration des tests unitaires avec InMemory Le test unitaire est de type *xUnit* et va permettre d'injecter le fournisseur **InMemory**. * On crée un nouveau projet de tests unitaires (*xUnit*) * On lui ajoute le package NuGet : *Microsoft.EntityFrameworkCore.InMemory* @@ -90,7 +179,7 @@ namespace ex_041_004_UnitTests .UseInMemoryDatabase(databaseName: "Add_Test_database") .Options; - // runs the test against one instance of the context + //prepares the database with one instance of the context using (var context = new NounoursContext(options)) { Nounours chewie = new Nounours { Nom = "Chewbacca" }; @@ -103,7 +192,7 @@ namespace ex_041_004_UnitTests context.SaveChanges(); } - // Use a separate instance of the context to verify correct data was saved to database + //prepares the database with one instance of the context using (var context = new NounoursContext(options)) { Assert.Equal(3, context.Nounours.Count()); @@ -127,7 +216,7 @@ Assert.Equal("Chewbacca", context.Nounours.First().Nom); Notez que le choix du fournisseur est bien fait au démarrage du test avec la création du ```DbContextOptionsBuilder``` : ```csharp var options = new DbContextOptionsBuilder() - .UseInMemoryDatabase(databaseName: "Add_writes_to_database") + .UseInMemoryDatabase(databaseName: "Add_Test_database") .Options; ``` et que l'injection est effectuée plus bas : @@ -147,7 +236,7 @@ public void Modify_Test() .UseInMemoryDatabase(databaseName: "Modify_Test_database") .Options; - // Run the test against one instance of the context + //prepares the database with one instance of the context using (var context = new NounoursContext(options)) { Nounours chewie = new Nounours { Nom = "Chewbacca" }; @@ -160,25 +249,25 @@ public void Modify_Test() context.SaveChanges(); } - // Use a separate instance of the context to verify correct data was saved to database + //prepares the database with one instance of the context using (var context = new NounoursContext(options)) { string nameToFind = "ew"; - Assert.Equal(2, context.Nounours.Where(n => n.Nom.Contains(nameToFind, System.StringComparison.CurrentCultureIgnoreCase)).Count()); + Assert.Equal(2, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count()); nameToFind = "ewo"; - Assert.Equal(1, context.Nounours.Where(n => n.Nom.Contains(nameToFind, System.StringComparison.CurrentCultureIgnoreCase)).Count()); - var ewok = context.Nounours.Where(n => n.Nom.Contains(nameToFind, System.StringComparison.CurrentCultureIgnoreCase)).First(); + Assert.Equal(1, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count()); + var ewok = context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).First(); ewok.Nom = "Wicket"; context.SaveChanges(); } - // Use a separate instance of the context to verify correct data was saved to database + //prepares the database with one instance of the context using (var context = new NounoursContext(options)) { string nameToFind = "ew"; - Assert.Equal(1, context.Nounours.Where(n => n.Nom.Contains(nameToFind, System.StringComparison.CurrentCultureIgnoreCase)).Count()); + Assert.Equal(1, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count()); nameToFind = "wick"; - Assert.Equal(1, context.Nounours.Where(n => n.Nom.Contains(nameToFind, System.StringComparison.CurrentCultureIgnoreCase)).Count()); + Assert.Equal(1, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count()); } } ``` @@ -186,16 +275,16 @@ Ce cas de test : * vérifie d'abord qu'il y a deux nounours dont le nom contient la chaîne "ew", ```csharp string nameToFind = "ew"; -Assert.Equal(2, context.Nounours.Where(n => n.Nom.Contains(nameToFind, System.StringComparison.CurrentCultureIgnoreCase)).Count()); +Assert.Equal(2, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count()); ``` * vérifie qu'il y a un nounours dont le nom contient la chaîne "ewo" ```csharp nameToFind = "ewo"; -Assert.Equal(1, context.Nounours.Where(n => n.Nom.Contains(nameToFind, System.StringComparison.CurrentCultureIgnoreCase)).Count()); +Assert.Equal(1, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count()); ``` * modifie le nom de ce nounours en "Wicket" ```csharp -var ewok = context.Nounours.Where(n => n.Nom.Contains(nameToFind, System.StringComparison.CurrentCultureIgnoreCase)).First(); +var ewok = context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).First(); ewok.Nom = "Wicket"; ``` * enregistre les changements @@ -205,14 +294,144 @@ context.SaveChanges(); * vérifie ensuite qu'il n'y a plus qu'un Nounours dont le nom contient la chaîne "ew" ```csharp string nameToFind = "ew"; -Assert.Equal(1, context.Nounours.Where(n => n.Nom.Contains(nameToFind, System.StringComparison.CurrentCultureIgnoreCase)).Count()); +Assert.Equal(1, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count()); ``` * vérifie qu'il y a un Nounours dont le nom contient la chaîne "wick" ```csharp nameToFind = "wick"; -Assert.Equal(1, context.Nounours.Where(n => n.Nom.Contains(nameToFind, System.StringComparison.CurrentCultureIgnoreCase)).Count()); +Assert.Equal(1, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count()); +``` + +### Configuration des tests unitaires avec SQLite in memory +Le projet se construit exactement da la même manière que le précédent à quelques exceptions près que voici. +* package NuGet : +à la place du NuGet *Microsoft.EntityFrameworkCore.InMemory*, il faut ajouter *Microsoft.EntityFrameworkCore.Sqlite* +* ouverture de la connexion : +au début des tests, il faut penser à ouvrir la connexion avec la base en mémoire SQLite qui doit rester ouverte durant tout le test. +```csharp +//connection must be opened to use In-memory database +var connection = new SqliteConnection("DataSource=:memory:"); +connection.Open(); +``` +* avant de commencer à traiter avec la base en mémoire, on peut vérifier qu'elle a bien été créée : +```csharp +//context.Database.OpenConnection(); +context.Database.EnsureCreated(); +``` +Au final, les tests ressemblent à : +```csharp +using ex_041_004_TestingInMemory; +using Microsoft.EntityFrameworkCore; +using System.Linq; +using Xunit; +using Microsoft.Data.Sqlite; + + +namespace ex_041_004_UnitTests_w_SQLiteInMemory +{ + public class NounoursDB_Tests + { + [Fact] + public void Add_Test() + { + //connection must be opened to use In-memory database + var connection = new SqliteConnection("DataSource=:memory:"); + connection.Open(); + + var options = new DbContextOptionsBuilder() + .UseSqlite(connection) + .Options; + + //prepares the database with one instance of the context + using (var context = new NounoursContext(options)) + { + //context.Database.OpenConnection(); + context.Database.EnsureCreated(); + + Nounours chewie = new Nounours { Nom = "Chewbacca" }; + Nounours yoda = new Nounours { Nom = "Yoda" }; + Nounours ewok = new Nounours { Nom = "Ewok" }; + + context.Nounours.Add(chewie); + context.Nounours.Add(yoda); + context.Nounours.Add(ewok); + context.SaveChanges(); + } + + //uses another instance of the context to do the tests + using (var context = new NounoursContext(options)) + { + context.Database.EnsureCreated(); + + Assert.Equal(3, context.Nounours.Count()); + Assert.Equal("Chewbacca", context.Nounours.First().Nom); + } + } + + [Fact] + public void Modify_Test() + { + //connection must be opened to use In-memory database + var connection = new SqliteConnection("DataSource=:memory:"); + connection.Open(); + + var options = new DbContextOptionsBuilder() + .UseSqlite(connection) + .Options; + + //prepares the database with one instance of the context + using (var context = new NounoursContext(options)) + { + //context.Database.OpenConnection(); + context.Database.EnsureCreated(); + + Nounours chewie = new Nounours { Nom = "Chewbacca" }; + Nounours yoda = new Nounours { Nom = "Yoda" }; + Nounours ewok = new Nounours { Nom = "Ewok" }; + + context.Nounours.Add(chewie); + context.Nounours.Add(yoda); + context.Nounours.Add(ewok); + context.SaveChanges(); + } + + //uses another instance of the context to do the tests + using (var context = new NounoursContext(options)) + { + context.Database.EnsureCreated(); + + string nameToFind = "ew"; + Assert.Equal(2, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count()); + nameToFind = "wo"; + Assert.Equal(1, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count()); + var ewok = context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).First(); + ewok.Nom = "Wicket"; + context.SaveChanges(); + } + + //uses another instance of the context to do the tests + using (var context = new NounoursContext(options)) + { + context.Database.EnsureCreated(); + + string nameToFind = "ew"; + Assert.Equal(1, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count()); + nameToFind = "wick"; + Assert.Equal(1, context.Nounours.Where(n => n.Nom.ToLower().Contains(nameToFind)).Count()); + } + } + } +} ``` +### exécution des tests unitaires +Vous pouvez maintenant exécuter les tests unitaires via l'*Eplorateur de tests*. +* Dans le menu *Test*, choisissez *Explorateur de tests* + +* Cliquez sur "Exécuter tous les tests" + +* Observez le bon fonctionnement + --- Copyright © 2019-2020 Marc Chevaldonné \ No newline at end of file diff --git a/p08_BDD_EntityFramework/ex_041_004_TestingInMemory/readmefiles/readme_01.png b/p08_BDD_EntityFramework/ex_041_004_TestingInMemory/readmefiles/readme_01.png new file mode 100644 index 0000000..a684d38 Binary files /dev/null and b/p08_BDD_EntityFramework/ex_041_004_TestingInMemory/readmefiles/readme_01.png differ diff --git a/p08_BDD_EntityFramework/ex_041_004_TestingInMemory/readmefiles/readme_02.png b/p08_BDD_EntityFramework/ex_041_004_TestingInMemory/readmefiles/readme_02.png new file mode 100644 index 0000000..f53e54d Binary files /dev/null and b/p08_BDD_EntityFramework/ex_041_004_TestingInMemory/readmefiles/readme_02.png differ