Expect + Where

If the business is complex, the corresponding code implementation will have different branching logic, similar to the following pseudocode:

if () {
    if () {
        // Code logic
    } else {
        // Code logic}}else if () {
    for () {
        if () {
            // Code logic
        } else {
            // Code logic
            returnresult; }}}Copy the code

Such if else nested code is difficult to avoid for business reasons, and it can be painful and tedious to write using traditional Junit unit test code if you want to test such code to ensure that every branch logic is covered. You can use Junit’s @Parametered annotation or dataProvider approach, but it’s not intuitive or easy to debug

Here’s how Spock solves this problem in terms of specific business code, starting with business code 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

The Spock code tests this by identifying the date of birth, gender, age, and other information based on the id number entered.

class IDNumberUtilsTest extends Specification {

    @Unroll
    def "Id number :#idNo's birthday, gender, age :#result"() {
        expect: "When + then"
        IDNumberUtils.getBirAgeSex(idNo) == result

        where: "Table test for different branching logic."
        idNo                 || result
        "310168199809187333"| | ["birthday": "1998-09-18"."sex": "Male"."age": "22"]
        "320168200212084268"| | ["birthday": "2002-12-08"."sex": "Female"."age": "18"]
        "330168199301214267"| | ["birthday": "1993-01-21"."sex": "Female"."age": "27"]
        "411281870628201"| | ["birthday": "1987-06-28"."sex": "Male"."age": "33"]
        "427281730307862"| | ["birthday": "1973-03-07"."sex": "Female"."age": "47"]
        "479281691111377"| | ["birthday": "1969-11-11"."sex": "Male"."age": "51"]}}Copy the code

The expect tag is used in the first line of the body of the test method, which combines the when + then tags: “What will be done when + what will be verified?

When calling IDNumberUtils. GetBirAgeSex (idNo) approach, the verification results is the result, the result how to validate the result in the corresponding is the where a column of data, when the input parameter idNo is “310168199809187333”, Returns the result is: [” birthday “:” 1998-09-18 “, “sex”, “male”, “age” : “22”]

Expect can be used alone, without where, only in this scenario

The @unroll annotation expands each line of tests under the WHERE tag as a separate case run, plus the method body “ID number :#idNo’s date of birth, gender, age :#result”, using Groovy’s literal properties to dynamically replace string variables. In this way, it is easy to distinguish and understand the single test results of each run as follows:

Each test result corresponds to a row in the WHERE tag

In addition, intellij IDEA can be run with coverage to check the single-test coverage:

The green columns circled on the left represent code that has been covered by the single test, and the red columns are branches that have not been covered by the single test. If you need to improve coverage further, you can add one more line of test conditions to the WHERE table

(The full source code can be found in spock.)

2. Jacoco

Jacoco is a tool for unit test coverage statistics. Of course, Spock also has the function of coverage statistics. The third party Jacoco is mainly used by domestic companies, including our company, which is also using Jacoco

Of course, you can also use Spock’s own single-test coverage tool, which will be described in a later article. This article focuses on how to use Jacoco to verify that branches are fully covered

In the pom file reference jacoco plug-in: jacoco – maven – the plugin, then execute MVN test command, after the success will generate unit test coverage report in the target directory:

(Specific generation path can be set)

Open index.html with a browser and you can see all of the single-test coverage statistics:

Click on the package name to find the IDNumberUtils class we just tested. When you open it, you can see the exact coverage:

A green background means complete coverage, a yellow background means partial coverage, and a red background means no coverage

For example, the yellow else if() on line 45 indicates that two quarters of the branches are missing, although the code below it is also covered (shown in green), because our single test code did not test flag false, and Certificateno.length ()! =18, so only half (2/4) is covered.

The reason for this is that if your company sets a branch coverage requirement of more than 50%, then you need to add additional tests for this condition to your unit test code, even if the business code does not handle this exception

It doesn’t matter which single test framework you use, because it’s just a rule for branch coverage statistics, but with Spock it’s a lot easier to solve by adding a line of test data for where

Source: javakk.com/281.html