📜 ⬆️ ⬇️

ASP.NET MVC Lesson E. Testing

The purpose of the lesson. Learn how to create tests for code. NUnit. The principle of TDD. Mock. Unit tests. Integrated Testing. Data generation

Testing, TDD principle, unit testing and more.

Testing for me personally is a topic of many thoughts. Need or don't need tests? But no one will argue that resources are needed for writing tests.
Consider two cases:
  1. We make the site, we show the customer, he sends a list of inaccuracies and additional requests, we cheerfully rule them and give the site to the customer, i.e. lay out on his server. No one goes to his server, the customer understands that the miracle did not happen and stops paying for hosting / domain. The site is dying. Do we need tests there?
  2. We make the site, show the customer, he sends the list of edits, we cheerfully edit them, launch the site. Six months later, the site has 300 unique per day, and this number is growing day by day. The customer constantly asks for new features, the old code begins to grow, and over time it is harder to maintain.



')
You see, the dilemma is such that the result of launching the site is unpredictable. I also had it so that the site made on my knee started up quite cheerfully, and it happened that the customer paid for the cool work, but did not even accept it. So, the tactics of behavior can be:


Consider the principle of TDD:
  1. Read the task and write a test that falls.
  2. Write any code that allows you to pass this test and the rest of the tests
  3. Do refactoring, i.e. remove duplicate code if necessary, but so that all tests pass


For example, the following correction was given:

We decided to add a tag field to the blog. Since we already have many blog entries, we decided to make this field optional. Since there is already an existing code, they did not use scaffolding. Manually checked the creation of a record - everything is ok. Run tests - everything is ok. But forgot to add a change to the field in UpdatePost (cache.Tags = instance.Tags;). When you change the old record, we add tags that are not actually saved. In this case, the tests were a great success. Life is pain!

Well, as you can see, we have violated the basic principle of TDD - first write a test that collapses, and then write the code that processes it. But (!) There is a second trick - we wrote a test that checks the creation of a blog entry with a tag. Of course, this was not compiled right away (i.e., the test did not pass), but we added something like “throw new NotImplementedException ()” to the ModelView. Everything compiled, the test is lit in red, we add this field with a tag, removing the exception, the test passes. All other tests also pass. The principles are respected, but the error remains.

What can I say, for every principle there is a situation where it will not work. Those. there is no such thing - they turned off their brains and drove them. One thing is for sure, and this is the main conclusion from these arguments:
tests should be written quickly
So what tasks we solve mainly on the site:


These are the main actions. How, for example, passes the registration:


Let's create for all this unit tests:

Let's start, perhaps.

Install NUnit

Follow the link http://sourceforge.net/projects/nunit/ and install NUnit. Also in VS we install the NUnit Test Adapter (well, to run the tests directly in VS).


Create a daddy of type Solution Folder Test and add the LessonProject.UnitTest project to it and install NUnit there:
Install-Package NUnit 


Create a UserControllerTest class in (/Test/Default/UserContoller.cs):
  [TestFixture] public class UserControllerTest { } 


So, the principle of writing method method names Method_Scenario_ExpectedBehavior:


For example, we check the first thing that we return View with the class UserView for registration:
  public void Register_GetView_ItsOkViewModelIsUserView() { Console.WriteLine("=====INIT======"); var controller = new UserController(); Console.WriteLine("======ACT======"); var result = controller.Register(); Console.WriteLine("====ASSERT====="); Assert.IsInstanceOf<ViewResult>(result); Assert.IsInstanceOf<UserView>(((ViewResult)result).Model); } 


So, all tests are divided into 3 parts Init-> Act-> Assert:


Open the Test Explorer tab:


If the NUnit adapter was installed correctly, then we will see our test method.
We start. The test is passed, you can go to open the champagne. Stoop. This is only the easiest part, but what about the part where we save something. In this case, we do not have a DB, our Repositary is null, zero, nothing.
We now study the class and methods for initialization (documentation). SetUpFixture - a class labeled with this attribute means that it has methods that perform initialization before tests and sweep after tests. This refers to the same namespace.


Create the UnitTestSetupFixture.cs class (/Setup/UnitTestSetupFixture.cs):
  [SetUpFixture] public class UnitTestSetupFixture { [SetUp] public void Setup() { Console.WriteLine("==============="); Console.WriteLine("=====START====="); Console.WriteLine("==============="); } [TearDown] public void TearDown() { Console.WriteLine("==============="); Console.WriteLine("=====BYE!======"); Console.WriteLine("==============="); } } 


Run and get:
===============
=====START=====
===============
=====INIT======
======ACT======
====ASSERT=====

===============
=====BYE!======
===============


