Unit Test-get rid of dependencies and unit test get rid of Dependencies

Source: Internet
Author: User

Unit Test-get rid of dependencies and unit test get rid of Dependencies

In unit testing, an object is often subject to another object that you cannot control. Therefore, you must replace it with an object that you can control to get rid of it.

1: Why do we need to break dependency 1.1: slow running speed

This method depends on the database if the user ID is used to determine whether the user exists. In this way, it becomes an integration test. If a large number of tests are performed, the speed will be slow.

1.2: configuration required

Because the database is dependent, database-related files are configured.

1.3: a lot of content is tested at a time, and the error message cannot be located.

This method may be wrong because the input user ID is empty, the user ID may not exist, or the database connection may be disconnected, so that we cannot start our unit test.

2: stubs

An external dependency refers to an object in the system. The tested code interacts with this object, but you cannot control this object. It is similar to the cooperation between the front-end engineer and the back-end engineer. If the front-end engineer needs to wait for the data returned from the back-end to process the data, the back-end engineer is an external dependency item. Because he cannot control the background code

Definition: stub is a controllable alternative to a dependency (or co-author) in the system (that is, you can find an object to replace an object that you cannot control ). By using a stub, you do not need to directly process the dependency when testing the code. (To put it bluntly, you define an object to replace an object that you cannot control)

3: refactor the code design to improve code testability 3.1: extract the interface to replace the underlying layer

In fact, at the underlying layer, we should use interfaces. In this way, the upper-Layer Code depends on interfaces rather than specific objects to make the project more scalable. Of course, here we do things for better testing.

Extract an interface from the underlying method

public interface IUser
     {
         /// <summary>
         /// check if the user exists
         /// </ summary>
         /// <param name = "userId"> user name </ param>
         /// <returns> </ returns>
         bool IsExist (string userId);
     }

Classes for accessing the underlying database

public class User: IUser
     {
         public bool IsExist (string userId)
         {
             // Query from the database
             // return true if any
         }
     }

Unit of work to be tested

public bool IsExistUser(string userId)
        {            
             var user = new User();
             return user.IsExist(userId);
        }          

A controllable stub

public class FackUser:IUser
    {
        public bool WillBevalid = false;        
        public bool IsExist(string userId)
        {
            return WillBevalid;    
        }
    }

The injection of the stubs starts below.

4: dependency injection (inject a pseudo implementation into the tested Unit)

4.1: Construct parameter Injection

As the name implies, the pseudo object is injected during parameter construction during instantiation.

Now we need to modify the above class as follows:

Tested class

public class UserBll
    {
        private readonly IUser _user;
        public UserBll(IUser user)
        {
            this._user = user;
        }
        public bool IsExistUser(string userId)
        {            
            return _user.IsExist(userId);                        
        }       
  }

Test code

[Test]
         public void IsExistUser_ExistUser_ReturnsTrue ()
         {
             var fackUser = new FackUser {WillBevalid = true};
             var user = new UserBll (fackUser); // Inject pseudo object
             bool result = user.IsExistUser ("1");
             Assert.IsTrue (result);
         }

Constructor injection: constructor injection is simple, intuitive, and easy to understand. However, the problem is that when you rely more and more on the constructor, more and more parameters will become difficult to maintain.

Use Case: The design of the analogy api is that some users are constructor with parameters.

4.2: Use properties (get; set) to inject pseudo objects

Tested class

public class UserBll
     {
         public IUser User {get; set;}

         public UserBll (IUser user)
         {
             User = new User (); // The default case executes the normal object
         }

         public bool IsExistUser (string userId)
         {
             return User.IsExist (userId);
         }
} 

Code Testing

[Test]
         public void IsGetName_NormalGetName_ReturnsTrue () {
             var fackUser = new FackUser {WillBevalid = true};
             var user = new UserBll {User = fackUser}; // Attribute injection
             bool result = user.IsExistUser ("1");
             Assert.IsTrue (result);
         }

Summary of property injection: it is similar to constructor injection but easier to read and write.

When to use property injection: to indicate which one of the dependencies of the test class is optional, or to test it, you can use the default dependency to inject the property.

