Junit4拓展工具JCategory与Testng的Group功能比较
前言##
笔者前段时间分享过一遍文章,关于如何通过引入新注解来扩展Junit4,以解决Process上的问题:
最近在跟外面的同事聊的时候,得知Testng中,Group的功能就能实现类似的效果,经过比较得知,果然如此!
再看JCategory##
在看Testng之前,先看看我的这个拓展,我叫它JCategory。 经过多次重构,现在结构看起来更清晰,更容易理解,并且加上了中英文的描述。 更重要的是所有代码已上传到Github:
传送门:JCategory
基本的使用方式是这样的:
首先我们可以在写Case时,用JCategory的注解来打上标签:
@Test
@Sprint("15.13")
@UserStory("US2011")
@Defect("1234")
public void testJCategory()
{
// Case Logic
}
这样就可以在执行时,用Include或者Exclude功能进行随意Filter测试用例。
比如,只跑Sprint 15.14的Test Case:
@RunWith(JCategory.class)
@IncludeSprint("15.14")
public class JCategoryTest {
}
或者,我想除了Spring 15.14,其他的Test Case都跑:
@RunWith(JCategory.class)
@ExcludeSprint("15.14")
public class JCategoryTest {
}
来看看Testng的Group功能##
看完了JCategory,我们来看看Testng.
官方文档:Test Groups
使用起来也非常简单:
@Test(groups = {"Sprint15.14", "US20134", "defect1234"})
public void testGroup(){
}
这样我们就可以用配置文件来标记哪些Test Case是我们想跑的。
同样,如果我们只想跑Sprint 15.14的Test Case,我们可以这样写我们的配置文件:
<suite name="Suite">
<test name="Test">
<groups>
<run>
<include name="Sprint15.14" />
</run>
</groups>
<packages>
<package name=".*"></package>
</packages>
</test> <!-- Test -->
</suite> <!-- Suite -->
或者仅仅不想跑Sprint 15.14的Test Case:
<suite name="Suite">
<test name="Test">
<groups>
<run>
<exclude name="Sprint15.14" />
</run>
</groups>
<packages>
<package name=".*"></package>
</packages>
</test> <!-- Test -->
</suite> <!-- Suite -->
两者从原理上讲有什么区别呢?##
JCategory的实现基本上就是两大块:
- 如何找到所有Test Class?
主要是在运行时得到ClassPath路径String classPath = System.getProperty(getClasspathProperty());
然后遍历该路劲下所有的文件来查找Test Class
- 如何Filter 上面找到的Test Class里面的Test Method?
通过自定义的各种条件,比如Sprint,UserStory,Defect来模拟在敏捷模式下,对Test Method的各种需求,然后继承org.junit.runner.manipulation.Filter
类来定义基于上面需求的规则,以让Junit根据这些规则来判断某个Test Method是否要执行,比如Sprint 的规则:
public class FilterSprint extends Filter
{
private String value;
private Sprint sprint;
public FilterSprint(String value)
{
this.value = value;
}
@Override
public boolean shouldRun(Description description)
{
if(description.isTest())
{
sprint = description.getAnnotation(Sprint.class);
return filterRule();
}
return true;
}
public boolean filterRule() {
if(value != null && (sprint == null || !value.equalsIgnoreCase(sprint.value())))
return false;
return true;
}
}
这里要提一下,研究过Junit源码的同学可能注意到,Junit本身是不提供查找Test Class的功能的,我们在使用Junit的框架时,要把Test Class传给Junit,然后它才能帮我们执行这个Test Class. (这里就涉及到Junit的入口在哪,具体可以参考我以前的一篇博客: Junit4的入口在哪?)
可能有同学要问,我在Eclipse里不用指定文件,直接选择Project然后右键Run As Junit Test,就可以直接跑所有Case,那是为什么呢?这其实是Eclipse上的Junit插件帮我们完成了找Test Class这个功能。
而Testng不同,它是可以基于XML文件来执行Case,所有它必须提供查找Test Class的功能。那Testng是如何做的呢?我找到了下面,相关功能的源码:
private List<XmlClass> initializeXmlClasses()
{
List<XmlClass> result= Lists.newArrayList();
try {
String[] classes = PackageUtils.findClassesInPackage(m_name, m_include, m_exclude);
int index = 0;
for(String className: classes) {
result.add(new XmlClass(className, index++, false /* don't load classes */));
}
}
catch(IOException ioex) {
Utils.log("XmlPackage", 1, ioex.getMessage());
}
return result;
}
Testng是基于XML里面的配置来查找Test Class的,具体实现是由String[] classes = PackageUtils.findClassesInPackage(m_name, m_include, m_exclude);
这行代码实现的。 其基本思路是这样的:
- 找到所有的ClassLoaders
- 遍历每一个Class Loader,然后通过方法
Enumeration<URL> dirEnumeration = classLoader.getResources(packageDirName)
来得到符合期望资源的URL - 然后遍历所有URL,及其子元素,得到所有资源
- 遍历每一个资源,根据其文件类型-file,jar或者bundleresource,来找出其中包含的Test Class。
详情可以参考源码:
public static String[] findClassesInPackage(String packageName,
List<String> included, List<String> excluded)
throws IOException
{
String packageOnly = packageName;
boolean recursive = false;
if (packageName.endsWith(".*")) {
packageOnly = packageName.substring(0, packageName.lastIndexOf(".*"));
recursive = true;
}
List<String> vResult = Lists.newArrayList();
String packageDirName = packageOnly.replace('.', '/') + (packageOnly.length() > 0 ? "/" : "");
Vector<URL> dirs = new Vector<>();
// go through additional class loaders
Vector<ClassLoader> allClassLoaders = new Vector<>();
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
if (contextClassLoader != null) {
allClassLoaders.add(contextClassLoader);
}
if (m_classLoaders != null) {
allClassLoaders.addAll(m_classLoaders);
}
int count = 0;
for (ClassLoader classLoader : allClassLoaders) {
++count;
if (null == classLoader) {
continue;
}
Enumeration<URL> dirEnumeration = classLoader.getResources(packageDirName);
while(dirEnumeration.hasMoreElements()){
URL dir = dirEnumeration.nextElement();
dirs.add(dir);
}
}
Iterator<URL> dirIterator = dirs.iterator();
while (dirIterator.hasNext()) {
URL url = dirIterator.next();
String protocol = url.getProtocol();
if(!matchTestClasspath(url, packageDirName, recursive)) {
continue;
}
if ("file".equals(protocol)) {
findClassesInDirPackage(packageOnly, included, excluded,
URLDecoder.decode(url.getFile(), "UTF-8"),
recursive,
vResult);
}
else if ("jar".equals(protocol)) {
JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile();
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String name = entry.getName();
if (name.charAt(0) == '/') {
name = name.substring(1);
}
if (name.startsWith(packageDirName)) {
int idx = name.lastIndexOf('/');
if (idx != -1) {
packageName = name.substring(0, idx).replace('/', '.');
}
if (recursive || packageName.equals(packageOnly)) {
//it's not inside a deeper dir
Utils.log("PackageUtils", 4, "Package name is " + packageName);
if (name.endsWith(".class") && !entry.isDirectory()) {
String className = name.substring(packageName.length() + 1, name.length() - 6);
Utils.log("PackageUtils", 4, "Found class " + className + ", seeing it if it's included or excluded");
includeOrExcludeClass(packageName, className, included, excluded, vResult);
}
}
}
}
}
else if ("bundleresource".equals(protocol)) {
try {
Class params[] = {};
// BundleURLConnection
URLConnection connection = url.openConnection();
Method thisMethod = url.openConnection().getClass()
.getDeclaredMethod("getFileURL", params);
Object paramsObj[] = {};
URL fileUrl = (URL) thisMethod.invoke(connection, paramsObj);
findClassesInDirPackage(packageOnly, included, excluded,
URLDecoder.decode(fileUrl.getFile(), "UTF-8"), recursive, vResult);
} catch (Exception ex) {
// ignore - probably not an Eclipse OSGi bundle
}
}
}
String[] result = vResult.toArray(new String[vResult.size()]);
return result;
}
至于Testng如何筛选Test Method,这个就比较简单了,当它得到所有Test Class时,就可以通过反射得到这些Class的所有Methods,和其Groups注解的对应值,然后用调用正则匹配就可以判断这个Test Method是否是期望的了,具体可以参考类org.testng.internal.MethodGroupsHelper
的实现。
从上面看JCategory和Testng解决的问题是一致的,但具体实现细节有所差别,总的来讲:
- JCategory是根据ClassPath路径来查找Test Class,而Testng是通过ClassLoader的getResource方法来确定路径的
- 对于如何Filter Test Method,因为JCategory只是Junit的一个拓展,自然而然的,它要用的Junit的功能,所以它使用了Junit的
org.junit.runner.manipulation.Filter
接口来定义规则。而Testng是自定义的,value都是String类型,所以它直接用正则匹配就可以判断是否期望,比如pattern.matcher(group).matches()
JCategory和Testng的Group功能哪个更好?##
好吧,已经很明显了,Testng更强大,更灵活,因为Groups里的value值可以自由匹配。不像JCategory只有预定义的几个固定注解。
但其实这也是Junit与Testng设计理念上的不同所导致的差异,Junit目标就是在单元测试领域,但是Testng希望适用于更丰富的测试场景。
所以这里建议,如果你的项目可以使用Testng,那最好。如果必须用Junit,并且有类似的需求,可以考虑下JCategory,或者是参考JCategory和Testng的Group功能,自己做一个Junit的Group拓展功能。
Contact me ?
Email: jinsdu@outlook.com
Blog: http://www.cnblogs.com/jinsdu/
Github: https://github.com/CarlJi
童鞋,如果觉得本文还算用心,还算有用,何不点个赞呢(⊙o⊙)?
Junit4拓展工具JCategory与Testng的Group功能比较的更多相关文章
- 封装WebAPI客户端,附赠Nuget打包上传VS拓展工具
一.前言 上篇< WebAPI使用多个xml文件生成帮助文档 >有提到为什么会出现基于多个xml文件生成帮助文档的解决方案,因为定义的模型可能的用处有: 1:单元测试 2:其他项目引用(可 ...
- btrace拓展工具-java应用性能诊断优化利器
Btrace是一个实时监控工具,可以无需修改应用代码(事实上它修改了字节码),来达到不可告人的秘密!这是性能调优和诊断的利器! 它可以获取应用程序代码的执行时间,他可以让你无需修改代码,帮你做时间的打 ...
- chrome跨域拓展工具
下载chrome跨域扩展工具 1) http://crx.2333.me/ 扩展程序id:nlfbmbojpeacfghkpbjhddihlkkiljbi
- TestNG中group的用法
TestNG中的组可以从多个类中筛选组属性相同的方法执行. 比如有两个类A和B,A中有1个方法a属于组1,B中有1个方法b也属于组1,那么我们可以通过配置TestNG文件实现把这两个类中都属于1组的方 ...
- 第八章| 3. MyAQL数据库|Navicat工具与pymysql模块 | 内置功能 | 索引原理
1.Navicat工具与pymysql模块 在生产环境中操作MySQL数据库还是推荐使用命令行工具mysql,但在我们自己开发测试时,可以使用可视化工具Navicat,以图形界面的形式操作MySQL数 ...
- Java开发工具MyEclipse的设置自动联想功能
最近初学Java,正在使用MyEclipse来编写新的项目,刚开始打开MyEclipse感觉这个工具既陌生又熟悉,熟悉之处在于编辑器的几大共通之处它都具备,比如说基本的设置.编辑区.调试区都是类似的, ...
- 为什么选择使用 Dropbox 而不是其他品牌同步工具(不要加上多余的功能,要极致和专注)
作者:吴锋链接:http://www.zhihu.com/question/19646859/answer/14707821来源:知乎著作权归作者所有,转载请联系作者获得授权. 窃以为楼主的问题,准确 ...
- mfc小工具开发之定时闹钟之---功能介绍
使用背景: 之前在xp上用过飞雪日历,感觉挺好用的,还有在音频上的兴趣,促使了我也要自己做一个简单的定时闹钟. 之前开发过图片格式的小工具,没来的急分享,后期整理后,一块奉上,写这篇介绍的时候已近完成 ...
- [转载]Process工具类,提供设置timeout功能
FROM:http://segmentfault.com/blog/lidonghao/1190000000372535 在前一篇博文中,简单介绍了如何使用Process类来调用命令行的功能,那样使用 ...
随机推荐
- 20169210《Linux内核原理与分析》第五周作业
本次内容分为两部分,第一部分是实验,第二部分是教材的第四章和第六章. 第一部分:实验 本次的实验内容是构造一个简单的Linux系统MenuOS,过程如下. 首先使用如下命令进入LinuxKernel ...
- BMVC reading list
'Combining Local and Global Cues for Closed Contour Extraction' Vida Movahedi, James Elder 'FRIF: Fa ...
- eclipse通过classpath variable引用类库
众所周知.eclipse的project bulid path中能够引用第三方类库(如图1). 图1 可是这样的方式有个缺点:对类库的引用是通过绝对路径.假设有两台电脑(办公室1台.家1台),非常可能 ...
- myeclipse开发代码颜色搭配保护视力
废话不多说,这个东西主要是为了保护视力的,另外我也挺喜欢上边的颜色搭配的,今天特拿出来分享.直接上图
- Android获取设备隐私 忽略6.0权限管理
1.前言 (1).由于MIUI等部分国产定制系统也有权限管理,没有相关api,故无法判断用户是否允许获取联系人等隐私.在Android 6.0之后,新增权限管理可以通过官方api判断用户的运行状态: ...
- mysql中enum的用法
字段 类型 长度/值*1 整理 属性 Null 默认2 额外 注释 enum 说明:enum类型的字段,若长度值写长度1/2,报错 (1) 数据长度为1,则为0,1,2… (2) ...
- 3行3列表格 table实现,div+css实现
table实现: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://ww ...
- js基础知识之_对象
javascript 对象 1.基于对象 一切皆对象,以对象的概念来编程 2.面向对象的编程(oop,Object oriented programming) 1.对象 就是人们要研究的任何事物,不仅 ...
- Linked Server for SQL Server 2012(x64) to Oracle Database 12c(x64)
因为把两台数据库装了同一台机机器上,所以没有安装oracle Client的部分,Oracle部分使用netca创建的Net Service Name,使用tnsping以及登入方式的确认用户权限的以 ...
- [DB2]实现项目多数据库切换(上)--环境部署
基本软硬件信息:Windows 8.1 X64 / Microsoft Visual Studio 2012 / ThinkPad S3-S431 安装工具:IBM Data Studio 4.1. ...