利用SCORE法则来总结一次偷懒的单元测试过程

最近遇到一个单元测试的问题,本周正好学个了一个SCORE法则,这里正好练练手应用此法则将问题的前因后果分享给大家。

S:背景
  代码要有单元测试,检测的标准就是统计代码的单元测试覆盖率,程序员需要达到指定的最低覆盖率要求。

  C:冲突,或者叫问题吧

项目结构与代码扫描工具的特殊关系导致需要额外写更多的单元测试,因为目前开发管理部门的代码描述配置的是按JAVA工程来扫描,并不能将多个工程当成一个整体来扫描。

我的一个项目将接口以及实体对象单独成立为一个JAVA工程,整个项目分成两个JAVA工程:

  • 接口以及实体,工程名称为core
  • 业务逻辑以及数据持久化的实现,工程名称为service,依赖上面的core

一般情况下,由于core里面只包含接口以及实体,所以我没有意识到去写单元测试,因为单元测试的价值会比较小,无非就是测试实体是否可以序列化,如果实现了JSR303,那么这些校验的逻辑可能也有点测试的价值。由于我们的service依赖core,在为service写单元测试时,实际上已经调用了接口以及实体,理论上是不需要再为core去写单元测试的。但核心问题时代码扫描工具目前开发管理部门做的还没这么智能,它是以单个JAVA工程来统计单元测试覆盖率的,针对我们的结构如果只在service中写单元测试,那么有效的代码覆盖行只会统计service项目中的,至于调用的core项目中的代码并不包含在其中。而core的这些接口以及实体所占的代码行还是有一定分量的,如果不将这些统计进来那么想达到高的覆盖率还是比较费劲的,除非你有大把的时间去写。

 O:选择的方案
  实体对象无非就是一些get,set成本的方法,要想测试它们我们可以利用序列化机制,对象序列化成字符串会完成get调用,反过来将字符串序列化成对象会完成set的调用,那如何实现呢?

  • 为每个实体对象,编写单元测试,实例化对象,最后完成序列化与反序列化。

优点:可以精确的控制每个属性的值
         缺点:需要编写众多单元测试,时间成本高,且新增加实体类就意味着要编写新的单元测试,删除或者修改也会影响。

  • 利用反射机制,动态计算工程中的实体,自动去完成序列化与反序列化。

优点:省事,只需要少量代码即可完成所有实体类的单元测试工作,且不会因为新增加实体量而编写单元测试
         缺点:不能精确控制实体中的特定属性的赋值,但如果有特殊案例,可再单独编写单元测试来补充。

  • 优化代码扫描工具

理论上是可行的,但有难度,而且也不灵活,工具是死的只会按照事先写好的规则去执行,比如现在的状况就是它只负责按单个JAVA工程去扫描。

R:结果
  从笔记的标题可以看出来,我肯定是选择了方案2这种偷懒的做法,针对这类实体类的测试做到了不随实体类的增加与减少而去变更单元测试用例,节省出来的时间价值太诱人。

E:评价,这里因为只是我个人使用,所以属于个人的一些总结吧
  在需要满足公司的代码规矩的时候,需要注意自己的实现方法,尽量提高效率,偷懒才会更加放松愉快的工作。


  实现过程:输入一个包含实体类的包命名空间,系统加载包下面所有类,如果是枚举调用枚举方法,如果是非枚举生成默认实例对象并完成序列化与反序列化。

  • 按指定的package加载类,传递一个包的命名空间,返回此包下面所有类。此段代码是借鉴网上的,据说这是spring源码中的一部分,具体我还没有核实。
