A common approach in .NET for testing applications that work with a database is dependency injection (Dependency Injection). It is proposed to separate the code working with the base from the main logic by creating an abstraction, which can later be replaced in tests. This is a very powerful and flexible approach, which nevertheless has some drawbacks - increasing complexity, separating logic, explosive growth in the number of types. More in the previous article Something is not right with testing in .NET (Java, etc.) or in Wiki / Dependency Injection .
There is a simpler approach, widespread in the world of dynamic languages. Instead of creating an abstraction that can be controlled in tests, this approach suggests controlling the base itself. The test framework provides a clean base for each test, and you can create a test script in it. It is easier and gives more confidence in the tests.
There is some kind of application for inventory accounting of goods. Goods can be moved between warehouses using movement documents. A method is needed that allows to obtain balances at a specified warehouse at a specified point in time.
public class ReminesService { RemineItem[] GetReminesFor(Storage storage, DateTime time) { ... } }
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" /> </configSections> <entityFramework> <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework"> <parameters> <parameter value="MSSQLLocalDB" /> </parameters> </defaultConnectionFactory> <providers> <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" /> </providers> </entityFramework> </configuration>
public class Countries : IModelFixture<Country> { public string TableName => "Countries"; public static Country Scotland => new Country { Id = 1, Name = "Scotland", IsDeleted = false }; public static Country USA => new Country { Id = 2, Name = "USA", IsDeleted = false }; }
IModelFixture<T>
interface. Instances of models are declared static to provide access to them from other fixtures and tests. You must explicitly specify the primary keys ( Id
) and follow their uniqueness within the same model. class Manufacturers : IModelFixture<Manufacturer> { public string TableName => "Manufacturers"; public static Manufacturer BrownForman => new Manufacturer { Id = 1, Name = "Brown-Forman", CountryId = Countries.USA.Id, IsDeleted = false }; public static Manufacturer TheEdringtonGroup => new Manufacturer { Id = 2, Name = "The Edrington Group", CountryId = Countries.Scotland.Id, IsDeleted = false }; }
public class Goods : IModelFixture<Good> { public string TableName => "Goods"; public static Good JackDaniels => new Good { Id = 1, Name = "Jack Daniels, 0.5l", ManufacturerId = Manufacturers.BrownForman.Id, IsDeleted = false }; public static Good FamousGrouseFinest => new Good { Id = 2, Name = "The Famous Grouse Finest, 0.5l", ManufacturerId = Manufacturers.TheEdringtonGroup.Id, IsDeleted = false }; }
private static Good _famousGrouseFinest = new Lazy<Good>(() => new Good { Id = 2, Name = "The Famous Grouse Finest, 0.5l", ManufacturerId = Manufacturers.TheEdringtonGroup.Id, IsDeleted = false }; public static Good FamousGrouseFinest => _famousGrouseFinest.Value;
HttpContext
can be set in asp.net). It is better to collect all these operations in one place and run before each test. We have called such a place - World. To prepare the database, you need to call the ResetWithFixtures
method and pass a list of initial fixtures there. static class World { public static void InitDatabase() { using (var context = new MyContext()) { var dbTest = new EFTestDatabase<MyContext>(context); dbTest.ResetWithFixtures( new Countries(), new Manufacturers(), new Goods() ); } } public static void InitContextWithUser() { HttpContext.Current = new HttpContext( new HttpRequest("", "http://your-domain.com", ""), new HttpResponse(new StringWriter()) ); HttpContext.Current.User = new GenericPrincipal( new GenericIdentity("root"), new string[0] ); } }
public class ModelBuilder { public MoveDocument CreateDocument(string time, Storage source, Storage dest) { var document = new MoveDocument { Number = "#", SourceStorageId = source.Id, DestStorageId = dest.Id, Time = ParseTime(time), IsDeleted = false }; using (var db = new MyContext()) { db.MoveDocuments.Add(document); db.SaveChanges(); } return document; } public MoveDocumentItem AddGood(MoveDocument document, Good good, decimal count) { var item = new MoveDocumentItem { MoveDocumentId = document.Id, GoodId = good.Id, Count = count }; using (var db = new MyContext()) { db.MoveDocumentItems.Add(item); db.SaveChanges(); } return item; } }
[SetUp] public void SetUp() { World.InitDatabase(); // } [Test] public void CalculateRemainsForMoveDocuments() { /// ARRANGE - var builder = new ModelBuilder(); // var doc1 = builder.CreateDocument("15.01.2016 10:00:00", Storages.MainStorage, Storages.RemoteStorage); builder.AddGood(doc1, Goods.JackDaniels, 10); builder.AddGood(doc1, Goods.FamousGrouseFinest, 15); // var doc2 = builder.CreateDocument("16.01.2016 20:00:00", Storages.RemoteStorage, Storages.MainStorage); builder.AddGood(doc2, Goods.FamousGrouseFinest, 7); /// ACT - var remains = RemainsService.GetRemainFor(Storages.RemoteStorage, new DateTime(2016, 02, 01)); /// ASSERT - Assert.AreEqual(2, remains.Count); Assert.AreEqual(10, remains.Single(x => x.GoodId == Goods.JackDaniels.Id).Count); Assert.AreEqual(8, remains.Single(x => x.GoodId == Goods.FamousGrouseFinest.Id).Count); }
Storages.MainStorage
, Goods.JackDaniels
, Goods.FamousGrouseFinest
, etc.Source: https://habr.com/ru/post/319568/
All Articles