zoukankan      html  css  js  c++  java
  • Spock单元测试框架实战指南三-If esle 多分支场景测试

    一. 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下增加一行针对的测试数据即可


    作者:Java老K
    链接:https://juejin.cn/post/6899453731843080200
    来源:掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    EntityFramework之领域驱动设计实践(二)
    博客园开始对X++语言语法高亮的支持
    一种来源于Microsoft Dynamics AX的权限管理设计思想
    使用InternalsVisibleToAttribute控制internal成员的访问
    EntityFramework之领域驱动设计实践:总结
    EntityFramework之领域驱动设计实践(九)
    EntityFramework之领域驱动设计实践(三)
    EntityFramework之领域驱动设计实践(七)
    EntityFramework之领域驱动设计实践(五)
    EntityFramework之领域驱动设计实践 (一)
  • 原文地址:https://www.cnblogs.com/java1024/p/14048625.html
Copyright © 2011-2022 走看看