Spock [even] a single measurement can be so silky

image.png

0. Why Everyone hates to write a single test

Before about swagger mentioned in the article before, two things most programmers hate, one is people do not write the document, the other is to write their own documentation. Here, if the document into unit testing also established.
Understand the role of each developer unit testing, code coverage also know that the higher the better. High coverage of the code, the lower the probability is relatively appear BUG, the more stable operation of the line, then pot the less, they will not be afraid to test his colleagues sudden concern.
Since so many benefits, why would he hate it? At least in my opinion, let alone measure are the following reasons I like it up.
First, I want to write a lot of additional code , a single high coverage test code more often than you want to test, develop real business code to be more, or even several times the business code. This makes it difficult to accept that, you think about the development of five minutes, a single test two hours is what kind of mood. And not a single measure finished on the right, behind the business and if changed, the code you write a single measure should maintain synchronization.
Second, even if you have the patience to write a single test, but in the current environment this fight speed squeeze time, it will give you so much to write a single measure time? Write a single measurement time can achieve a demand , how would you choose?
Third, write a single measurement is usually a very boring thing , because he is quite dead, the main purpose is to verify, by contrast he is more like a manual labor, business did not really write the code that creates a sense of accomplishment. Write, could not verify the bug is lost, white writing, verify the bug they feel that they are playing their own face.

1. why everyone must also write a single test

So the conclusion is not to write a single test? So the question again, come out to mix sooner or later have to repay , on-line out of the question, the ultimate responsibility for who? Not to mention the demand of the product, not the students test did not find the problem, at most, it is their joint responsibility. This is certainly the most responsible for writing the code for you. Especially for developers who are engaged in financial, trade, electricity providers and other closely related business, with each line of code to fight traffic is real money. Every star do things, microblogging hung, has been transferred as a joke, after all, just entertainment-related, if hanging is Alipay, micro letter, that the user does not have the degree inclusive. These services if serious problems arise, ranging from out the door, then his entire career to bear the stain, while in object-oriented development from prison to become directly oriented development . Therefore, the test program is not only to protect the unit, the more protection you are writing programs .
Finally helpless come to a conclusion, a single measurement is people love to hate something, do something but do not want to have to do . Although we can not write a single measure to change it, but we can change how to write a unit test it.

2. SPOCK can help you improve single test experience

Of course, this article is not to teach you to improve code coverage using unorthodox methods. But to be effective you write unit tests through a magic framework spock. spock source of this name, personal guess is because the "Star Trek" The namesake (cover art). So spock is how to improve the efficiency of a single test write it? I think the following points:
first, he can use fewer lines of code to implement unit testing , so you can focus on to verify the results rather than the process of writing a single test code. So how did he write less code that does this matter? It turned out he use of magic called groovy.
groovy is actually a dynamic language based on the jvm. It can be simply understood jvm to run on the python, or js. Here, the students may not have contact with dynamic languages, there is a relatively stereotype they will, too flexible, it is prone to problems, and maintainability is poor, so that with a "dynamic moment cool, family xxx" the stems. First of all, these are indeed his problem, strictly speaking, is to use the problems caused when improper. So mainly to see people use. Andrews areas such as official dependency management tool is based on gradle groovy development.
Also, do not mistake me learn this framework, but also to learn a language, it costs too much. In fact, you need not worry, if you will groovy certainly better if not does not matter. Because groovy is java-based, so can rest assured that bold syntax of java, some use to the groovy little unique syntax, and will tell you later.
Second, he has a better semantic, let alone measure your code readability higher.
The semantics of the word may not be well understood. Two examples for instance, the first one is better semantic language - HTML. His grammar is characterized labels, different in different types of tags. For example, the head is the head of information, body is the main content of the information, table is the table of information, for people without programming experience, it can also be very easy to understand. The second difference is more semantic language - Regular. He can say that basically there is no such thing as semantics, directly resulting problem is that even if you write your own regular, after a few days you do not know what was written yes. For example, the following regular, you can guess what he means? (You can leave a message reply)

