最近遇到一个单元测试的问题,本周正好学个了一个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();
}
}
}
}

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

  1. c#利用泛型集合,为自己偷偷懒。

    有人说"越懒"的程序员进步的越快!其实还挺有道理.亲身体验,从刚出来工作到现在,自己变"懒"了许多,但感觉写出来的代码确有了不少提升.刚开始啊,同样的代码,赋值 ...

  2. STAR法则的感想

    STAR法则百度百科上被解释为,面试官用于收集面试者信息的工具,而我个人理解,它更像是一个表达技巧,叙述结构,我们先来看看什么是STAR法则: STAR法则,即为Situation Task Acti ...

  3. Ng ML笔记

    目录 一.线性回归 1,假设函数.代价函数,梯度下降 2,特征处理 3,代价函数和学习速率 4,特征和多项式回归 5,正规方程 二.逻辑回归(Logistic Regression,LR) 1,假设函 ...

  4. 前端开发:css技巧,如何设置select、radio 、 checkbox 、file这些不可直接设置的样式 。

    前言: 都说程序员有三宝:人傻,钱多,死得早.博主身边的程序“猿”一大半应了这三宝,这从侧面说明了一个问题,只有理性是过不好日子的.朋友们应该把工作与生活分开,让生活变得感性,让工作变得理性,两者相提 ...

  5. String Reduction

    问题出自这里 问题描述: Given a string consisting of a,b and c's, we can perform the following operation: Take ...

  6. 『Tarjan算法 无向图的割点与割边』

    无向图的割点与割边 定义:给定无相连通图\(G=(V,E)\) 若对于\(x \in V\),从图中删去节点\(x\)以及所有与\(x\)关联的边后,\(G\)分裂为两个或以上不连通的子图,则称\(x ...

  7. (计算几何基础 叉积) nyoj68-三点顺序

    68-三点顺序 内存限制:64MB 时间限制:1000ms 特判: No通过数:27 提交数:43 难度:3 题目描述: 现在给你不共线的三个点A,B,C的坐标,它们一定能组成一个三角形,现在让你判断 ...

  8. hihoCoder 1515 分数调查(带权并查集)

    http://hihocoder.com/problemset/problem/1515 题意: 思路: 带权并查集的简单题,计算的时候利用向量法则即可. #include<iostream&g ...

  9. Python教你找到最心仪的对象

    规则 单身妹妹到了适婚年龄,要选对象.候选男子100名,都是单身妹妹没有见过的.百人以随机顺序,从单身妹妹面前逐一经过.每当一位男子在单身妹妹面前经过时,单身妹妹要么选他为配偶,要么不选.如果选他,其 ...

随机推荐

  1. css浮雕效果

    浮雕效果 今天看百度地图看到了一个效果 感觉这个效果用在网页上应该蛮赞的,于是就学习了一下 浮雕效果需要用到伸缩盒的知识(flex) flex在chrome是完全支持的,要加-webkit-前缀,其他 ...

  2. Atitit GRASP(General Responsibility Assignment Software Patterns),中文名称为“通用职责分配软件模式”

    Atitit GRASP(General Responsibility Assignment Software Patterns),中文名称为"通用职责分配软件模式" 1. GRA ...

  3. 使用angular中ng-repeat , track by的用处

    我们见到最简单的例子是: <div ng-repeat="link in links" ></div> 如果item的值有重复的,比如links=[&quo ...

  4. SQL Server 更改跟踪(Chang Tracking)监控表数据

    一.本文所涉及的内容(Contents) 本文所涉及的内容(Contents) 背景(Contexts) 主要区别与对比(Compare) 实现监控表数据步骤(Process) 参考文献(Refere ...

  5. CSS系列:CSS选择器

    选择器(selector)是CSS中很重要的概念,所有HTML语言中的标记样式都是通过不同的CSS选择器来控制的.用户只需要通过选择对不同的HTML标签进行选择,并赋予各种样式声明,即可实现各种效果. ...

  6. 前端工程优化:javascript的优化小结

     我觉得优化javascript是一门高深的学问,在这里也只能站在前人的肩膀上,说一些我浅显的认识,更希望的是抛钻引玉,如有不对,敬请斧正. 首先,要认识到是,优化js的关键之处在于,优化它的运行速度 ...

  7. Objective-C精选字符串处理方法

    无论是什么编程语言对字符串的操作是少不了的,对复杂的字符串的分析和操作我们可以用正则表达式来达到我们的目的.简单的字符串处理我们可以借助OC中NSString封装好的字符串处理方法,不过前提是你得了解 ...

  8. C++ 连接数据库的入口和获取列数、数据

    这里不具体放出完整的程序,分享两个核心函数: 由于这里用到的函数是编译器自己的库所没有的,需要自己下载mysql.h库或者本地有数据库,可以去bin找到,放进去. 前提,我自己的测试数据库是WampS ...

  9. js 对闭包的理解

    <!DOCTYPE html> <html> <body> <p>局部变量计数.</p> <button type="but ...

  10. C# 操作数据库表和数据库

    <1>c#创建数据库表: private void CreatTable(string name)      //创建数据库源数据表,name为表名 { con.ConnectionStr ...