前言##

笔者前段时间分享过一遍文章,关于如何通过引入新注解来扩展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. Character frequency

    地址:http://www.codewars.com/kata/53e895e28f9e66a56900011a/train/python Write a function that takes a ...

  2. polygonal approximation

    Several methods and codes in the website: https://sites.google.com/site/dilipprasad/source-codes TRA ...

  3. Activiti5.16.4数据库表结构

    一.ACTIVITI 数据库E-R图(5.16.4) Activiti 5.16.4 总共有24张表,增加act_evt_log(事件日志),以及增加了对SasS的支持. 在流程定义.运行实例和历史的 ...

  4. [rxjs] Shares a single subscription -- publish()

    If have an observable and you subscribe it twice, those tow subscritions have no connection. console ...

  5. JKXY的视频内容下载工具类

    package cn.jsonlu.make.license; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONOb ...

  6. AngularJS Filter用法详解【转+实际测试】 格式化货币

    AngularJS内建了一些常用的Filter,我们一一来看一下. currencyFilter(currency): 用途:格式化货币 方法原型: function(amount, currency ...

  7. (转) Spring读书笔记-----部署我的第一个Spring项目

    一.Spring介绍 Spring是一个轻量级的Java EE容器,它也是一种从实际需求出发,着眼于轻便,灵活,易于开发,易测试和易部署的轻量级开发框架.Spring它完成了大量开发中的通用步骤,留给 ...

  8. sql存储过程通过ID删除两表中的数据。

    CREATE OR REPLACE PROCEDURE del_p --建立名为del_p 的过程 IS CURSOR get_abid --简历名为get_abid的cursor 用来存放a表的id ...

  9. sql脚本的格式

    创建表前先判断是否存在 IF OBJECT_ID(N'TableDataDictionary') IS NULL 存储过程头:--=================================== ...

  10. Oracel JDBC URL 和 Driver 的获取

    Driver 的获取 Driver Name:   oracle.jdbc.driver.OracleDriver Oracel JDBC URL的获取: URL:   jdbc:oracle:thi ...