((?:(?:25[0-5]|2[0-4]\d|[01]?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d?\d))

3. enjoy the magic SPOCK

3.1 introduces dependence

        <!--如果没有使得 spring boot,以下包可以省略-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--引入spock 核心包-->
        <dependency>
            <groupId>org.spockframework</groupId>
            <artifactId>spock-core</artifactId>
            <version>1.3-groovy-2.5</version>
            <scope>test</scope>
        </dependency>
        <!--引入spock 与 spring 集成包-->
        <dependency>
            <groupId>org.spockframework</groupId>
            <artifactId>spock-spring</artifactId>
            <version>1.3-groovy-2.5</version>
            <scope>test</scope>
        </dependency>
        <!--引入 groovy 依赖-->
        <dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy-all</artifactId>
            <version>2.5.7</version>
            <scope>test</scope>
        </dependency>
Explanation

Notes already indicated, the first packet is spring boot projects need to use if you just want to use spock, as long as the bottom three can be. Wherein the first packet  spock-core provides the core functionality spock, the second package  spock-spring is provided (may not be introduced into the spring without the use of) the integrated spring. Note that both package version number -> 1.3-Groovy-2.5 . The first version is the version number 1.3 represents the fact spock, the second version represents the version spock to be dependent groovy environment. Finally, a package that we have to rely on groovy.

3.2 basis of test preparation classes

3.2.1 Calculator.java

/*
 * *
 *  * blog.coder4j.cn
 *  * Copyright (C) 2016-2019 All Rights Reserved.
 *
 */
package cn.coder4j.study.example.spock;

/**
 * @author buhao
 * @version Calculator.java, v 0.1 2019-10-30 10:34 buhao
 */
public class Calculator {

    /**
     * 加操作
     *
     * @param num1
     * @param num2
     * @return
     */
    public static int add(int num1, int num2) {
        return num1 + num2;
    }

    /**
     * 整型除操作
     *
     * @param num1
     * @param num2
     * @return
     */
    public static int divideInt(int num1, int num2) {
        return num1 / num2;
    }

    /**
     * 浮点型操作
     * @param num1
     * @param num2
     * @return
     */
    public static double divideDouble(double num1,  double num2){
        return num1 / num2;
    }
}
Explanation

This is a very simple calculator class. Written only three methods, one is an addition operation, an integer divide operation, a type of floating-point division operations.

3.3 Start single probe  Calculator.java

3.3.1 Creating a single test class CalculatorTest.groovy

class CalculatorTest extends  Specification {
    
}
Explanation

Here we must note, before we have said spock is based on groovy. Therefore, not a single test class suffix .java and .groovy ** . Do not create a category into common java. Otherwise, create no problem, but to write some groovy syntax error. If you are using by IDEA can create, we create a Java class before all choose the first option, we now select the third Groovy Class ** on it.
image.png
The other is spock test class needs to inherit  spock.lang.Specification class.

3.3.2 Verify add operation - expect

    def "test add"(){
        expect:
        Calculator.add(1, 1) == 2
    }
Explanation

def is groovy keywords that can be used to define variables with the method name. Behind "test add" is the name of your unit tests, you can also use Chinese. Finally, important to note that  expect this keyword.
expect literally means expectations, we expect what happened. When other single metrology frame, is similar thereto assert. For example _Assert.assertEquals (_Calculator.add (_1 + 1) , 2) _ Thus, we assert that represents the add operation 1 1 incoming addition result is 2. If this result is illustrated by the use, if not then the use cases fails. This is done in line with our above code function.
grammatical meaning expect is to expect in the block within, all expressions are validated by the establishment, on the contrary have not set up any of the validation fails. Here the introduction of a block concept. How to understand spock block it? Spock we said above, there is the role of this block because of good semantic and better readability. It may be likened to the html tag. html tag range is between two labels, and a little more concise spock, from the beginning of this label to the next tab or local codes beginning of the end, it is his range. As long as we expect to see the label to understand, within the scope of his are we going to get the expected results.

3.3.3 Verify add operation - given - and

Here the code is relatively simple, the parameters I used only once, so write directly to death. If you want to reuse, I have to put these parameters drawn into variables. This can be used when spock a given block. grammatical meaning given the equivalent of an initialization code block.

    def "test add with given"(){
        given:
        def num1 = 1
        def num2 = 1
        def result = 2

        expect:
        Calculator.add(num1, num2) == result
    }

Of course, you can also write like this, but seriously is not recommended , because although you can achieve the same effect, but does not comply with the semantics of spock . Just as we are generally introduced in the head inside js, css, but you may be introduced in the body or any tag, there is no problem but destroyed the grammar semantics, inconvenience to understand and maintain.

    // 反倒
    def "test add with given"(){
        expect:
        def num1 = 1
        def num2 = 1
        def result = 2
        Calculator.add(num1, num2) == result
    }

If you want a little better semantics, we can define separate parameters and results, you can use this time and blocks. Its grammatical function can be interpreted as a label with the most recent on top of him.

    def "test add with given and"(){
        given:
        def num1 = 1
        def num2 = 1

        and:
        def result = 2

        expect:
        Calculator.add(num1, num2) == result
    }

3.3.4 Verify add operation - expect - where

Read the above example, you may feel better spock just semantics, but none the less write few lines of code it. Do not worry, here we take a look at the big kill spock of the WHERE .

    def "test add with expect where"(){
        expect:
        Calculator.add(num1, num2) == result

        where:
        num1    |   num2    |   result
        1       |   1       |   2
        1       |   2       |   3
        1       |   3       |   4
    }

where blocks can be understood as a place to prepare test data , he can now expect to use a combination. Expect the above code block which defines three variables num1, num2, result. These data we can define where a block in. where blocks use a method like the markdown defined in the table. The first line or headers, lists the name of the variable we want to transfer data, where to correspond to expect in , but no less can be more. Other lines are data lines, like the header is through "|" delimited. By this, Spock will run 3 times with Example 2 were 1 = 2,1 + 3,1 = 2 + 3 + 4 = these use cases. how about it? It is not very convenient, and then later expand the use cases just add a row of data on it. 

3.3.5 Verify add operation - expect - where - @Unroll

These use cases above are normal can run through, and then if IDEA will finish as follows:
image.png
So now we look at what happens if a useful embodiment does not pass, the last 4 code above into 5

    def "test add with expect where"(){
        expect:
        Calculator.add(num1, num2) == result

        where:
        num1    |   num2    |   result
        1       |   1       |   2
        1       |   2       |   3
        1       |   3       |   5
    }

Run again, IDEA will be shown below
image.png
marked out on the left is the result of the implementation of the use cases, you can see that although there are three data, two data is successful, it will only display the overall success, it did not show through. But three data, how do I know which do not pass it?
The right side is marked out error log spock printed. Can clearly see that in num1 is 1, num2 to 3, result is 5 and determine the relationship between them as a result == false is correct. spock print this log is very calendar harm, if it is more strings, also calculates the degree of match between the exception string with the correct string, interested students can self-test.
Ah, although you can use the log to know which cases did not pass, but still feel a bit of trouble. spock know it. So he also provides a ** @Unroll ** notes. Us on the above code together with this comment:

    @Unroll
    def "test add with expect where unroll"(){
        expect:
        Calculator.add(num1, num2) == result

        where:
        num1    |   num2    |   result
        1       |   1       |   2
        1       |   2       |   3
        1       |   3       |   5
    }

Results are as follows:  image.png
by adding ** @ Unroll ** annotation, Spock above code automatically split into three separate single measured tests were run, the results clearer.
So you can more clearly? Of course you can, we found spock split, the name of each use case in fact you write the name of a single measurement method, and then add back an array subscript, is not very intuitive. We can string syntax groovy, the use case into the variable name, as follows:

    @Unroll
    def "test add with expect where unroll by #num1 + #num2 = #result"(){
        expect:
        Calculator.add(num1, num2) == result

        where:
        num1    |   num2    |   result
        1       |   1       |   2
        1       |   2       |   3
        1       |   3       |   5
    }

As above, we added the following method name  # # num1 + num2 = #resultThe . Here is somewhat similar to the method we use in mybatis or some template engine. # Variable number splicing statement on it, after the implementation of results are as follows.
image.png
This time clearer.
Another point is that where this is used by default in tabular form:

        where:
        num1    |   num2    |   result
        1       |   1       |   2
        1       |   2       |   3
        1       |   3       |   5

Very intuitive, but this form has a downside. Above "|" number of the so neat. I'm a TAG is a space out of the press. While the syntax is not required to be aligned, but a death sentence for obsessive-compulsive disorder. However, the good news can also have another form:

    @Unroll
    def "test add with expect where unroll arr by #num1 + #num2 = #result"(){
        expect:
        Calculator.add(num1, num2) == result

        where:
        num1 << [1, 1, 2]
        num2 << [1, 2, 3]
        result << [1, 3, 4]
    }

By "<<" symbol (note the direction), is assigned to an array variable, the data equivalent to the above table, the table is not intuitive, but they do not consider relatively simple alignment issues, these two forms of personal preference.

3.3.6 Verify integer divide operation - when - then

We all know that there will be an integer division by zero throws an "/ by zero" exception, if it asserts the exception. Using the above expect very good operation, we can use another similar block ** when ... then **.

    @Unroll
    def "test int divide zero exception"(){
        when:
        Calculator.divideInt(1, 0)

        then:
        def ex = thrown(ArithmeticException)
        ex.message == "/ by zero"
    }

when ... then usually occur in pairs, when it represents an operation performed When block, then the block will be desired. For example the code described above, when executed  Calculator.divideInt (1, 0) operation, they will throw  ArithmeticException abnormality, and the abnormality information is  / by ZERO .

3.4 Preparation Spring test class

We have already learned the basics usage spock, here we will learn to integrate knowledge and spring, first create a few demo class for testing

3.4.1 User.java

/*
 * *
 *  * blog.coder4j.cn
 *  * Copyright (C) 2016-2019 All Rights Reserved.
 *
 */
package cn.coder4j.study.example.spock.model;

import java.util.Objects;

/**
 * @author buhao
 * @version User.java, v 0.1 2019-10-30 16:23 buhao
 */
public class User {
    private String name;
    private Integer age;
    private String passwd;

    public User(String name, Integer age, String passwd) {
        this.name = name;
        this.age = age;
        this.passwd = passwd;
    }

    /**
     * Getter method for property <tt>passwd</tt>.
     *
     * @return property value of passwd
     */
    public String getPasswd() {
        return passwd;
    }

    /**
     * Setter method for property <tt>passwd</tt>.
     *
     * @param passwd value to be assigned to property passwd
     */
    public void setPasswd(String passwd) {
        this.passwd = passwd;
    }

    /**
     * Getter method for property <tt>name</tt>.
     *
     * @return property value of name
     */
    public String getName() {
        return name;
    }

    /**
     * Setter method for property <tt>name</tt>.
     *
     * @param name value to be assigned to property name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * Getter method for property <tt>age</tt>.
     *
     * @return property value of age
     */
    public Integer getAge() {
        return age;
    }

    /**
     * Setter method for property <tt>age</tt>.
     *
     * @param age value to be assigned to property age
     */
    public void setAge(Integer age) {
        this.age = age;
    }

    public User() {
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(name, user.name) &&
                Objects.equals(age, user.age) &&
                Objects.equals(passwd, user.passwd);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age, passwd);
    }
}

