利用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();
}
}
}
}
利用SCORE法则来总结一次偷懒的单元测试过程的更多相关文章
- c#利用泛型集合,为自己偷偷懒。
有人说"越懒"的程序员进步的越快!其实还挺有道理.亲身体验,从刚出来工作到现在,自己变"懒"了许多,但感觉写出来的代码确有了不少提升.刚开始啊,同样的代码,赋值 ...
- STAR法则的感想
STAR法则百度百科上被解释为,面试官用于收集面试者信息的工具,而我个人理解,它更像是一个表达技巧,叙述结构,我们先来看看什么是STAR法则: STAR法则,即为Situation Task Acti ...
- Ng ML笔记
目录 一.线性回归 1,假设函数.代价函数,梯度下降 2,特征处理 3,代价函数和学习速率 4,特征和多项式回归 5,正规方程 二.逻辑回归(Logistic Regression,LR) 1,假设函 ...
- 前端开发:css技巧,如何设置select、radio 、 checkbox 、file这些不可直接设置的样式 。
前言: 都说程序员有三宝:人傻,钱多,死得早.博主身边的程序“猿”一大半应了这三宝,这从侧面说明了一个问题,只有理性是过不好日子的.朋友们应该把工作与生活分开,让生活变得感性,让工作变得理性,两者相提 ...
- String Reduction
问题出自这里 问题描述: Given a string consisting of a,b and c's, we can perform the following operation: Take ...
- 『Tarjan算法 无向图的割点与割边』
无向图的割点与割边 定义:给定无相连通图\(G=(V,E)\) 若对于\(x \in V\),从图中删去节点\(x\)以及所有与\(x\)关联的边后,\(G\)分裂为两个或以上不连通的子图,则称\(x ...
- (计算几何基础 叉积) nyoj68-三点顺序
68-三点顺序 内存限制:64MB 时间限制:1000ms 特判: No通过数:27 提交数:43 难度:3 题目描述: 现在给你不共线的三个点A,B,C的坐标,它们一定能组成一个三角形,现在让你判断 ...
- hihoCoder 1515 分数调查(带权并查集)
http://hihocoder.com/problemset/problem/1515 题意: 思路: 带权并查集的简单题,计算的时候利用向量法则即可. #include<iostream&g ...
- Python教你找到最心仪的对象
规则 单身妹妹到了适婚年龄,要选对象.候选男子100名,都是单身妹妹没有见过的.百人以随机顺序,从单身妹妹面前逐一经过.每当一位男子在单身妹妹面前经过时,单身妹妹要么选他为配偶,要么不选.如果选他,其 ...
随机推荐
- let命令
基本用法 ES6新增了let命令,用来声明变量.它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效. 上面代码在代码块之中,分别用let和var声明了两个变量.然后在代码块之外调 ...
- 替代jquery1.9版本以前的toggle事件函数(开关)
以上文章为转载自http://blog.sina.com.cn/s/blog_50042fab0101c7a9.html var flag=1; $(".selector").cl ...
- Sql Server系列:自定义函数
用户自定义函数可以像系统函数一样在查询或存储过程中调用,可以接受参数.执行操作并将操作结果以值的形式返回.返回值可以是单个标量或结果集. 1. 标量函数 标量函数返回一个确定类型的标量值,对于多语句的 ...
- C#设计模式系列:开闭原则(Open Close Principle)
1.开闭原则简介 开闭原则对扩展开放,对修改关闭,开闭原则是面向对象设计中可复用设计的基石. 2.开闭原则的实现 实现开闭原则的关键就在于抽象,把系统的所有可能的行为抽象成一个抽象底层,这个抽象底层规 ...
- XSD文件生成C#VO实体类
最近公司要做一个项目,需要和现有的其他项目对接,由于不知道他们的数据库,只有XSD文件.所以,我们在修改相应的程序时,就需要根据他们提供的XSD文件,来写我们的VO实体类,由于我写过根据Oracle数 ...
- Android随笔之——跨进程通信(一) Activity篇
在Android应用开发中,我们会碰到跨进程通信的情况,例如:你用QQ通讯录打电话的时候会调用系统的拨号应用.某些新闻客户端可以将新闻分享到QQ.微信等应用,这些都是跨进程通信的情况.简而言之,就是一 ...
- 扩展KMP算法
一 问题定义 给定母串S和子串T,定义n为母串S的长度,m为子串T的长度,suffix[i]为第i个字符开始的母串S的后缀子串,extend[i]为suffix[i]与字串T的最长公共前缀长度.求出所 ...
- 【记录】JS 生成 URL 二维码
示例代码: <html> <head> <title>example</title> </head> <body> <sc ...
- phpstorm 63342默认端口怎么修改
phpstorm进行网页调试的时候,默认是加端口号63342,在配置本地php环境的时候默认端口不一定是63342这个,更多的是系统默认的端口号80,那么问题就出现了,如何在phpstorm中将633 ...
- js 利用throw 写的一个小程序
在下边的小程序中比较特殊的是使用isNaN()函数判断一个参数是不是数字, <!DOCTYPE html> <!DOCTYPE html> <html> <h ...