1. Introduction

Recently, I am working on the development of a project, which is released twice a week. All of these are normal, but there is a special project quality requirement. The unit test coverage rate of background code should reach 80%, and this index is a mandatory requirement for going online. At the time, I thought this requirement was funny. Internet development also needs to write single test? ! I consulted my partner in the XX Nail Business Department privately and learned that they have been implementing such A standard here for a period of time, and indeed improved the quality of RESEARCH and development, guaranteed the quality of code and service in the background, and reduced the delay of delivery due to quality problems (in fact, I strongly agree with it). Just take advantage of this opportunity to comb through the overall unit testing related knowledge content.

2. Knowledge of unit testing

2.1 Unit Testing

Unit testing refers to the inspection and verification of the smallest testable unit in software. In the context of unit testing, a unit refers to an implementation class in Java, a window or a menu in graphical software, etc.

Unit testing is the lowest level of testing activity that occurs during software development. Individual units of software are tested in isolation from the rest of the program, often associated with code review, static and dynamic analysis, etc.

  • Static analysis: Reading through project code to find errors or collect some metrics, usually using tools like CheckStyle/FindBugs.
  • Dynamic analysis: Provides information on execution tracking, time analysis, and coverage by observing the actions of software at runtime.

In today’s fast-to-win era, getting projects done faster means getting ahead of the market, so many people wonder: Why should we write unit tests when we can’t finish the features? In fact, writing unit tests does more good than harm:

  • Improve development speed, automate test execution, improve the efficiency of test code execution;
  • Improves the quality of software code by using small releases to integrate for easier debugging by implementers. It also introduces the concept of refactoring to make code cleaner and more resilient.
  • Improving the reliability of the system can ensure the correctness of the code to a certain extent.

At present, unit tests can be divided into class tests, functional tests and interface tests. The relationship between the benefits (improving the code quality of the project) is class tests < functional tests < interface tests, so everyone puts most of the focus on interface tests, and is it necessary to write class tests and functional tests?

                   

In terms of the project development iteration cycle, the more bugs are fixed in the later stage, the higher the cost will be. Therefore, unit testing shifts the testing process to the left, allowing developers to control the quality of design and implementation earlier, thus reducing the repair cost.

From the perspective of software system, business complexity will become higher and higher, and various associations and dependencies layer by layer add up to uncontrollable quality factors. Therefore, making the components and associations of the system in a stable state can greatly reduce the overlap of risk factors, thus enhancing the stability and quality of the whole system. Once you understand this, writing unit tests will become as natural a development habit as writing functional code.

2.2 Code Coverage

** Code coverage is an indirect measure of software quality by calculating the percentage of total source code executed during testing. ** It guarantees the quality of the test while potentially guaranteeing the quality of the actual product. Based on this, we can look for areas in the program that have not been tested by test cases and further create new test cases to increase coverage. By nature, it belongs to the category of white-box testing, which is to design test cases mainly based on the internal structure of the source code and test different parts of the software by designing different inputs. Common programming languages have code coverage testing tools. Code coverage-based analysis can do the following:

  • Analyze the parts of the code that are not covered, and work back to see if the initial test design is adequate, requirements/design is clear, test design is understood incorrectly, etc., and then conduct supplemental test case design.
  • Detection of obsolete code in the program can reverse the confused thinking points in the code design, remind the design/developer to clarify the logical relationship of the code, and improve the quality of the code.

In many development tools such as IDEA, statistical coverage tools are built in.

3. Unit test framework

3.1 Java unit Testing Framework

Most unit testing frameworks consist of the following components:

  • TestRuner: Responsible for driving unit test case execution and reporting test execution results;
  • TestFixture: is provided as a test suitesetUp() andtearDown()Method to ensure that the execution of two test cases is independent of each other;
  • TestResultThis component is used to collect the execution results of each TestCase;
  • Test: as aTestSuiteandTestCaseThe superclass exposes the run() method to TestRunner calls;
  • TestCase: class that is exposed to users, who write their own TestCase logic by inheriting TestCase;
  • TestSuite: Provides suite function to manage TestCases.