3.4.2 UserDao.java

/*
 * *
 *  * blog.coder4j.cn
 *  * Copyright (C) 2016-2019 All Rights Reserved.
 *
 */
package cn.coder4j.study.example.spock.dao;

import cn.coder4j.study.example.spock.model.User;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

/**
 * @author buhao
 * @version UserDao.java, v 0.1 2019-10-30 16:24 buhao
 */
@Component
public class UserDao {

    /**
     * 模拟数据库
     */
    private static Map<String, User> userMap = new HashMap<>();
    static {
        userMap.put("k",new User("k", 1, "123"));
        userMap.put("i",new User("i", 2, "456"));
        userMap.put("w",new User("w", 3, "789"));
    }

    /**
     * 通过用户名查询用户
     * @param name
     * @return
     */
    public User findByName(String name){
        return userMap.get(name);
    }
}

3.4.3 UserService.java

/*
 * *
 *  * blog.coder4j.cn
 *  * Copyright (C) 2016-2019 All Rights Reserved.
 *
 */
package cn.coder4j.study.example.spock.service;

import cn.coder4j.study.example.spock.dao.UserDao;
import cn.coder4j.study.example.spock.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author buhao
 * @version UserService.java, v 0.1 2019-10-30 16:29 buhao
 */
@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    public User findByName(String name){
        return userDao.findByName(name);
    }

    public void loginAfter(){
        System.out.println("登录成功");
    }

    public void login(String name, String passwd){
        User user = findByName(name);
        if (user == null){
            throw new RuntimeException(name + "不存在");
        }
        if (!user.getPasswd().equals(passwd)){
            throw new RuntimeException(name + "密码输入错误");
        }
        loginAfter();
    }
}

