一. Expect + Where

如果业务比较复杂,对应的代码实现会有不同的分支逻辑,相似上面的伪代码:

if () {    if () {        // 代码逻辑    } else {        // 代码逻辑    }} else if () {    for () {        if () {            // 代码逻辑        } else {            // 代码逻辑            return result;        }    }}

这样的 if else 嵌套代码因为业务的起因很难防止,如果要测试这样的代码,保障笼罩到每一个分支逻辑的话,应用传统的Junit单元测试代码写起来会很苦楚和繁琐,尽管能够应用Junit的@parametered参数化注解或者dataprovider的形式,但还是不够直观,调试起来也不不便

上面就联合具体业务代码解说Spock如何解决这种问题,还是先看下业务代码逻辑:

/** * 身份证号码工具类<p> * 15位:6位地址码+6位出生年月日(900101代表1990年1月1日出世)+3位程序码 * 18位:6位地址码+8位出生年月日(19900101代表1990年1月1日出世)+3位程序码+1位校验码 * 程序码奇数分给男性,偶数分给女性。 * @author 公众号:Java老K * 集体博客:www.javakk.com */public class IDNumberUtils {    /**     * 通过身份证号码获取出生日期、性别、年龄     * @param certificateNo     * @return 返回的出生日期格局:1990-01-01 性别格局:F-女,M-男     */    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 new HashMap<>();                flag = Character.isDigit(number[x]);            }        } else if (number.length == 18) {            for (int x = 0; x < number.length - 1; x++) {                if (!flag) return new HashMap<>();                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 ? "女" : "男";            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 ? "女" : "男";            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);        return map;    }}

依据输出的身份证号码辨认出生日期、性别、年龄等信息,逻辑不简单,就是分支多,咱们来看下Spock代码是如何测试这种状况:

class IDNumberUtilsTest extends Specification {    @Unroll    def "身份证号:#idNo 的生日,性别,年龄是:#result"() {        expect: "when + then 组合"        IDNumberUtils.getBirAgeSex(idNo) == result        where: "表格形式测试不同的分支逻辑"        idNo                 || result        "310168199809187333" || ["birthday": "1998-09-18", "sex": "男", "age": "22"]        "320168200212084268" || ["birthday": "2002-12-08", "sex": "女", "age": "18"]        "330168199301214267" || ["birthday": "1993-01-21", "sex": "女", "age": "27"]        "411281870628201"    || ["birthday": "1987-06-28", "sex": "男", "age": "33"]        "427281730307862"    || ["birthday": "1973-03-07", "sex": "女", "age": "47"]        "479281691111377"    || ["birthday": "1969-11-11", "sex": "男", "age": "51"]    }}

在测试方法体的第一行应用了expect标签,它的作用是when + then标签的组合,即 "什么时候做什么 + 而后验证什么后果" 组合起来

即当调用IDNumberUtils.getBirAgeSex(idNo) 办法时,验证后果是result,result如何验证对应的就是where里的result一列的数据,当输出参数idNo是"310168199809187333"时,返回后果是: ["birthday": "1998-09-18", "sex": "男", "age": "22"]

expect能够独自应用,能够不须要where,只是在这个场景须要

@Unroll注解示意开展where标签上面的每一行测试,作为独自的case跑,再加上办法体"身份证号:#idNo 的生日,性别,年龄是:#result",应用了groovy的字面量个性,动静替换字符串变量,这样每次跑的单测后果展现也很容易辨别,不便了解,如下:

每个测试后果对应where标签里的一行

另外在intellij idea里能够run with coverage的运行形式查看单测覆盖率状况:

右边圈出的绿色柱子示意单测已笼罩的代码,红色柱子是单测还没有笼罩到的分支,如果须要进一步提高覆盖率,只需在where表格中再增加一行测试条件即可

(残缺的源码在公众号: java老k 里回复spock获取)

二. Jacoco

Jacoco是统计单元测试覆盖率的一种工具,当然Spock也自带了覆盖率统计的性能,这里应用第三方Jacoco的起因次要是国内公司应用的比拟多一些,包含咱们公司当初应用的也是Jacoco,所以为了兼容就以Jacoco来查看单测覆盖率

当然你也能够应用Spock自带的单测覆盖率工具,在前面的文章里会介绍具体如何配置,本篇次要说下如何通过Jacoco确认分支是否齐全笼罩到

在pom文件里援用jacoco的插件: jacoco-maven-plugin, 而后执行mvn test 命令,胜利后会在target目录下生成单元测试覆盖率的报告:

(具体生成门路能够设置)

应用浏览器关上index.html,就能看到所有的单测覆盖率统计指标:

点击包名找到咱们方才测试的IDNumberUtils类,关上后能够看到具体的笼罩状况:

绿色背景示意齐全笼罩,黄色是局部笼罩,红色没有笼罩到

比方第45行黄色背景的else if()判断,提醒有4分之2的分支缺失,尽管它上面的代码也被笼罩了(显示为绿色),然而因为咱们的单测代码没有测试flag为false,以及certificateNo.length()!=18的场景,所以只能算笼罩了一半(2/4)

讲这个的起因是因为如果公司设置的分支覆盖率要求大于50%,那么你就要在单元测试代码里额定减少这种状况的测试,即便业务代码里没有这样例外情况的解决

这种状况跟具体应用哪种单测框架没关系,因为这只是分支覆盖率统计的规定,只不过应用Spock的话,解决起来会更简略,只需在where下减少一行针对的测试数据即可

文章起源:http://javakk.com/281.html