Mock

So, Mock is a parody object. Those. for example, not a database, but something similar to a database. Mirage, in general. There is a Stub - a stub. Example stub method:
 public int GetRandom() { return 4; } 


But we will use mock:
 Install-Package Moq 


Determine what kind of environment we have, so that we initialize Mock objects for it. In principle, this is all that we once carried to the Ninject Kernel:


And here I will make a small note. We cannot render Config to mirages. Not in terms of what is completely impossible, but in terms of what is a bad idea. For example, we changed the letter pattern so that string.Format () throws a FormatException error. And in the test everything is fine, the test passes perfectly. And what is he responsible for after that? Never. So the original configuration file should be used. Let's leave it for later.

Regarding, iMapper - there is no need for this, we can safely use CommonMapper.
But first, let's initiate IKernel to work in test mode. In App_Start / NinjectWebCommon.cs, in the RegisterServices method, we specify how the interfaces should be implemented, and call it in bootstrapper.Initialize (CreateKernel). In the future, we turn on the receipt of the service via DependencyResolver.GetService (). So create a NinjectDependencyResolver (/Tools/NinjectDependencyResolver.cs):
 public class NinjectDependencyResolver : IDependencyResolver { private readonly IKernel _kernel; public NinjectDependencyResolver(IKernel kernel) { _kernel = kernel; } public object GetService(Type serviceType) { return _kernel.TryGet(serviceType); } public IEnumerable<object> GetServices(Type serviceType) { try { return _kernel.GetAll(serviceType); } catch (Exception) { return new List<object>(); } } } 


Add a method to SetUp (/Setup/UnitTestSetupFixture.cs):
 [SetUp] public virtual void Setup() { InitKernel(); } protected virtual IKernel InitKernel() { var kernel = new StandardKernel(); DependencyResolver.SetResolver(new NinjectDependencyResolver(kernel)); InitRepository(kernel); //  return kernel; } 


Create a MockRepository
(/Mock/Repository/MockRepository.cs):
 public partial class MockRepository : Mock<IRepository> { public MockRepository(MockBehavior mockBehavior = MockBehavior.Strict) : base(mockBehavior) { GenerateRoles(); GenerateLanguages(); GenerateUsers(); } } 

(/Mock/Repository/Entity/Language.cs)
 namespace LessonProject.UnitTest.Mock { public partial class MockRepository { public List<Language> Languages { get; set; } public void GenerateLanguages() { Languages = new List<Language>(); Languages.Add(new Language() { ID = 1, Code = "en", Name = "English" }); Languages.Add(new Language() { ID = 2, Code = "ru", Name = "" }); this.Setup(p => p.Languages).Returns(Languages.AsQueryable()); } } } 


(/Mock/Repository/Entity/Role.cs)
  public partial class MockRepository { public List<Role> Roles { get; set; } public void GenerateRoles() { Roles = new List<Role>(); Roles.Add(new Role() { ID = 1, Code = "admin", Name = "Administrator" }); this.Setup(p => p.Roles).Returns(Roles.AsQueryable()); } } 


(/Mock/Repository/Entity/User.cs)

 public partial class MockRepository { public List<User> Users { get; set; } public void GenerateUsers() { Users = new List<User>(); var admin = new User() { ID = 1, ActivatedDate = DateTime.Now, ActivatedLink = "", Email = "admin", FirstName = "", LastName = "", Password = "password", LastVisitDate = DateTime.Now, }; var role = Roles.First(p => p.Code == "admin"); var userRole = new UserRole() { User = admin, UserID = admin.ID, Role = role, RoleID = role.ID }; admin.UserRoles = new EntitySet<UserRole>() { userRole }; Users.Add(admin); Users.Add(new User() { ID = 2, ActivatedDate = DateTime.Now, ActivatedLink = "", Email = "chernikov@gmail.com", FirstName = "Andrey", LastName = "Chernikov", Password = "password2", LastVisitDate = DateTime.Now }); this.Setup(p => p.Users).Returns(Users.AsQueryable()); this.Setup(p => p.GetUser(It.IsAny<string>())).Returns((string email) => Users.FirstOrDefault(p => string.Compare(p.Email, email, 0) == 0)); this.Setup(p => p.Login(It.IsAny<string>(), It.IsAny<string>())).Returns((string email, string password) => Users.FirstOrDefault(p => string.Compare(p.Email, email, 0) == 0)); } } 


Consider how the mock works. He has such a good method, like Setup (again, a complete setup!) That works like this:
this.Setup( ).Returns( );

For example:
this.Setup(p => p.WillYou()).Returns(true);

Let us consider in more detail what other options might be:

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


All Articles