前言##

笔者前段时间分享过一遍文章,关于如何通过引入新注解来扩展Junit4,以解决Process上的问题:

JUnit扩展:引入新注解Annotation

最近在跟外面的同事聊的时候,得知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的实现基本上就是两大块:

  1. 如何找到所有Test Class?

主要是在运行时得到ClassPath路径String classPath = System.getProperty(getClasspathProperty()); 然后遍历该路劲下所有的文件来查找Test Class

  1. 如何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); 这行代码实现的。 其基本思路是这样的:

  1. 找到所有的ClassLoaders
  2. 遍历每一个Class Loader,然后通过方法 Enumeration<URL> dirEnumeration = classLoader.getResources(packageDirName) 来得到符合期望资源的URL
  3. 然后遍历所有URL,及其子元素,得到所有资源
  4. 遍历每一个资源,根据其文件类型-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功能比较的更多相关文章

  1. 封装WebAPI客户端,附赠Nuget打包上传VS拓展工具

    一.前言 上篇< WebAPI使用多个xml文件生成帮助文档 >有提到为什么会出现基于多个xml文件生成帮助文档的解决方案,因为定义的模型可能的用处有: 1:单元测试 2:其他项目引用(可 ...

  2. btrace拓展工具-java应用性能诊断优化利器

    Btrace是一个实时监控工具,可以无需修改应用代码(事实上它修改了字节码),来达到不可告人的秘密!这是性能调优和诊断的利器! 它可以获取应用程序代码的执行时间,他可以让你无需修改代码,帮你做时间的打 ...

  3. chrome跨域拓展工具

    下载chrome跨域扩展工具 1) http://crx.2333.me/ 扩展程序id:nlfbmbojpeacfghkpbjhddihlkkiljbi

  4. TestNG中group的用法

    TestNG中的组可以从多个类中筛选组属性相同的方法执行. 比如有两个类A和B,A中有1个方法a属于组1,B中有1个方法b也属于组1,那么我们可以通过配置TestNG文件实现把这两个类中都属于1组的方 ...

  5. 第八章| 3. MyAQL数据库|Navicat工具与pymysql模块 | 内置功能 | 索引原理

    1.Navicat工具与pymysql模块 在生产环境中操作MySQL数据库还是推荐使用命令行工具mysql,但在我们自己开发测试时,可以使用可视化工具Navicat,以图形界面的形式操作MySQL数 ...

  6. Java开发工具MyEclipse的设置自动联想功能

    最近初学Java,正在使用MyEclipse来编写新的项目,刚开始打开MyEclipse感觉这个工具既陌生又熟悉,熟悉之处在于编辑器的几大共通之处它都具备,比如说基本的设置.编辑区.调试区都是类似的, ...

  7. 为什么选择使用 Dropbox 而不是其他品牌同步工具(不要加上多余的功能,要极致和专注)

    作者:吴锋链接:http://www.zhihu.com/question/19646859/answer/14707821来源:知乎著作权归作者所有,转载请联系作者获得授权. 窃以为楼主的问题,准确 ...

  8. mfc小工具开发之定时闹钟之---功能介绍

    使用背景: 之前在xp上用过飞雪日历,感觉挺好用的,还有在音频上的兴趣,促使了我也要自己做一个简单的定时闹钟. 之前开发过图片格式的小工具,没来的急分享,后期整理后,一块奉上,写这篇介绍的时候已近完成 ...

  9. [转载]Process工具类,提供设置timeout功能

    FROM:http://segmentfault.com/blog/lidonghao/1190000000372535 在前一篇博文中,简单介绍了如何使用Process类来调用命令行的功能,那样使用 ...

随机推荐

  1. 20169210《Linux内核原理与分析》第五周作业

    本次内容分为两部分,第一部分是实验,第二部分是教材的第四章和第六章. 第一部分:实验 本次的实验内容是构造一个简单的Linux系统MenuOS,过程如下. 首先使用如下命令进入LinuxKernel ...

  2. BMVC reading list

    'Combining Local and Global Cues for Closed Contour Extraction' Vida Movahedi, James Elder 'FRIF: Fa ...

  3. eclipse通过classpath variable引用类库

    众所周知.eclipse的project bulid path中能够引用第三方类库(如图1). 图1 可是这样的方式有个缺点:对类库的引用是通过绝对路径.假设有两台电脑(办公室1台.家1台),非常可能 ...

  4. myeclipse开发代码颜色搭配保护视力

    废话不多说,这个东西主要是为了保护视力的,另外我也挺喜欢上边的颜色搭配的,今天特拿出来分享.直接上图

  5. Android获取设备隐私 忽略6.0权限管理

    1.前言 (1).由于MIUI等部分国产定制系统也有权限管理,没有相关api,故无法判断用户是否允许获取联系人等隐私.在Android 6.0之后,新增权限管理可以通过官方api判断用户的运行状态: ...

  6. mysql中enum的用法

    字段 类型 长度/值*1 整理 属性 Null 默认2 额外 注释 enum         说明:enum类型的字段,若长度值写长度1/2,报错 (1)  数据长度为1,则为0,1,2… (2)   ...

  7. 3行3列表格 table实现,div+css实现

    table实现: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://ww ...

  8. js基础知识之_对象

    javascript 对象 1.基于对象 一切皆对象,以对象的概念来编程 2.面向对象的编程(oop,Object oriented programming) 1.对象 就是人们要研究的任何事物,不仅 ...

  9. Linked Server for SQL Server 2012(x64) to Oracle Database 12c(x64)

    因为把两台数据库装了同一台机机器上,所以没有安装oracle Client的部分,Oracle部分使用netca创建的Net Service Name,使用tnsping以及登入方式的确认用户权限的以 ...

  10. [DB2]实现项目多数据库切换(上)--环境部署

    基本软硬件信息:Windows 8.1 X64 / Microsoft Visual Studio 2012  / ThinkPad S3-S431 安装工具:IBM Data Studio 4.1. ...