Unit testing best Practices for domain-driven design (ii)

Source: Internet
Author: User
Tags xunit

Unit testing best Practices for domain-driven design (i)

After introducing the DDD case, we can finally get to the topic, the test code of this program is based on Xunit, the assertion component is fluentassertions, and similar components have shouldly. In addition, this case uses code contracts for. NET, and if this plugin is not installed, individual tests may not pass correctly.

In order to achieve the 2nd goal: "Try not to mock, including the database read part", I tried 3 kinds of scenarios:

1, test code to connect to the real database, only need to configure the test database in the test project in the Web. config, you can achieve this goal. However, after all, the program has many shortcomings, such as the need to keep the test library and the changes in the official library in sync, unit testing is not conducive to integration in CI, not conducive to team collaboration and so on.

2, using SQL Lite, but because SQL Lite itself does not support some LINQ expressions such as: Skip, there are also some features can not be consistent with SQL Server, eventually abandon the scenario.

3, the use of test components effort, can be very good with the Entity Framework use, because effort internal use of relational memory database nmemory, it is very suitable for running unit tests.

Of course, I'm still looking forward to Microsoft's ability to write unit test components based on EF.

I mentioned in the article "Field driven design in my eyes": Don't use database-specific technologies such as stored procedures and triggers. On the one hand these logic should be domain logic, on the other hand once used these techniques also means that we can not write tests for these logic.

First, the use of effort

In order to be able to use effort-based dbcontext in Castle, you need to register effort in Castle:

public class Fakedbcontextinstaller:iwindsorinstaller {Public Const string dbconnectionkey = "Fakedbconnection"        ;        Public Const string Fakebooklibrarydbcontextkey = "Fakebooklibrarydbcontext"; public void Install (IWindsorContainer container, Iconfigurationstore store) {container. Register (Component.for<dbconnection> (). Usingfactorymethod (dbconnectionfactory.createtransient). Named (Dbconnectionkey).            Lifestyleperwebrequest ()); Container. Register (Component.for<booklibrarydbcontext> (). DependsOn (Dependency.oncomponent (typeof (DbConnection), Dbconnectionkey)). Named (Fakebooklibrarydbcontextkey). Lifestyleperwebrequest ().        IsDefault ()); }    }

Ii. writing a scenario for testing

In order to reuse the test data, we need to write the scene (Scenario), the following file organization structure describes this intent:

To register the user as an example, design Registeruserscenario:

    public class Registeruserscenario:scenariobase    {public        usermodel Givingmodel {get; set;}        Public Guid Id {get; private set;}        Public Registeruserscenario (IWindsorContainer container): Base (Container)        {            Givingmodel = new Usermodel ()            {                Name = "Lilei",                Password = "Password1",                email = "[email protected]",            };        public override void Execute ()        {            var userservice = container.resolve<iuserservice> ();            Id = Userservice.register (Givingmodel);        }    }

The scene always provides the correct data, and the execution of such a scenario always results in the correct result:

        [Fact]        public void When_registeruserwithvalid_should_createuser ()        {            //arrange            var scenario=new Registeruserscenario (Container);            ACT            scenario. Execute ();            Assert            var user = Userservice.getuser (scenario. ID);            User. Name.should (). Be (scenario. Givingmodel.name);            User. Email.should (). Be (scenario. Givingmodel.email);        }

The method name of the test is important, and we will know what the test is doing after reading the method name.

In order to get the result of the failure, we need to rewrite the data in the scenario, such as the following test:

        [Fact]        public void When_registeruserwithemptyname_should_throwexception ()        {            //arrange            var scenario=new Registeruserscenario (Container)            {                Givingmodel = new Usermodel ()                {                    Name = string. Empty,                    email = "[email protected]",                    Password = "Password1"                }            };            ACT            scenario. Invoking (s = = S.execute ()). Shouldthrow<exception> ("Invalid username");        }

Third, based on the previous scene to write a new scene, so as to achieve the purpose of reusing data

For example, we need to write a "user login" test, first need to write Loginscenario

 public class Loginscenario:scenariobase {public string Email {get; set;}        public string Password {get; set;}        public bool Login {get; private set;}        Public Guid Id {get; private set;} Public Loginscenario (IWindsorContainer container): Base (Container) {var registerscenario=new registeru            Serscenario (container);            Registerscenario.execute ();            Id = registerscenario.id;            Email = RegisterScenario.GivingModel.Email;        Password = RegisterScenario.GivingModel.Password;            } public override void Execute () {var userservice = container.resolve<iuserservice> ();        Login=userservice.login (Email, Password); }    }

In the constructor of this scene, we execute the Registerscenario to achieve the purpose of reusing the data.

To write a test for user logon:

    public class Userlogintests:testbase {[Fact] public void When_loginwithinexistentemail_should_throwex                Ception () {//arrange var loginscenario=new loginscenario (Container) {            Email = "[Email protected]",}; Act loginscenario.invoking (s = = S.execute ()).       Shouldthrow<applicationserviceexception> ("No such user");            } [Fact] public void When_loginwithwrongpassword_should_returnfalse () {//arrange            var loginscenario=new loginscenario (Container) {Password = "Wrongpassword"};            Act Loginscenario.execute (); Assert loginScenario.Login.Should ().       Befalse ();            } [Fact] public void When_loginwithcorrectpassword_should_returntrue () {//arrange                    var Loginscenario = new Loginscenario (Container);   Act Loginscenario.execute (); Assert loginScenario.Login.Should ().        Betrue (); }    }

We always need to write new scenarios for new business logic, and new scenarios are always based on previously written scenarios, and any functionality of the entire system can be overwritten with real test code.

Since we have a separate scope open for each test in the test base class, the database will be dispose of at the end of each test. So every test, no matter how many times it runs, is the same effect. The disadvantage is that these tests do not run in parallel, xunit default to run in parallel for different test classes, and we disable the parallel run capability of Xunit by adding the same [Collection ("Integrationtests")] label on the test class.

Using this scheme to cover the system of Unit test, the developer will be confident that every time the code is submitted, the developer can ensure that all the unit tests are "passed".

High-quality unit testing not only ensures smooth operation of the system, but also an effective document, and when you have read the test cases for each scenario, you are basically able to be familiar with the business.

Close to the real unit test can also save you debug time, as long as you write the test pass, basic can ensure the reliability of the background code. In addition you can debug from these test code at any time, compared to the debug code from the front-end interface can save a lot of time, once and for all.

For more details please see Source: https://git.oschina.net/richieyangs/MvcTests.BestPractice.git

Unit testing best Practices for domain-driven design (ii)

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.