利用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名,都是单身妹妹没有见过的.百人以随机顺序,从单身妹妹面前逐一经过.每当一位男子在单身妹妹面前经过时,单身妹妹要么选他为配偶,要么不选.如果选他,其 ...
随机推荐
- k近邻(KNN)复习总结
摘要: 1.算法概述 2.算法推导 3.算法特性及优缺点 4.注意事项 5.实现和具体例子 6.适用场合内容: 1.算法概述 K近邻算法是一种基本分类和回归方法:分类时,根据其K个最近邻的训练实例的类 ...
- 大叔也学Xamarin系列
回到占占推荐博客索引 我就是我,请叫我仓储大叔 大叔听很多客户说,xamarin的资料网上太少了,是的,大叔也相信,因为大叔在学xamarin里确实很费劲,只能看看androd for java了,呵 ...
- java中对final关键字的理解以及使用场景
谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来了解final这个关键字的用法.
- WCF 安全性 之 自定义证书验证
案例下载 http://download.csdn.net/detail/woxpp/4113172 客户端调用代码 通过代理类 代理生成 参见 http://www.cnblogs.com/woxp ...
- Make Helix Curve in OpenCASCADE
Make Helix Curve in OpenCASCADE eryar@163.com Abstract. OpenCASCADE does not provide helix curve dir ...
- JavaScript 中的类方法,对象方法,Prototype方法
<script type="text/javascript"> function baseClass() { this.showMsg = function() { a ...
- java使用Executor(执行器)管理线程
一.一个实现了Runnable接口的类 class MyThread implements Runnable{ private static int num = 0; @Override public ...
- 关于Android多项目依赖在Eclipse中无法关联源代码的问题解决
被Eclipse中Android依赖项目无法关联源代码的问题困扰了许久,网上搜索了一下,终于得到解决,大大提高了开发效率. 问题描述: 项目有A,B两个Android Project组成, B是And ...
- scikit-learn K近邻法类库使用小结
在K近邻法(KNN)原理小结这篇文章,我们讨论了KNN的原理和优缺点,这里我们就从实践出发,对scikit-learn 中KNN相关的类库使用做一个小结.主要关注于类库调参时的一个经验总结. 1. s ...
- 利用xinetd进行时间同步
时间同步,往往是大型项目里一个基本的需求:如果现在有多台服务器需要时间同步为标准时间,一般有如下两种做法: 一:NTP服务 将一台能上外网的服务器配成公网时间服务器(譬如:ntp服务配置文件中的0.r ...