public static Set<Class<?>> getClasses(String pack) {

        Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
        boolean recursive = true;
        String packageName = pack;
        String packageDirName = packageName.replace(‘.‘, ‘/‘);
        Enumeration<URL> dirs;
        try {
            dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
            while (dirs.hasMoreElements()) {
                URL url = dirs.nextElement();
                String protocol = url.getProtocol();
                if ("file".equals(protocol)) {
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    findAndAddClassesInPackageByFile(packageName, filePath,
                            recursive, classes);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return classes;
    }

    public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, Set<Class<?>> classes) {
        File dir = new File(packagePath);
        if (!dir.exists() || !dir.isDirectory()) {
            return;
        }
        File[] dirfiles = dir.listFiles(new FileFilter() {
            public boolean accept(File file) {
                return (recursive && file.isDirectory())
                        || (file.getName().endsWith(".class"));
            }
        });
        for (File file : dirfiles) {
            if (file.isDirectory()) {
                findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive, classes);
            } else {
                String className = file.getName().substring(0,file.getName().length() - 6);
                try {
                    classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + ‘.‘ + className));
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
    }
  • 循环加载的类来做处理。因为实体对象中包含有枚举,枚举因为我们有自己固定的规则所以需要区别对待。来看看枚举的定义:

包含两个无参的实例方法与两个有参的静态方法,且继承了一个接口IEnumCodeName

public enum AppOwnerType implements IEnumCodeName {

    Enterprise(1, "Enterprise"),
    User(2, "User");

    private String name;
    private int code;

    private AppOwnerType(int code, String name) {
        this.name = name;
        this.code = code;
    }

    public static AppOwnerType getByCode(int code) {
        return EnumHelper.getByCode(AppOwnerType.class, code);
    }

    public static AppOwnerType getByName(String name) {
        return EnumHelper.getByName(AppOwnerType.class, name);
    }

    public String getName() {
        return name;
    }

    @Override
    public int getCode() {
        return code;
    }

    public static void main(String a[]){
        System.out.println(AppOwnerType.Enterprise.getName());
    }
}

判断当前类为是否是上面我们定义的枚举,通过是否实现IEnumCodeName接口为依据。这里可以看出来在项目中为枚举定义一个接口是多么的重要

private boolean isEnumCodeNameByObj(Class<?> classObj){
        Class<?>[] interfaces=classObj.getInterfaces();
        if(null==interfaces||interfaces.length==0){
            return false;
        }
        List<Class<?>> interfaceList=Lists.newArrayList(interfaces);
        Object enumCodeNameObj=Iterables.find(interfaceList, new Predicate<Class<?>>() {
            @Override
            public boolean apply(Class<?> input) {
                return input.getName().indexOf("IEnumCodeName")!=-1;
            }
        },null);
        return null!=enumCodeNameObj;
    }
    • 如果类为枚举,执行枚举方法的测试。
private void testEnum(Class<?> classObj) throws Exception {
        EnumHelper.IEnumCodeName enumCodeName=ClassloadHelper.getFirstEnumByClass(classObj);
        Method[] methods= classObj.getMethods();
        if(null!=enumCodeName) {
            Method methodCode = classObj.getMethod("getByCode",new Class[]{int.class});
            methodCode.invoke(null,enumCodeName.getCode());
            Method methodName = classObj.getMethod("getByName",new Class[]{String.class});
            methodName.invoke(null,enumCodeName.getName());
        }

    }
    • 如果类是非枚举,生成默认的实例然后再调用序列化与反序列化。(JsonHelper是封装的jackson,这里就不贴了)
private void testObj(Class<?> classObj) throws Exception {

        Object obj = classObj.newInstance();
        String jsonString = JsonHelper.toJsonString(obj);
        Object objNew = JsonHelper.json2Object(jsonString,classObj);
        Assert.isTrue(null!=objNew);
        Assert.isTrue(!StringUtils.isBlank(jsonString));
    }
  • 单元测试代码:
@Test
    public void testPojo() throws Exception {
        Set<Class<?>> classes=ClassloadHelper.getClasses("xxx.core.model");
        if(null!=classes){
            for(Class classObj:classes){
                try {
                    boolean isEnumCodeName=this.isEnumCodeNameByObj(classObj);
                    if(isEnumCodeName) {
                        this.testEnum(classObj);
                    }
                    else {
                        this.testObj(classObj);
                    }
                }
                catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }

时间: 2024-10-17 17:26:49

利用SCORE法则来总结一次偷懒的单元测试过程的相关文章

世界排名第一畅销书《秘密》吸引力法则

一.秘密的揭露1. 生命的伟大秘密就是吸引力法则.2.  吸引力法则说“同类相吸”.因此当你有了一个思想,你也会吸引同类的思想过来.3.  思想是具有磁性的,并且有着某种频率.当你思考时,那些思想就发送到宇宙中,然后吸引所有同频率的同类事物.所有发出的思想,都会回到源头──你.4.  你就像是一座“人体发射台”,用你的思想传送某种频率.如果想改变生命中的任何事,就借由改变你的思想来转换频率.5.  你当下的思想正在创造你的未来.你最常想的.或者最长把焦点放在上面的,将会出现在你的生命中,成为你的

值得收藏!16段代码入门Python循环语句

[ 导读 ]本文重点讲述for语句和while语句.for语句属于遍历循环,while语句属于当型循环.除了两个循环语句外,还介绍了break.continue与pass三个用于控制循环结构中的程序流向的语句.在此基础之上,也介绍了列表推导式,这是一种特殊的循环语句. 循环语句又称为重复结构,用于反复执行某一操作.面对大数量级的重复运算,即使借助计算机,重复编写代码也是费时的,这时就需要借助循环语句.使用循环语句一般要用到条件判断,根据判断式的返回值决定是否执行循环体. 循环分为两种模式,一种是

MongoDB---前世今生

MongoDB的官方文档基本是how to do的介绍,而关于how it worked却少之又少,本人也刚买了<MongoDB TheDefinitive Guide>的影印版,还没来得及看,本文原作者将其书中一些关于MongoDB内部现实方面的一些知识介绍如下,值得一看. 今天下载了<MongoDB The Definitive Guide>电子版,浏览了里面的内容,还是挺丰富的.是官网文档实际应用方面的一个补充.和官方文档类似,介绍MongoDB的内部原理是少之又少,只有在附

图像中区域生长算法的详解和实现

区域生长算法的基本思想是将有相似性质的像素点合并到一起.对每一个区域要先指定一个种子点作为生长的起点,然后将种子点周围领域的像素点和种子点进行对比,将具有相似性质的点合并起来继续向外生长,直到没有满足条件的像素被包括进来为止.这样一个区域的生长就完成了.这个过程中有几个关键的问题: 1 给定种子点(种子点如何选取?) 种子点的选取很多时候都采用人工交互的方法实现,也有用其他方式的,比如寻找物体并提取物体内部点或者利用其它算法找到的特征点作为种子点. 2 确定在生长过程中能将相邻像素包括进来的准则

隐含马尔可夫模型HMM的中文分词器 入门-1

<pre name="code" class="sql">http://sighan.cs.uchicago.edu/bakeoff2005/ http://www.52nlp.cn/中文分词入门之资源 中文分词入门之资源 作为中文信息处理的"桥头堡",中文分词在国内的关注度似乎远远超过了自然语言处理的其他研究领域.在中文分词中,资源的重要性又不言而喻,最大匹配法等需要一个好的词表,而基于字标注的中文分词方法又需要人工加工好的分词语料

二次规划问题

解决最优化问题 :   稍微对它做一下改动,如下:      这是一个约束优化问题,更进一步说是一个二次规划问题,复习一下约束优化问题: 定义1:约束非线性问题是, 其中和都是定义在上的实值连续函数,且至少有一个是非线性的(反之为线性约束优化问题),m是一个正整数,叫做目标函数,叫做约束函数,如果目标函数是二次函数则叫做二次规划问题,由同时满足所有约束方程的点组成的集合叫做可行域,这些点叫可行点. 定义2:Farkas引理,对于给定的n维向量与b,对于任意满足  的向量P,必有的充要条件是:b在

acm课程总结报告

本学期的选修课ACM程序设计进入尾声,首先要总结的当然是感谢老师这类的客套话,良心话是真的谢谢费老耐心认真的教学,确实学到了很多东西,这一点从数据结构这门课的学习中容易看出,轻松很多. 本学期总共学习里四个专题:第一讲贪心算法,第二讲搜索,第三讲动态规划以及现在正在 努力做的图.下面我将以这四个专题为基础分别讲解ACM中所获得知识内容,感悟. 专题一贪心算法. 贪心算法包括计算活动安排的贪心算法,背包问题,删数问题.他的理论基础有三点,1,在问题的每一步选择中都采取在当前状态下最好或者最优的选择

MONGODB全面总结

关于Mongodb的全面总结,学习mongodb的人,可以从这里开始! 分类:            MongoDB2013-06-08 09:5610213人阅读评论(0)收藏举报 目录(?)[+] BSON 效率 传输性 性能 写入协议 数据文件 名字空间和盘区 内存映射存储引擎 其他 MongoDB的架构 MongoDB的特点 MongoDB的功能 MongoDB的局限性与不足 适用范围 MongoDB的不适用范围 要点 MongoDB分布式复制 MongoDB语法与现有关系型数据库SQL

高斯核函数

高斯核函数 所谓径向基函数 (Radial Basis Function 简称 RBF), 就是某种沿径向对称的标量函数.通常定义为空间中任一点x到某一中心xc之间欧氏距离的单调函数 , 可记作 k(||x-xc||), 其作用往往是局部的 , 即当x远离xc时函数取值很小. 高斯核函数 - 常用公式 最常用的径向基函数是高斯核函数 ,形式为 k(||x-xc||)=exp{- ||x-xc||^2/(2*σ)^2) } 其中xc为核函数中心,σ为函数的宽度参数 , 控制了函数的径向作用范围.