3.4.3 Application.java

/*
 * *
 *  * blog.coder4j.cn
 *  * Copyright (C) 2016-2019 All Rights Reserved.
 *
 */

package cn.coder4j.study.example.spock;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

3.5 Integration Testing with spring

/*
 * *
 *  * blog.coder4j.cn
 *  * Copyright (C) 2016-2019 All Rights Reserved.
 *
 */

package cn.coder4j.study.example.spock.service

import cn.coder4j.study.example.spock.model.User
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import spock.lang.Specification
import spock.lang.Unroll

@SpringBootTest
class UserServiceFunctionTest extends Specification {

    @Autowired
    UserService userService

    @Unroll
    def "test findByName with input #name return #result"() {
        expect:
        userService.findByName(name) == result

        where:
        name << ["k", "i", "kk"]
        result << [new User("k", 1, "123"), new User("i", 2, "456"), null]

    }

    @Unroll
    def "test login with input #name and #passwd throw #errMsg"() {
        when:
        userService.login(name, passwd)

        then:
        def e = thrown(Exception)
        e.message == errMsg

        where:
        name    |   passwd  |   errMsg
        "kd"     |   "1"     |   "${name}不存在"
        "k"     |   "1"     |   "${name}密码输入错误"

    }
}

spock spring with integrated particularly simple, as long as you join said at the beginning of  spock-spring and  spring-the Boot-Starter-the Test . And then on the type of test code plus  @SpringBootTest  comment on it. The class would like to use direct injection came on it, but be aware that this can only be considered functional testing or integration testing, because the use case is running will start spring container must also have external dependencies. Very time-consuming, and sometimes local external dependencies can not run, so we usually are done through unit testing mock.