There are many Java unit test frameworks in the industry, most of which are based on the design idea of Junit framework. Among them, the most common unit test frameworks are Junit4/Junit5/TestNG. At present, these frameworks support annotation and parameterized testing, and specify different test values to run unit tests at run time. It also works with Maven/Gradle build tools.

3.2 A simple Junit example

public class JunitDemoTest { @Rule public Timeout timeout = new Timeout(1000); @Before public void init() { //... } @After public void destroy() { //.... } @Test public void testAssertArrayEquals() throws InterruptedException { byte[] str1 = "test1".getBytes(); byte[] str2 = "test2".getBytes(); byte[] str3 = "test".getBytes(); assertTrue("true", String.valueOf(str1).equals(String.valueOf(str3))); assertFalse("false", String.valueOf(str1).equals(String.valueOf(str2))); assertArrayEquals("false - byte arrays not same", str1, str2); Thread.sleep(50000); }}Copy the code

3.2 Implementation principles of Junit

The Junit framework starts with a method called JunitCore#run(Runner Runner).

The Junit4ClassRunner class inherits from ParentRunner’s BlockJUnit4ClassRunner class /SpringRunner class /Suit class. Each implementation’s run method is called.

The pre-run and post-run matching rules are initialized in the assembly Statement.

After calling The childrenInvoker, the single test task is started using the task thread pool. The getFilteredChildren method takes all the methods in the unit test class that need to be tested and loops through the runChild method to put the test methods into the task thread pool for execution.

In runChild, methodBlock is used to generate a Statement object for each test method,

MethodBlock will first generate the object of the test class, and then generate the pre/post/rule judgment/real target method execution chain.

RunBefores/RunAftersAnd so onStatementClass to form a chain of execution,

Invoke the test method using Java’s reflection mechanism.

In runLeaf, evaluate is called to the generated Statement object, which is the beginning of the chain of method execution. The result of the call is sent through the EachTestNotifier object, which is implemented in the listener mode. For example, the result of the run, failure or exception information is sent.

Several other frameworks are based on Junit to extend this basic process, to write unit tests in daily development provides a great convenience, but also in the large project development will encounter some of the problems such as collaborative development, such as some depend on the interface or the underlying module is not complete development, so write unit tests is invalid, It is also necessary to “fake” some data/results to complete these underlying dependencies, and to solve these problems Mock techniques are developed.

4. The Mock framework

4.1 introduce the Mock

A Mock usually means that when we test an object S, we construct fake objects to simulate the interaction with S, and the behavior of these Mock objects is predetermined and expected. Use these Mock objects to test whether S works properly under normal logic, exception logic, or stress. The biggest advantage of introducing a Mock is that it has a fixed behavior, which ensures that when you access one of its methods you always get the expected result that is returned without any logic. There are usually several benefits:

  • Isolate errors in other modules that cause test errors in this module.
  • Isolate the development status of other modules, as long as the interface is defined, it does not matter whether they are completed or not.
  • Some slower operations can be usedMock ObjectInstead, quick return.

