This is the first article in the Spock series. The entire album will cover what Spock is for. Why Spock? What good can it do us? How is it different from JUnit, JMock, Mockito? To give you an objective view of Spock, we’ll look at the common problems and pain points of writing unit test code, how Spock solves it, how Spock code is written, and the advantages and disadvantages of Spock.

What is Spock?

Spock is abroad a good testing framework, based on BDD, powerful function, can let our test code standardization, clear structure levels, according to the characteristics of the groovy dynamic language and provide all kinds of labels for more efficient and concise writing test code, provides a universal, simple, structured description language

Spockframework.org

“Spock is a testing and specification framework for Java and Groovy applications.

It stands out from the crowd because of its beautiful and expressive normative language.

Spock was inspired by JUnit, RSpec, jMock, Mockito, Groovy, Scala, Vulcans”

The features of Spock are as follows:

  • Let our test code more standard, built-in a variety of tags to standardize the semantics of the single test code, so that our test code structure is clear, more readable, reduce the difficulty of later maintenance
  • Provide multiple tags, such as:where,with,thrown. Helps us navigate complex test scenarios
  • In addition, using Groovy as a dynamic language to write test code allows us to write more concise test code, suitable for agile development, and improve the efficiency of writing single-test code
  • Following the BDD behavior-driven development pattern, testing for more than just test coverage, helps improve code quality
  • IDE compatibility, built-in mock function

Why Spock? What’s the difference between Spock and JUnit, JMock, Mockito?

Existing single-test frameworks such as junit, JMock, and Mockito are independent tools that provide specific solutions for different business scenarios.

Junit is purely for testing and does not provide mock functionality

Micro service has been the mainstream of the Internet company technology architecture, most of the system are distributed, usually by way of interface interaction between services and services, and even inside is divided into multiple service module, a lot of business functions need to rely on the underlying interface the data returned to continue to the rest of the process, or from the database/Redis storage devices available, Or from a configuration in the configuration center.

As a result, these dependencies (interface, Redis, DB, configuration center…) must be added if we want to test the logic of the code. To mock.

If the interface is unstable or has problems, it will affect the normal testing of our code, so we need to simulate the place where we call the interface, and let it return the specified result (prepared data in advance), so that we can verify our own code is correct, conforms to the expected logic and results.

JMock or Mockito provides mock functionality to mask dependencies such as interfaces, but does not mock static methods of static classes. PowerMock or Jmockit provides mock of static classes and methods. But they require integration (junit+ Mockito + Powermock), the syntax is cumbersome, and the tools don’t tell you “How should I write unit test code?”

The number of tools also leads to different people writing different styles of unit test code…

Spock provides specification descriptions and defines various tags (given, WHEN, then, WHERE, etc.) to describe what the code “should do”, what the input criteria are, and whether the output meets expectations, so as to standardize the writing of code from a semantic level.

Spock comes with Mock functionality, which is easy to use (and can be extended to other Mock frameworks, such as Power Mock). Coupled with the powerful syntax of groovy’s dynamic language, Spock can write concise and efficient test code, and make it easier to intuitively verify the flow of business code behavior, enhancing our control over code execution logic.

Background and Original Intention

Online information about Spock is simpler, including demo website, unable to solve complex business scenarios in our project, need to find a set of mature solution for their project, so feel the need to put our project in using Spock experience sharing, help improve the efficiency of the single test development and validation code quality.

After mastering Spock, the overall single-test development efficiency of our project team increased by more than 50%, and the code readability and maintainability were improved and enhanced.

Suits the crowd

All the demo code is run in IntelliJ IDEA, spring-boot project, based on Spock 1.3-Groovy-2.5

How does Spock address the pain points of traditional unit test development

This article mainly discusses several common problems we encounter in the process of writing unit tests, and how to solve them using JUnit and Spock respectively. Through comparison, we will give you an overall understanding.

Cost and efficiency of unit test code development

For business code in complex scenarios, the cost of writing single test code increases when there are many if/else branches. Normal business code may be only a few dozen lines, but in order to test this function, the test code may be much more than a few dozen lines to cover most of the branch scenarios

For our production shortly before the occurrence of an accident: there is a function of online more than a year has been normal, no problems, but recently a new caller requested data, reached the code in a branch of logic, not commonly used caused the bug, throw an exception to block the main process directly, but the caller request quantity is not big…

It is estimated that the students who wrote this code at the beginning also thought it was very unlikely to go to this branch. Although the unit test code was also written at that time, there were many branches, which just missed the test of this branch logic, leaving hidden trouble for the future online

This is also the most common problem we encounter when writing unit tests: in order to meet the high requirement of branch coverage, there are different if/else results, and the traditional single test writing method may need to be called several times to cover all branch scenarios. On the one hand, it is troublesome to write single test, and at the same time, it will increase the redundancy of single test code

