Sometimes it's not that we don't want to do unit tests, but the code is really not testing ....
For example, if a car does not complete the test after it has been produced, no one dares to drive it. The code is the same, if the project fails to do the test, then the customer will not dare to use it, even if used, will encounter "car accident."
Why to test/test the benefits
- It can detect bugs early and fix bugs
- It will save the total cost of developing and maintaining a software. In fact, the cost of maintaining the software is much greater than the cost of developing it. Writing unit tests at the time of development does add some cost, but in the long run these tests will reduce the overall cost of the software in terms of maintenance.
- It will encourage developers to improve their designs. If you write a test or write a test code at the time of development, developers will have to think carefully about the problem to be solved, so they will write a better design without thinking about how to test the code.
- Equivalent to self-documenting. Because all the tests are all expected behavior of the software being developed.
- Increase confidence and remove fear. Sometimes after modifying the code, we worry about whether this is damaging the existing functionality, and if the unit tests cover the important functions of the software, then as long as the tests pass, it is essential to be confident that the functionality is not compromised.
The test can be divided into many classes from different angles. We should first ensure that good unit testing can be done well, as long as unit testing can be done well, then other tests should be done well.
Why write code that is easy to test
Let me elaborate:
When it comes to software testing, articles on the internet often cite this example of building a car, so I'll take the example of a car to illustrate the problem.
Suppose we need to design and produce a car, there may be two ways:
The first is the design of the car into a complex whole , all the necessary parts are welded together, it can be said that it only a large part, is the car itself. The good thing about this is that we don't have to spend so much time and energy making spare parts such as engines, tires, windows and so on. It is possible to reduce the design and production costs of the car. But if the car is used for a long time, considering after-sales and maintenance, then the cost will certainly be very high.
If the car is broken, we cannot detect where it went wrong, because it is a whole, unable to isolate a part of the test; Even if we know where there is a problem, we cannot replace the damaged part, because it is still a whole ...
The second way is the right way, we use replaceable parts for design and production, which facilitates testing and after-sales maintenance . Because every part in the car can be replaced, it can be taken out separately for testing. If the car does not start, check each part and replace the faulty part instead of the whole car in the same way as in the first one.
It is clear that the normal car manufacturer is the second way to use it because it has testability and maintainability .
Software development This field is similar to designing a car, you can develop the software in the same way as in the first, or you can develop the software in the same way as in the second.
In reality, too many developers use the first way to put a lot of code and functionality together. In fact, developers should use the second way to design and write code, even though it may take more time and effort in the early stages of development.
Sometimes it's not the developer who doesn't want to take the second approach, but it takes a lot of effort to find out that the code that's written is still not good for unit testing, so the real problem is not knowing how to write code that's easy to test.
What kind of code is easy to test
Or a car example, if we suspect that the car's battery is broken, then the first way to create a car can not be carried out on its "battery" for a separate test, because it is welded together, there is no use to detect the Plug and so on; The second way to build the car can be taken out of the battery, and we use the voltage meter and other special equipment in the case of isolation to detect it.
The second way the isolation test is possible is because it uses replaceable parts, that is, the parts can be taken down.
In professional terms it is the second way that there is seam (seam). What is seam (seam) in the software? A seam is where you can replace a behavior in a program without having to modify it in this place. Or you can have your code remove the dependencies and create a place where you can isolate the test object ..... I may explain not understand, look at the picture:
The dotted line is the seam.
Because of the presence of seams, we can conduct isolation tests:
Replace the calling class and the dependency with the test Fixture and the test double , respectively.
The first method of the software will not be able to disassemble the code for testing, because the dependency cannot be replaced, unable to access the test environment, that is, the isolation test can not be done.
Why is the code unable to isolate the test?
The code that cannot be tested has some features:
- The new keyword . If the new keyword appears in this part of the code, which means that an instance of an external resource or a more complex type is created within a constructor or method, then testing can be difficult. Instead, the approach should be to rely on injection.
- static method/property invocation . A static method creates a tight coupling for its caller and the class in which it is called. There is no problem with using these methods like Math.min (), String.Join (), but if you use DateTime.Now, Console.Write () Then there may be a problem. At this point you may need to use a wrapper class.
- Single stereoscopic Singleton. The essence of Singleton is the shared state. However, in order to isolate the test, it is best to avoid using singleton. If you do need to use it, you can test it with a non-singleton alias, of course, through dependency injection.
- globally shared state , this should be understood
- references to third-party frameworks or external resources . Once such a reference is made, isolation testing is not possible. What we need to do is to abstract these things and ignore the details and only care about the specific results under certain conditions.
How to create a gap
- decoupling the dependencies . In C #, we do this by programming interfaces rather than implementations.
- Dependency Injection . The main use is to inject the constructor function.
To do these two points, we can use test double to replace the dependency and inject it into the tested class for isolation testing.
Example
Here's an example that's hard to test, and it's not perfect to show all the features of the non-testable code, but it also contains at least two features:
First of all, its dependencies are new, and these dependencies depend on the database, so we need to know the specific data content in the database when testing. The result is that the test is difficult to complete.
Second, there is a third-party mapper.map () static method, which may be tested and has no side effects, but it may not be. And it creates a tight coupling between the Productcontrollerhard and the Mapper class.
For the first question, I want to know how to deal with, is to use the interface. I will not introduce more.
For the second problem, the use of static methods results in tight coupling. If this static method is our own method of writing, we can reconstruct it and turn it into an instance method. But if it comes from a third-party library, and the third-party library does not provide a version that can rely on injection, then we can write a wrapper class (wrapper) to wrap the method:
But since this mapper comes from the AutoMapper library, this library provides a imapper interface, so you can use Imapper for dependency injection.
The code that can be tested should be as follows:
. NET Core TDD prequel: Writing code that is easy to test--stitch