    For distributed system testing, useMock ObjectThere are two other important benefits:
  • throughMock ObjectSome distributed tests can be converted to local tests
  • willMockUsed for stress testing, it is currently commonly used to solve the problem that test clusters cannot simulate online clusters under large scale pressures

Mock framework has EasyMock/JMock/Mokito/PowerMokito, the current project using the PowerMokito.

4.2 A simple Mock example

A complete Mock includes four steps: set the target -> set the consumption conditions -> expected return result -> consume and verify the return result. We use Mockito tool to achieve a simplest example, Mockito is mainly through Stub Stub. Accurately position the test pile by method name plus parameter and return the expected value.

@Data public void TestDao { public User getUser(String uid) { return new User("testUser"); } @Data public void TestService { private TestDao testDao; public User getUser(String uid) { return testDao.getUser(uid); }} @RunWith(PowerMokitoRunner.class) public void MockDemoTest { @InjectMock private TestService testService @Mock private TestDao testDao; @Before public void init() { Mockito.when(testDao.getUser(any())).thenReturn(new User("test2")); } @test public void test1() {userInfo = testService.getUser("uid"); userInfo = testService.getUser("uid"); }}Copy the code

The four main steps are as follows

  • Set goals > User User

  • Testdao.getuser (any())

  • Expected return -> thenReturn(..)

  • Consume and verify the returned result -> testdao.getUser (“uid”)

4.3 Main implementation principles of PowerMockito

PowerMockitoThe core principle of the framework is to use the CGLIGB bytecode tool based on the proxy pattern to implement a proxy that generates a target object or method. When the call is initialized with when, it matches and then returns according to return.

PowerMockitoRunner

Startup inheritance Junit# Runner class, I’ll call runwith method, the Junit framework will go load PowerMokito# PowerMockJUnitRunnerDelegateImpl class.

In Whitebox# findSingleFieldUsingStrategy will go with @ Mock / @ InjectMocks annotation of parameters, and initialized.

Finally, the Mock object is generated in Whitebox#newInstance.

“Piling” is usually done when the unit test is initialized, such as when… thenReturn…. Generate Stubbing

After the return will be generated, the piling progress will be updated, and the corresponding agent will be generated.

PerformStubbing will call createMock to generate the proxy object.

When making a proxy, the MockMaker class is called to generate the Mock object.

PowerMockMaker/CglibMockMaker implements MockMaker interface, here PowerMockito supports multiple bytecode enhancement to rewrite the way. The CGLIB approach currently in use.

ClassImposterizer#createProxyClass proxys the unit test target class using the Enhance class of the CGLIB bytecode utility and inserts methods to intercept class objects.

Method defined to intercept the class.

This creates a new proxy class object and returns the expected result when a method is executed. Because there are many implementations of PowerMockito framework, here is a simple example to understand:

public class PowerMockito { private static Map<Invocation, Object> results = new HashMap<Invocation, Object>(); private static Invocation lastInvocation; public static <T> T mock(Class<T> clazz) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(clazz); enhancer.setCallback(new MockInterceptor()); return (T)enhancer.create(); } private static class MockInterceptor implements MethodInterceptor { public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { Invocation invocation = new Invocation(proxy, method, args, proxy); lastInvocation = invocation; if (results.containsKey(invocation)) { return results.get(invocation); } return null; } } public static <T> When<T> when(T o) { return new When<T>(); } public static class When<T> { public void thenReturn(T retObj) { results.put(lastInvocation, retObj); }}}Copy the code

5. Suggestions for writing unit tests

Here are some tips for writing unit tests:

  • Write single test is not a waste of time, but to better improve the quality of design and implementation;
  • Background development is hierarchical architecture, need to subcontract test case management, layered single test writing;
  • To maximize code coverage, think about test datasets such as boundaries/exceptions/failures;
  • Don’t abuse mocks as they are called, and don’t ignore the original purpose of writing single tests in order to achieve method coverage.
  • To improve the efficiency of writing, the author also encapsulates a generation component.

6. Summary

This article is based on the recent use of single test in the project development knowledge arrangement, mainly describes the definition and function of single test, now used in the Java background development Junit and Mock framework of the basic use and implementation principle, and finally tells the author’s own writing unit test some experiences and suggestions.

reference

  • Blog.csdn.net/unifirst/ar… Unit testing framework comparison
  • Blog.csdn.net/qq_26295547… Junit framework in detail
  • Tech.youzan.com/code-covera… Code Coverage
  • Zhuanlan.zhihu.com/p/144826192 code coverage
  • Blog.csdn.net/weixin_4236… 
  • Blog.csdn.net/weixin_4433… How to write good unit Tests