You can use junit’s @Parametered annotation or the DataProvider approach, but it’s not intuitive enough, and it doesn’t provide detailed error information if one of the branch test cases goes wrong.

For example, the following example demonstrates the code, based on the id number input date of birth, gender, age, etc., this method features a lot of if… else… Branch nested logic

/** * ID card number tools < P > * 15 digits: 6-digit address code + 6-digit date of birth (900101 stands for born on January 1, 1990) + 3-digit sequence code * 18 digits: 6-digit address code + 8-digit date of birth (19900101 means born on January 1, 1990) + 3-digit sequence code + 1-digit parity code * Sequence code The odd number is assigned to the male, and the even number is assigned to the female. *@authorOfficial number :Java old K * personal blog :www.javakk.com */
public class IDNumberUtils {
    /** * Get date of birth, gender, age * by id number@param certificateNo
     * @returnDate of birth format: 1990-01-01 Gender format: F- female, M- male */
    public static Map<String, String> getBirAgeSex(String certificateNo) {
        String birthday = "";
        String age = "";
        String sex = "";

        int year = Calendar.getInstance().get(Calendar.YEAR);
        char[] number = certificateNo.toCharArray();
        boolean flag = true;
        if (number.length == 15) {
            for (int x = 0; x < number.length; x++) {
                if(! flag)return newHashMap<>(); flag = Character.isDigit(number[x]); }}else if (number.length == 18) {
            for (int x = 0; x < number.length - 1; x++) {
                if(! flag)return newHashMap<>(); flag = Character.isDigit(number[x]); }}if (flag && certificateNo.length() == 15) {
            birthday = "19" + certificateNo.substring(6.8) + "-"
                    + certificateNo.substring(8.10) + "-"
                    + certificateNo.substring(10.12);
            sex = Integer.parseInt(certificateNo.substring(certificateNo.length() - 3,
                    certificateNo.length())) % 2= =0 ? "Female" : "Male";
            age = (year - Integer.parseInt("19" + certificateNo.substring(6.8))) + "";
        } else if (flag && certificateNo.length() == 18) {
            birthday = certificateNo.substring(6.10) + "-"
                    + certificateNo.substring(10.12) + "-"
                    + certificateNo.substring(12.14);
            sex = Integer.parseInt(certificateNo.substring(certificateNo.length() - 4,
                    certificateNo.length() - 1)) % 2= =0 ? "Female" : "Male";
            age = (year - Integer.parseInt(certificateNo.substring(6.10))) + "";
        }
        Map<String, String> map = new HashMap<>();
        map.put("birthday", birthday);
        map.put("age", age);
        map.put("sex", sex);
        returnmap; }}Copy the code

For this scenario, Spock provides the WHERE tag, which makes it easy to test multiple branches in a tabled manner

The following comparison diagram is a unit test of “get date of birth, gender and age by ID number”. On the left is Junit and on the right is Spock. The same function is shown in red. Click to enlarge to see the differences)

Comparison results:

The single test code written with Spock on the right column has simple syntax, and the table-based test covering multi-branch scenarios is more intuitive, which improves development efficiency and is more suitable for agile development

(The syntax of Spock code will be explained in a future article.)

Ii. Unit test code readability and post-maintenance

In microservice architecture, many scenarios need to rely on the results returned by other interfaces to verify the logic of their own code, so mock tools are required. However, JMock or Mockito syntax is cumbersome, and single test code is not as intuitive as business code, so single test cannot be written according to business process ideas. As well as the development students do not pay attention to the readability of the single test code, which ultimately leads to the test code is difficult to read, and even more difficult to maintain

You may write your own test, and it will look like a cloud in a few days (of course, adding comments will be much better). For example, if you change the original code logic, the single test will fail, or if you add branch logic, the single test will not be covered. As the iterations of subsequent versions, the single test code will become more and more bloated and difficult to maintain

Spock provides a variety of semantic tags, such as Given, When, then, Expect, Where, with, and etc., to standardize the single-test code in terms of behavior. Each tag has a corresponding semantic, so that the structure of our single-test code has a sense of hierarchy, and the functional modules are clearly divided for later maintenance

Spock also supports the extension of third-party mock frameworks, such as Power Mock. Spock ensures that the code is more standard, modular in structure, clear in boundary range, and readable, which makes it easy to expand and maintain. It describes the test steps in natural language, so that non-technical personnel can understand the test code

For example, the following business code:

Call the user interface or get the user information from the database, then do some transformation and judgment logic (the business code here is just listing common business scenarios for demonstration purposes)

/** * User services *@authorOfficial number :Java old K * personal blog :www.javakk.com */
@Service
public class UserService {

    @Autowired
    UserDao userDao;

    @Autowired
    MoneyDAO moneyDAO;