3.6 and spring mock test

/*
 * *
 *  * blog.coder4j.cn
 *  * Copyright (C) 2016-2019 All Rights Reserved.
 *
 */

package cn.coder4j.study.example.spock.service

import cn.coder4j.study.example.spock.dao.UserDao
import cn.coder4j.study.example.spock.model.User
import spock.lang.Specification
import spock.lang.Unroll

class UserServiceUnitTest extends Specification  {

    UserService userService = new UserService()
    UserDao userDao = Mock(UserDao)

    def setup(){
        userService.userDao = userDao
    }

    def "test login with success"(){

        when:
        userService.login("k", "p")

        then:
        1 * userDao.findByName("k") >> new User("k", 12,"p")
    }

    def "test login with error"(){
        given:
        def name = "k"
        def passwd = "p"

        when:
        userService.login(name, passwd)

        then:
        1 * userDao.findByName(name) >> null

        then:
        def e = thrown(RuntimeException)
        e.message == "${name}不存在"

    }

    @Unroll
    def "test login with "(){
        when:
        userService.login(name, passwd)

        then:
        userDao.findByName("k") >> null
        userDao.findByName("k1") >> new User("k1", 12, "p")

        then:
        def e = thrown(RuntimeException)
        e.message == errMsg

        where:
        name        |   passwd  |   errMsg
        "k"         |   "k"     |   "${name}不存在"
        "k1"        |   "p1"     |   "${name}密码输入错误"

    }
}

