Preface: This article is for those who have been writing unit tests for some time and have further pursuit of the rationality and validity of unit tests. Testing is an essential part of programming. As the initial test, the unit test is also the smallest granularity test, which is very critical. Here are some of my insights and practices on Java unit testing.

1. Choose a reasonable testing framework

JUnit tests are divided into a number of scenarios:

  • No unit tests called by third-party interfaces
  • Method to invoke unit tests for third-party interfaces
  • Static methods are used in the

The first of these is best solved by simply introducing JUnit dependencies. In the second scenario, the most common handling methods are piling and mocking, both of which simulate/generate a called object and test methods that need to call a third-party interface. I prefer mockito for mock testing, and the annotations and methods of a set of tools make unit tests cleaner and more readable to write. For method unit tests that invoke static methods, PowerMock can be used.

2. Enumeration of input use cases for different scenarios

In code, we usually design our test cases by considering the input of different scenarios:

  • The invoked interface has an expected exception
  • The invoked interface has an unexpected exception (outside the convention), known as a bottom-pocket situation
  • Incorrect arguments to an input function
  • An input function whose parameters are out of bounds (e.g. the input list object has a maximum of 100 but is actually called with 101)
  • Tests the correctness of arguments when calling third-party services in a function

The above scenarios are well understood except for the last one, especially the case of parameter out of bounds (I often encounter in development), which is often ignored when we call the agreement with the front-end or background other services. It is necessary to consider the handling of the case of parameter out of bounds. If neither the definer nor the user of the interface takes this into account, then when the boundary is actually crossed, the default handling of both parties (which they think should be done) may be inconsistent, resulting in bugs.

3. Test the number of times to be tuned in the function/the correctness of parameters

Generally writing unit tests will ignore the importance of this part, write call times, called third party function input validation can effectively avoid a lot of bugs. For example, use the eq argument to verify the input parameter of a function during a mock, Assert. Fail to ensure that the step is not executed where it is not needed, and in test cases where exceptions cannot be thrown. Verify is recommended for checking the number of calls and parameter conditions. In the code I’ve reviewed, there are some people who don’t test functions that return type void, which is actually a pitfall. The return type void is suitable for verifying third-party interfaces called in functions and using EQ to verify parameters. For example, in a service where you process a batch of data, you end up calling a save method that returns void, and the whole method returns void. Verify: void void void void void void void void void void void void void void void void void void void void void

Unit test coverage

For example, our team’s requirement for unit test coverage is 90%, which is just an observation indicator. A higher coverage rate does not necessarily mean that the test is done, but a lower coverage rate definitely means that the test is not in place and there are problems. For example, if you have a unit test that covers a branch path but does not verify and assert, the test fails. Such unit tests do not guarantee that the unit is correct. For example, if you fix a bug and find that the test code passed without any changes and was covered, then your previous tests were not sufficient. Even if the coverage was achieved, you still need to supplement the test cases to ensure the effectiveness of the unit tests.

5. Specification of use cases for unit tests

Code is meant to be seen, and so is test code, and effective test code comments make it easier for anyone reading or maintaining the code to understand. It is recommended to add some comments before the test cases of complex test scenarios. The test functions are usually named testHelloWorld_httpReturnError, and then add Java Doc comments above the functions, such as the scenario where the HTTP status code returns 400. There is also the test code line should not be too long, if very long, it is recommended to extract the corresponding private function, so as to make the test logic more clear.

Test cases

The input to the testCase is typically extracted as a setUp function that mocks the members and returns the pre-global Settings for all test function launches, You can use @before or @beforeeach. It is customary to call setUp within each function to isolate each test case. If the data string of a test case is too long, you are advised to use a file to read the data.

7.TDD test driven development

Because my team leader is an expert in the field of testing and has taught TDD courses for many times in the company, I also have certain feelings and understanding of TDD under his influence. The Chinese name for TDD is test-driven development, which means to have test cases first and then develop them accordingly. Each time after writing test cases, determine the determined input and output, and then write development code, and then gradually improve the interface under the continuous improvement of test cases. This is a departure from the idea that we used to develop and test, and one of the big problems with development and test is that the branching case that you’ve been thinking about, when you write test cases, is constrained and some scenarios are not taken into account. If we write test cases first and then iterate, we are closer to describing a requirement step by step, constantly refining the details of the requirement, so that it is not easy to miss details during the development of the test. The benefit of this development approach is that the bug rate of the code is greatly reduced, and the change in thinking makes it less error-prone to develop complex requirements. The downside is that it takes time to develop good test cases, it takes a lot of time to write tests and interfaces, and it takes time to get used to this development pattern. This method was tried out for a while when our department’s business was not busy. Later, the business was tight and several interfaces were needed, so this mode was no longer used. However, TDD is a development mode worth learning without affecting it.