Overview
Writing unit tests helps improve the quality of your code, and when you write unit tests, some features may depend on other code (such as calling other components).
Usually we just want to test the functionality itself and not want to test the code it depends on.
Why is it?
The goal of unit testing is to verify that the functionality is correct, but the code on which the functionality depends is outside the scope of functionality, which may be external components that the unit test cannot verify the accuracy of the external components.
When a unit test fails with an error calling "dependent code", it affects the judgment of the test result, and we cannot determine whether the function itself is correct.
Perhaps the function is correct, but the unit test will still be considered a failure when invoking a dependent code error.
If you want to test these dependent code, we should write the unit tests for the code separately.
How to solve?
"Dependent code" has become a roadblock for us to write such unit tests, and we can solve this problem in 2 ways:
1. Mock-dependent code
2. Decomposition dependencies
The power of a mock is beyond doubt, but the mock is not omnipotent, it is limited, and we cannot mock static classes in unit tests.
However, this problem can be solved by "decomposing dependencies", and this article will demonstrate these 2 ways through an example.
Example before refactoring
This code describes a scene-"breeding animals", which contains 2 classes:
Animalfeedingservice (animal husbandry Service), as well as static class feeder (breeder).
Animalfeedingservice describes the "feeding" behavior, and the feeder class describes the "supplemental food" behavior.
In raising animals, if the animal's food bowl is empty, the breeder needs to replenish the food.
<summary>///Animal Husbandry Service//</summary>public class animalfeedingservice{ private bool Foodbowlempty { Get Set } public void Feed () { if (foodbowlempty) Feeder.replenishfood ();} } <summary>///breeder//</summary>public static class feeder{//<summary>/// Supplemental food// </summary> public static void Replenishfood () { }}
Unit test code (based on Xunit and rhino.mocks framework)
public class animalfeedingservicetests{ [Fact] public void Testfeed () { Animalfeedingservice Service = new Animalfeedingservice (); Test feed () method service. Feed (); }}
Since feeder is a static class, we cannot mock the feeder class when writing unit tests for Animalfeedingservice, and we do not want to validate the functionality of the feeder class in this unit test.
If there is an error when calling Feeder.replenishfood (), the execution of this unit test is unsuccessful.
At the same time, the correctness of the Feed () method cannot be verified.
After refactoring
In order to be able to remove the dependency on the feeder class in the unit test, we can add the wrapper interface Ifeederservice to the feeder class, and then let Animalfeedingservice rely on the wrapper interface. So in the Animalfeedingservicetest class, we don't have to think about the feeder class.
Display Code
<summary>///Animal Husbandry Service//</summary>public class animalfeedingservice{ private bool Foodbowlempty { Get Set Public Ifeederservice Feederservice {get; set;} Public Animalfeedingservice (Ifeederservice feederservice) {this . Feederservice = Feederservice; } public void Feed () { if (foodbowlempty) Feederservice.replenishfood ();} } <summary>///Feeding Service Interface ///</summary>public interface ifeederservice{void Replenishfood ();} <summary>///Feeding Service Implementation//</summary>public class feederservice:ifeederservice{public void Replenishfood () { feeder.replenishfood (); }} <summary>///breeder///</summary>public static class feeder{public static void Replenishfood () { }}
Unit test code (based on Xunit and rhino.mocks framework)
public class animalfeedingservicetests{ [Fact] public void Testfeed () { // Mockrepository var mocks = new Mockrepository () based on the rhino.mocks framework ; Mock Ifeederservice var feederservice = mocks. Dynamicmock<ifeederservice> (); Animalfeedingservice service = new Animalfeedingservice (feederservice); Test feed () method service. Feed (); }}
After the reconstructed animalfeedingservicetests, because Ifeederservice is a mock, Ifeederservice's Replenishfood () method is not called at all, So feeder's Replenishfood () method is not called.
The Feeder.replenishfood () method is ignored when invoking the feed () method, which allows us to focus more on the logic of the Feed () method itself.
Packaging mode
The above code, in fact, is a simple packaging mode (Wrapper pattern), packaging mode is not considered a specific design mode, because in the adapter mode (Adapter pattern), Decorative mode (Decorator pattern), Proxy mode ( Proxy pattern), façade mode (facade pattern) and so on have used the packaging class, these design patterns I am not specifically described here. Typically, wrapper classes are used to encapsulate other classes or component, and the wrapper class can provide convenience for upper-level calls and make the use of the underlying class or component more secure.
The UML structure of the packaging pattern is broadly as follows. It consists of 3 parts: Caller (Client), wrapper (Wrapper), Component (Component)
To talk about the concept of "decomposition dependency", according to this structure, we can clearly know: in order to relieve the dependence between the client and component, we added a wrapper on the client and component, so that the client relies on wrapper. Please note that since we are more inclined to interface-oriented programming, this wrapper is usually a wrapper interface. To give a common example, when we are writing some APIs, we only need to expose the API specifications (name, input and output parameters) to the client via wrapper, and the internal implementation of the API is isolated by wrapper.
Drink Remodeling Series [9]--decomposition dependent