Recently, it has fallen into the vast ocean of unit testing. Tens of thousands of lines of code suddenly require unit testing coverage, which is really terrible. After the most arduous struggle and learning, I finally crossed the technical barrier. I'd like to share my recent experience of stepping on pits and some typical use cases.
The following is a common project I have used, and some information is hidden. You can refer to it when you practice in your own project. Try not to copy the code directly. There are many compatibility holes in my own use, especially the automatic import function of IDE.
Technical scheme
This technical proposal is based on Spock unit testing framework promoted by the company. Spock is a unit testing framework based on Groovy language, and its foundation is Junit of Java. At present, the latest version has reached 2.0, but there are high requirements for Groovy and corresponding Java versions, so Groovy version uses 1. +, The Mock and Spy provided by Spock are good enough. The simulation of object behavior meets most scenarios, but there are limitations when it comes to static method simulation. Therefore, Mockito and PowerMock are introduced to realize the test simulation scenario of designing static methods.
The following are the dependent versions:
<dependency> <groupId>org.spockframework</groupId> <artifactId>spock-core</artifactId> <version>1.2-groovy-2.5</version> <scope>test</scope> </dependency> <dependency> <groupId>org.spockframework</groupId> <artifactId>spock-spring</artifactId> <version>1.2-groovy-2.5</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito2</artifactId> <version>1.7.4</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>1.7.4</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>2.8.9</version> <scope>test</scope> </dependency>
Groovy all also needs a self annotation because it supports groovy all
<dependency> <!-- use a specific Groovy version rather than the one specified by spock-core --> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy-all</artifactId> <version>2.4.7</version> </dependency>
In addition, several dependencies used in special scenarios are added in the provided configuration file for reference:
<dependency> <!-- enables mocking of classes (in addition to interfaces) --> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy</artifactId> <version>1.9.9</version> <scope>test</scope> </dependency> <dependency> <!-- enables mocking of classes without default constructor (together with CGLIB) --> <groupId>org.objenesis</groupId> <artifactId>objenesis</artifactId> <version>3.0.1</version> <scope>test</scope> </dependency> <dependency> <!-- only required if Hamcrest matchers are used --> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-core</artifactId> <version>2.1</version> <scope>test</scope> </dependency>
Non static resources
Due to the repeated method names of multiple single test frameworks, I also posted the import content. If the same code cannot run, you can check whether to import the correct methods and classes. import static is not recommended here, because there may be mixed use and problems that are difficult to check.
Since there is no logic using Spy release in the current test, Mock mode is used, and the method of Mock object needs to be simulated. This is divided into two categories: Spock and PowerMock (combined with Mockito). The reason is that in the scenario of mixed static and non static resources, the @ RunWith running rules of PowerMock are specified, which is incompatible with Spock writing method and requires the function of Mock object of PowerMock framework.
Mock tested object
@Autowired construction method
Take a controller as an example. The source code is as follows:
@Api(tags = "SLA Rule management module") @Slf4j @RestController @RequestMapping("/hickwall/v1/static/sla") public class FunController { HttpServletRequest request; ISlaService service; @Autowired public FunController(HttpServletRequest request, ISlaService service) { this.request = request; this.service = service; } }
Spock single test code is as follows:
import com.funtester.service.ISlaService import com.funtester.vo.sla.SlaBean import spock.lang.Shared import spock.lang.Specification import javax.servlet.http.HttpServletRequest class FunControllerTest extends Specification { def service = Mock(ISlaService) @Shared def request = Mock(HttpServletRequest) def FunController = new FunController(request, service) }
@Autowired property object, no construction method
The source code is as follows:
public class ApiImpl implements IApi { @Autowired private ApiRMapper mapper; }
The codes of Spock single test part are as follows:
import com.funtester.mapper.ApiRMapper import com.funtester.vo.ApiR import spock.lang.Shared import spock.lang.Specification ApiRMapper mapper = Mock(ApiRMapper) def drive = new ApiImpl(mapper:mapper)
PowerMock usage
Scenarios are also divided into two types: whether there is a construction method or not. Except that the Mock method is different, the others are the same. They are not listed here.
PS: if there is an attribute in the object attribute that is not annotated by @ Autowired, the lombok annotation of @ AllArgsConstructor cannot be used, and an error will be reported when the service is started.
The source code is as follows:
@Component @Slf4j public class TaskScheduled { @Autowired IService service; @Value("${hickwall.statistic.cid}") public String cid; }
Shared objects and initialization
The functions provided by Spock are used uniformly. The annotation @ Shared cannot be assigned in the Spock method without adding it, but it can be used as an ordinary object.
Spock framework Demo:
@Shared def slaBean = new SlaBean() def setupSpec() { request.getHeader("operator") >> "FunTester" slaBean.name = "test" slaBean.quota = 1 slaBean.upstream = "PRO" slaBean.threshold = 0.1 slaBean.creator = "FunTester" slaBean.id = 100 }
Define object behavior
Spock defines the behavior of Mock objects
The basic Spock syntax structure when then expct is as follows:
def "AddSla"() { when: def sla = FunController.addSla(slaBean) then: service.addSla(_) >> {f -> assert "FunTester" in f.creator 1 } expect: sla.code == 0 sla.data == 1 }
You can also add given at the beginning. when and then are usually used together.
The above Demo asserts and processes the parameters during the Mock method, which is also a feature of Spock framework. Others are Groovy syntax features.
Other syntax for defining Mock behavior is as follows:
service.getAllGroup(_,_) >> null//Return null service.getAllGroup(_,_) >> {throw new Exception()} //Throw exception service.getAllGroup(_,_) >> []//Returns an empty list. Groovy implements ArrayList by default service.getAllGroup(_,_) >> [slaBean,slaBean]//Return to normal list service.getAllGroup(_,_) >> [slaBean,slaBean]//Return to normal list service.getAllGroup(_,10) >> [slaBean,slaBean]//Timing a parameter service.getAllGroup(any(),10) >> [slaBean,slaBean]//any() is equivalent to_ service.getAllGroup(any(),10) >> service.getAllGroup(1,10)//Call other methods to return
Mockito simulates object behavior
The syntax for using Mockito with PowerMock is slightly more complex. First, we need to define the object behavior (usually in the com.funterbase.task.taskscheduledtest#setupspec method), and then use it in use cases.
Timed object behavior:
Mockito.when(newutil.filter(Mockito.any())).thenReturn(true)
After defining the behavior, it can be used normally in the Spock use case, including in the object method created through the Mock object. If you call the method that has defined the behavior, you will also follow the custom logic.
Other commonly defined behaviors:
Mockito.when(newutil.filter(Mockito.any())).thenReturn(null) Mockito.when(newutil.filter(Mockito.any())).thenThrow(Exception.class)//Throw exception PowerMockito.doNothing().when(newutil).filter(Mockito.any(ArrayList.class))//dothing, do nothing
In the third example, we assume that the filter method is a void method without return.
Generally, we need to build a return object. If the object needs too many attributes to be assigned, we can use the method of initializing assignment. The following is a Demo of the return value of Mock's method of returning list:
Mockito.when(newser.selectAllService()).thenReturn([new NewInterface() { { setUrl("/abc") setNname("test") setMethod("GET") } }, new NewInterface() { { setUrl("/abcd") setNname("test") setMethod("POST") } }, new NewInterface() { { setUrl("/abce") setNname("test") setMethod("GET") } }])