4.3: forge a member (pseudo object) in the factory)

Let's look at the factory class first.

[Test]
         public void IsGetName_NormalGetName_ReturnsTrue () {
             var fackUser = new FackUser {WillBevalid = true};
             var user = new UserBll {User = fackUser}; // Attribute injection
             bool result = user.IsExistUser ("1");
             Assert.IsTrue (result);
         }

Tested class

public class UserBll
    {        
        public bool IsExistUser(string userId)
        {                            
                var userFactory = new UserFactory();                                          
                return userFactory.Create().IsExist(userId);            
        }

Test code

  [Test]
        public void IsGetName_NormalGetName_ReturnsTrue() {
            var fackUser = new FackUser { WillBevalid = true };
            var userFactory = new UserFactory();
            userFactory.SetUser(fackUser);//设置自己要注入的伪对象
            bool result = new UserBll().IsExistUser("1");
            Assert.IsTrue(result);
        }

Summary of the spoofing method: This method is very simple. Add a pseudo-dependent item to the factory that you want to control. The tested Code does not change everything as it is.

This method is obviously better than the first two. It is equivalent to adding a factory buffer. Here we can do some logic processing.

4.4: extraction and re-creation

Steps for using this method:

In the tested class:

  • Add a method that returns a real virtual factory;
  • Use factory methods in normal code

In the test project:

  • Create a new class
  • Declare this new class to inherit the class to be tested
  • Create a public field for the interface type you want to replace (no attribute is required)
  • Rewrite Virtual Methods
  • Return public fields

In the test code:

  • Create a stub instance. The interface required by this stub
  • Create an instance of the new derived class instead of the test class

Forge a factory Method[Test]
         public void IsGetName_NormalGetName_ReturnsTrue () {
             var fackUser = new FackUser {WillBevalid = true};
             var userFactory = new UserFactory ();
             userFactory.SetUser (fackUser); // Set the pseudo object to be injected
             bool result = new UserBll (). IsExistUser ("1");
             Assert.IsTrue (result);
         }


Create a new class and integrate the tested class

[Test]
         public void IsGetName_NormalGetName_ReturnsTrue () {
             var fackUser = new FackUser {WillBevalid = true};
             var userFactory = new UserFactory ();
             userFactory.SetUser (fackUser); // Set the pseudo object to be injected
             bool result = new UserBll (). IsExistUser ("1");
             Assert.IsTrue (result);
         }

Test code:

[Test]
         public void IsGetName_NormalGetName_ReturnsTrue () {
             var fackUser = new FackUser {WillBevalid = true}; // Stub instance
             var testUser = new TestUser (fackUser); // Inject pseudo object (newly derived class)
             bool result = testUser.IsExistUser ("1");
             Assert.IsTrue (result);
         }

Summary of extraction and rewrite injection: fewer interfaces are written, and the code is easier to replace. I think the best way to do this is to leave a path, not just for testing. If you find this code is not good one day, you can simply add a new replacement at the underlying layer without affecting the original code.

When to use: It is especially useful when you want to simulate the desired value when calling external dependencies.

4.5: Restructuring technology variant Injection

First look at the tested class

public class UserBll
    {        
        public bool IsExistUser(string userId)
        {
return UserManager(userId);         
        }

        protected virtual bool UserManager(string userId)
        {
            IUser user = new User();
            return user.IsExist(userId);
        }        
    }

Create a new class and integrate the tested class

public class TestUser : UserBll
    {    
        public bool IsSupported;
        protected bool IsGetUserName(string userId) {
            return IsSupported;
        }
    }

Test class

public void IsGetName_NormalGetName_ReturnsTrue() {
            var testUser = new TestUser { IsSupported = true };
            bool result = testUser.IsExistUser("1");
            Assert.IsTrue(result);
        }

Summary: This is very similar to the previous method, but it is more thorough. This method is simpler. Do not add many constructors, setup methods, or factory classes. However, it does not conform to the encapsulation principles in object-oriented systems. This exposes things the user does not want to see.

Flexible use of various dependency injection. I personally think the last three are good.

 


Related Article

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.