spock using mock is also very simple, direct use of Mock (class) on it. Code above _UserDao userDao = Mock (UserDao). _ Written above example there are a few points to explain to this method as an example:

    def "test login with error"(){
        given:
        def name = "k"
        def passwd = "p"

        when:
        userService.login(name, passwd)

        then:
        1 * userDao.findByName(name) >> null

        then:
        def e = thrown(RuntimeException)
        e.message == "${name}不存在"

    }

given, when, then needless to say, everyone is familiar with, but then the first inside 1 * userDao.findByName (name) >> null  What the hell?
First, we can know that one use case there may be a plurality of blocks then, may be placed for a plurality of the plurality of desired then respectively.
Second,  1 * xx  represents the desired xx operation is performed once. * UserDao.findByName 1 (name) ** on the performance when executing  when userService.login (name, passwd) I look forward to (name) performed once userDao.findByName method. If this approach is desirable not to execute xx * _0 , which is useful in verifying the condition code, and then >> null_ What does it mean? When he performed on behalf of  the userDao.findByName (name) method, I asked him to return the results null . Because this object is our mock userDao out, he is a fake objects, in order to allow the subsequent processes according to our ideas, I can ">>" Let spock analog return the specified data.
Third, to be noted that the second code blocks then used
$ {name} reference variable, with title # name ** are different.

3.7 Other content

3.7.1 Public Methods

Method name effect
setup() Each method before calling
cleanup() After performing each method invocation
setupSpec() Each method call before loading the class time
cleanupSpec() Each class executing the method call once

These methods are generally used for some initialization before the start of the test, after the test is completed and the cleaning operation, as follows:

    def setup() {
        println "方法开始前初始化"
    }

    def cleanup() {
        println "方法执行完清理"
    }

    def setupSpec() {
        println "类加载前开始前初始化"
    }

    def cleanupSpec() {
        println "所以方法执行完清理"
    }

3.7.2 @Timeout

For some methods, we need to provide his time, if the run time exceeds the specified time even if the failure, then you can use the timeout comment

    @Timeout(value = 900, unit = TimeUnit.MILLISECONDS)
    def "test timeout"(){
        expect:
        Thread.sleep(1000)
        1 == 1
    }

Annotations are two values, one value we set value, unit is the unit value.

3.7.3 with

    def "test findByName by verity"() {
        given:
        def userDao = Mock(UserDao)

        when:
        userDao.findByName("kk") >> new User("kk", 12, "33")

        then:
        def user = userDao.findByName("kk")
        with(user) {
            name == "kk"
            age == 12
            passwd == "33"
        }

    }

with regarded as a syntactic sugar, before him we did not want to judge the value of the object only, user.getXxx () == xx. If too much property is also very troublesome, followed by wrapping with, as long as the write directly attribute name in braces can, as shown in the code.

4. Other

4.1 The complete code

Because of space limitations, not all had finished code, complete code has been uploaded  GitHub .

4.2 Reference Documents

In this paper, after a wonderful Bowen we visited the following bloggers, plus a summary of their own learning process from, if there is this article do not understand when looking at can look at the link below.

  1. Spock in Java and slowly fell in love with writing unit tests
  2. Use Groovy + Spock easily write more concise single measure
  3. Detailed introduction and use of Spock testing framework
  4. Spock BDD-based test
  5. Spock official documents
  6. Spock testing framework
  7. spock-testing-exceptions-with-data-tables

Guess you like

Origin www.cnblogs.com/kiwifly/p/11789468.html