    public UserVO getUserById(int uid){
        List<UserDTO> users = userDao.getUserInfo();
        UserDTO userDTO = users.stream().filter(u -> u.getId() == uid).findFirst().orElse(null);
        UserVO userVO = new UserVO();
        if(null == userDTO){
            return userVO;
        }
        userVO.setId(userDTO.getId());
        userVO.setName(userDTO.getName());
        userVO.setSex(userDTO.getSex());
        userVO.setAge(userDTO.getAge());
        // Display the zip code
        if("Shanghai".equals(userDTO.getProvince())){
            userVO.setAbbreviation("Shanghai");
            userVO.setPostCode(200000);
        }
        if("Beijing".equals(userDTO.getProvince())){
            userVO.setAbbreviation("Beijing");
            userVO.setPostCode(100000);
        }
        // Mobile phone number processing
        if(null! = userDTO.getTelephone() && !"".equals(userDTO.getTelephone())){
            userVO.setTelephone(userDTO.getTelephone().substring(0.3) +"* * * *"+userDTO.getTelephone().substring(7));
        }

        returnuserVO; }}Copy the code

The following comparison shows the unit tests implemented with Junit and Spock respectively. Junit is written on the left and Spock is on the right. The same function is circled in red on Junit and Spock.

Comparison results:

The junit single-test code on the left is redundant, lacks structural layers, and is not readable. With subsequent iterations, it is bound to lead to the accumulation of code, and the later maintenance cost will become higher and higher.

The single-test code on the right, Spock, will force the use of semantic tags such as Given, When, and then (at least one), otherwise the compilation will fail. This ensures that the code is more standardized, modular in structure, clear in boundary range, readable, easy to expand and maintain, and describes the test steps in natural language. Make the test code readable to non-technical people (given indicates input conditions, when triggers actions, then verifies output)

Spock’s built-in mock syntax is also very simple:

userDao.getUserInfo() >> [user1, user2]

The two right arrows “>>” indicate the result of the simulated getUserInfo interface and the groovy language. You can use the “[]” parentheses to indicate that you are returning a List (the syntax will be covered in the next article).

Unit testing is not only to achieve coverage statistics, but more importantly to verify the robustness of business code, logical rigor and rationality of design

There may be no time to write a single test at the beginning of the project in order to catch up with the schedule, or the single test written at this time is just to meet the coverage requirements (because some companies will use a single test coverage tool such as Jacoco to set a standard before release, such as 80% coverage for new code before release).

This, coupled with the fact that traditional single tests are written in a strongly typed language like Java, and the various mock underlying interfaces make writing single tests tedious and time-consuming

At this time, the single-test code written is rough and granular, and lacks effective verification of the single-test result value. Such unit test cannot play a full role in the verification and improvement of code quality, and more tests are for the sake of testing

Finally, you have to accept the result of “write a single test, but it doesn’t work”

For example, here is an example of business code:

Void (); void (); void (); void (); How do you know if the single test code is executing the statement inside the for loop (you can check by looking at coverage or breaking points, but that’s too cumbersome), and how do you make sure the amount inside the loop is calculated correctly?

You can think about writing unit tests in junit and how to verify these points.

/** * User services *@authorOfficial number :Java old K * personal blog :www.javakk.com */
@Service
public class UserService {

    @Autowired
    MoneyDAO moneyDAO;

    /** * The amount calculated according to the exchange rate *@param userVO
     */
    public void setOrderAmountByExchange(UserVO userVO){
        if(null == userVO.getUserOrders() || userVO.getUserOrders().size() <= 0) {return ;
        }
        for(OrderVO orderVO : userVO.getUserOrders()){
            BigDecimal amount = orderVO.getAmount();
            // Get the exchange rate
            BigDecimal exchange = moneyDAO.getExchangeByCountry(userVO.getCountry());
            amount = amount.multiply(exchange); // Calculate the amount according to the exchange rateorderVO.setAmount(amount); }}}Copy the code

Writing with Spock is much easier, as shown below:

Among them:

2 * moneyDAO. GetExchangeByCountry (_) > > > 0.1413 > 0.1421

This line of code calls the exchange rate interface twice in the for loop. The first exchange rate is 0.1413 and the second exchange rate is 0.1421 (simulating a real-time exchange rate interface), and then verifies with a junit assert. Verify whether the RMB price after exchange rate conversion is correct (full code will be listed in a follow-up article)

The benefits are:

To improve the controllability of the single-test code and facilitate the verification of the logic of the business code is correct and reasonable, which is a manifestation of the BDD behavior-driven development thought

Because testability is an important measure of code quality, if your code is not easily testable, consider refactoring, which is a positive side effect of unit testing

This article presents a comparison of Spock’s features and advantages in three aspects, followed by detailed explanations of Spock’s various uses (in conjunction with specific business scenarios), as well as some of Groovy’s syntax and considerations

Source: javakk.com/264.html