I will not repeat the importance of unit testing. For more information about the basic concepts, see http://blog.csdn.net/linlinlinxi007/article/details/5294098.
Recently, I saw a PPT about ut best practice best practices. Based on my understanding of UT, I made a simple statement about the content.
First, when developing products, the production code and testing Code both refer to UT at the code level, that is, they focus on the underlying logic implementation, but they have different concerns,
Production Code focuses on:
- Meet business (functional) Requirements
- Meet non-functional (system) Requirements
Testing Code focuses on:
- Testing
- Documentation
- Specification
In addition to completing basic test tasks, the testing code of Ut is also very important. It makes the testing code automatically become a document and Annotation of the natural production code, it can be compiled and run, and it remains up to date. It is always synchronized with the code, eliminating the need to add comments and instructions in the previous production code, however, the Code is inconsistent with documentation due to improper updates after reconstruction. Of course, the prerequisite is testing.
Code must be highly readable and tested, which is a big challenge for programmers who write ut code.
This is because the focus of ut's testing code is different from the production code, which leads to many differences between ut's writing and our normal production code. The testing code has its own best practices.
5 stepsThe basic ut case should follow a five-step compiling policy, that is:
- Setup
- Prepare an input
- Call a method
- Check an output
- Tear down
For test cases unrelated to some resources, setup and tear down are not mandatory. Here, check an output does not mean that the invoke method must have a return value, but that the test result of your test case must be perceptible and can be a class field, it can also be alter string info.
FastThe running ut must be executed quickly. Ut is the most frequently executed in all tests. During code refactoring, new functions are run anytime, anywhere during the development process, and have extremely high regression testing, to maximize the efficiency of production code. If it runs too slowly, it will inevitably cause fewer developers to execute it. A smaller number of executions will indicate a higher possibility of bugs in functionality with a higher granularity. There is a basic data indicator for reference: single test: <200 mssmall suite: <10 Sall tests suite: <10 min
ConsistentMaintaining test case consistency in any environment at any time is very important in UT.
Date currentDate = new Date();random.nextInt(100);
This Code makes it impossible to implement a consistent because different values are generated during each test. The basic method to solve the consistency problem of testing code is mock and Di. The core of the solution is the application of dependency technology.
AtomicAtomicity. There are only two types of test case running results: Pass and fail. There is no third case, that is, partially successfully tests does not exist. If a test case fail is used, the entire suite is also fail. In the theory of window breaking, if there is a case fail in the suite, but it is partially passed, then there will be a second case of fail until the entire suite cannot be tested. So Atomicity is an important feature to ensure the reliability of ut.
Single responsibilitySingle responsibility. In design pattern, we often talk about the SRP of the production code. SRP is also required in ut test case. A test case is responsible for testing a behaviour, not a method. If a method has multiple behaviour, you need to write multiple tests for this method.
Case, for example:
public String getXXXX(boolean flag){ if(flag){ ...... }else{ .... }}public String getXXXX(boolean flag){ if(flag){ ...... }else{ .... }}
In the above Code, the getxxxx method has two behaviour, so two test cases need to be written. For example, note that different behavior needs different test cases for testing, rather than writing it into the same case for multiple judgments.
Test isolationThe independence between test cases, that is, there should be no association between different test cases. The success of one execution will not affect the execution result of another case. Regardless of the order in which the test case runs, the results are the same. In the UT test framework xunit series, it provides a good implementation of test isolation by focusing on 5 steps execution methods. It is essentially different from other higher-level testing frameworks such as testng.
Environment isolationEnvironment independence. The impact of all external environments, such as database access, network, and WebService call, should be eliminated in UT.
The solution is to use mock
Using mock to remove environment dependencies for production code has the following benefits:
- No additional test logic will be generated in the product code
- Because you do not need to construct environment-dependent objects, you can perform quick testing.
- Easy to write
- Easy to reuse
The well-known mock frameworks in the Java field include easymock, jmock, and mockito.
Classes isolationIn many cases, many classes in our product code are closely coupled and dependent on the implementation of specific classes, so that you cannot simulate and replace them with mock objects in test. For example:
public String getXXX(){ DB db = new DB(); ...... return db.getXXX();}
No matter how the test is written, such code depends on the implementation of the DB class during the test. Assume that the DB is a class that operates the database, this code is dependent on the external environment for a long time. Even using mock cannot be avoided. This type of code cannot be isolated from a specific class, so it is also called untested code (unit testing is not allowed here, and integration testing requires support from a specific environment ). Available solutions:
- Do not directly call the constructor of a specific class in the method. Instead, use the factory method or di for injection;
- Through interface programming, the top-down design achieves IOC.
Fully automaticAutomation: no manual intervention (no manual steps involved into testing) should occur during unit test execution ). At present, the UT framework integrated in most ides has corresponding Automatic Command Execution to facilitate the compilation and execution of testing code.
Self-descriptive
Self-Comments: unit test should be the development level documentation document of the product code at the development level, and also the method specification that is updated together with the product code ), although ut tests behavior, the most basic entity for testing is the method in the class. Therefore, to make ut testing code a document and description, you must make it easy to read and understand, including the names of variables, methods, and classes.
No conditional Logic
The conditional judgment logic should not appear in the test code. The best way to write ut test code is to avoid keywords such as: If and switch, which means that all input to be tested is correct and the behavior of the tested method is predictable, the execution results must also be strict. When the testing Code contains conditions that need to be judged by the if or switch conditions, it is proved that you need to test the if else branch separately through two independent test cases. The following test code:
testMethodBeforeOrAfter(){...if (before) {assertTrue(behaviour1);} else if (after) {assertTrue(behaviour2);} else { //nowassertTrue(behaviour3);}}
Should be replaced
testMethodBefore(){before = true;assertTrue(behaviour1);}
testMethodAfter(){after = true;assertTrue(behaviour2);}
testMethodNow(){before = false;after = false;assertTrue(behaviour3);}
No LoopsLoop should not appear. The compiled high-quality ut code should not contain loop keywords such as "for", "while", and "Do-while. There are three possible loops in the test code:
- Hundreds of repetitions
- A few repetitions
- Unknown number of repetitions
Hundreds of repetitionsThe appearance of this situation proves that the written test coding is too complicated. The same test case tests different behavior and violates the SRP principle. The first thing to do is to simplify the complexity, split and simplify it.
A few repetitionsOnly a few repetitions is acceptable, but we should try to make the logic of these loop code clear through non-circular code (type the Code explicitly without loops ). For example, you can extract the logic that needs to be cyclically into a common method and then call this method separately.
Unknown number of repetitions,The occurrence of this situation is representative and is unlikely to solve the loops loop by Code explicitly, because the writing personnel themselves do not know how the test runs to the first place. Only one problem can be proved. The test writing itself has a problem. modifying this problem also makes the inputs of your test strict, make sure that the test outputs corresponding to such inputs are strict.
No exception catching
Do not catch exceptions that are not expected in the test code. This practice has some double-off semantics in it (no exception catching indicates not that there is no exception capture, but not the exception you expect to appear, that is, you should not capture other exceptions when capturing exceptions ).
- Catch an exception only if it's expected
- Catch only expected type of an exception
- Catch expected exception and call "fail" Method
- Let other exceptions go uncatched
testThrowingMyException(){try {myMethod(param);fail("MyException expected");} catch(MyException ex) {//OK}}
AssertionsTo make an assertion. When judging test case, try not to use semantics-related keywords such as "=", "equals", but to use assertions.
- It is best to use the various asserted methods provided by the running test framework.
- Write a proprietary assertion method for some situations where the judgment conditions are complex.
- Use the assert method you wrote in test suite.
- The loops judgment is performed in the assertion method, rather than calling the assertion method in the loops for judgment.
Informative assertion messages
Assertions should contain more information. From the information prompted in the assertion failure, anyone can easily understand the fault that caused the assertion failure. The asserted information can provide a better description of the product code, and another aspect can also make the asserted failure information more clearly and clearly transmitted to the testing personnel.
No test logic in production code
The test logic should not appear in the product code. There are two levels. One is that the test code should be separated from the product code, another bottom layer is that the logic of the Product Code itself should not contain the logic branch compiled for testing. It is not difficult to do the first thing, but it is not easy (at least I think so) to completely isolate the product code layer without the logic branches written specifically for testing ).
- Separate test code from product code
- Do not create methods and member variables that only exist for testing.
- Uses di dependency injection to clear the code Seams
- The branch written for the test logic should be as few as possible in the product code (completely avoid nearly impossible)
Separation per type
Differentiate different types of tests, such as UT and integration tests. Do not confuse ut with other types of tests. The differences must be understood from the following aspects:
- Different purpose of execution
- Different frequency of execution
- Different time of execution
- Different action in case of failure
No matter what the test is, ut is always the most underlying things of interest. It is the most direct test to deal with product code, so ut is also the fastest way to detect bugs while weaving the security net. Therefore, writing good ut code has all the advantages and disadvantages in all aspects. Although many people think that writing test is very time-consuming and it is difficult to write test cases in some code, this only means that the environment dependency of your product code is too strong or the class dependency is too strong. On the one hand, you need to remove this dependency, and on the other, you cannot understand the real benefits brought by UT. This is especially important for products with frequent changes in business requirements or frequent restructuring.