利用反射模仿JUnit写一个自己的测试驱动类
动机
这几天为了学Hibernate的集合类型映射回头复习了一下JCF(Java Collection Framework),于是不可避免的写了很多小程序,比如下面这个TestMap类用来复习Map:
package sample.map;
import java.util.HashMap;
import java.util.Map;
/**
*
* @author CodingMyWorld
*/
public class TestMap {
//Map最常规的put与get
private void test1() {
Map map = new HashMap();
map.put("1", "Monday");
map.put("2", "Tuesday");
map.put("3", "wednesday");
map.put("4", "Thursday");
System.out.println("size : " + map.size());
System.out.println("map[\"2\"] : " + map.get("2"));
}
//Map中的键值不能重复
private void test2() {
Map map = new HashMap();
map.put("1", "Mon.");
map.put("1", "Monday");
map.put("one", "Monday");
System.out.println("size : " + map.size());
System.out.println("map : " + map);
}
//遍历Map中的键值对
private void test3() {
Map map = new HashMap();
map.put("1", "Monday");
map.put("2", "Tuesday");
map.put("3", "wednesday");
map.put("4", "Thursday");
for(Object key : map.keySet()) {
System.out.println(key + " : " + map.get(key));
}
}
// main方法。。。
}
上面的代码毫无亮点,相信大家平时也会经常写一些类似这样的小程序来温故知新。真正吸引我注意力的下面这段代码:
public static void main(String[] args) {
TestMap test = new TestMap();
test.test1();
System.out.println("=================================");
test.test2();
System.out.println("=================================");
test.test3();
}
由于我写了很多这样的类,不可避免的我需要在每个类中的main方法都重复如上的代码。这样的代码写多了就让人恶心,一扫coding的乐趣。于是我突发奇想,既然我的目的只是运行每个小程序中的test方法查看输出,为什么不写一个驱动类来搞定呢?当然,我可以用JUnit来实现,但是既然要求如此简单明确,自己写一个又何妨,全当练手啦,顺便还能复习一下Reflection API。
思路
1. 利用一个Map保存要运行的测试用例类及其测试方法。
2. 通过反射为每个测试用例类构造一个实例。
3. 通过反射筛选出测试用例中的测试方法。测试方法需满足:1.方法名为testXxx 2.方法无参
4. 调用每个测试方法,并做一些统计
5. 适当的Exception Handling
执行
有了思路,做什么心里都比较有底了。不过暂时还不急着做。由于反射平时用的不是很多,先去学习一下还是很必要的,个人推荐Sun的tutorial,写得非常得细致。下面的资源推荐给对反射不太熟的朋友:
1.Sun的官方教程,内容最全面。《The Java Toturial. The Reflection API》
2. 也是Sun的一个教程,比较基础,快速入门可以看这个。《Using Java Reflection》
3. 阅读英语吃力的话,博客园的这篇也不错,。《Java反射机制的学习》
把反射搞懂之后再写这个程序就很Easy了,我直接贴上我的代码,欢迎各位在下面的评论中指正。
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
*
* @author CodingMyWorld
* 自定义测试驱动类
*/
public class MyTestRunner {
// 用于存放TestCase及其中的TestMethods
private Map<Class<?>, List<Method>> mTestCases = new HashMap<Class<?>, List<Method>>();
private static final String WARING_FMT = "警告: %s%n";
private static final String ERROR_FMT = "错误: %s%n%s%n";
// 增加一个测试用例类
public MyTestRunner addTestCase(Class testcase) {
List<Method> testMethods = findTestMethods(testcase);
if (testMethods.isEmpty()) {
System.out.format(WARING_FMT,
"TestCase '" + testcase.getName() + "' 没有定义测试方法");
} else {
mTestCases.put(testcase, testMethods);
}
return this;
}
// 返回某个测试用例中的测试方法
// 测试方法必须满足:1.方法名为testXxx 2.无参
private List<Method> findTestMethods(Class<?> testcase) {
// 得到类中定义的所有方法,包括private
Method[] methods = testcase.getDeclaredMethods();
List<Method> result = new ArrayList<Method>();
for (Method method : methods) {
if (method.getName().matches("test[A-Z\\d].*") && method.getParameterTypes().length == 0) {
result.add(method);
}
}
return result;
}
public void run() {
if (mTestCases.isEmpty()) {
System.out.format(WARING_FMT, "没有可运行的测试用例。");
return;
}
int succeedTCNum = 0;
System.out.format("开始执行。共有%d个测试用例%n", mTestCases.size());
for (Class<?> testcase : mTestCases.keySet()) {
// 1.构造实例
Object instance = null;
try {
Constructor constructor = testcase.getDeclaredConstructor();
constructor.setAccessible(true);
instance = constructor.newInstance();
} catch (NoSuchMethodException ex) {
System.err.format(ERROR_FMT, testcase.getName() + "没有无参的构造器!", ex.getCause().getMessage());
} catch (InvocationTargetException ex) {
System.err.format(ERROR_FMT, testcase.getName() + "构造发生异常!", ex.getCause().getMessage());
} catch (Exception ex) {
System.err.format(ERROR_FMT, testcase.getName(), ex.getMessage());
}
if (instance == null) {
continue;
}
System.out.format("%n==>TestCase:%s%n", testcase.getName());
// 2.执行每个测试方法
boolean isAllMethodSuccess = true;
List<Method> methods = mTestCases.get(testcase);
for (Method method : methods) {
System.out.format("----方法%s%n", method.getName());
try {
method.setAccessible(true);
method.invoke(instance);
} catch (InvocationTargetException ex) {
isAllMethodSuccess = false;
System.err.format(ERROR_FMT, method.getName() + "方法发生异常!", ex.getCause().getMessage());
} catch (Exception ex) {
isAllMethodSuccess = false;
System.err.format(ERROR_FMT, method.getName(), ex.getMessage());
}
}
if(isAllMethodSuccess) {
succeedTCNum++;
System.out.format("<==完成!%n");
}
}
System.out.format("%n执行完毕。共有%d个测试用例通过测试!", succeedTCNum);
}
}
有了该驱动类,就可以同时对多个类进行测试了,例如要测试TestMap和TestSet这两个类
package sample.map;
import java.util.HashMap;
import java.util.Map;
/**
*
* @author CodingMyWorld
*/
public class TestMap {
//Map最常规的put与get
private void test1() {
Map map = new HashMap();
map.put("1", "Monday");
map.put("2", "Tuesday");
map.put("3", "wednesday");
map.put("4", "Thursday");
System.out.println("size : " + map.size());
System.out.println("map[\"2\"] : " + map.get("2"));
}
//Map中的键值不能重复
private void test2() {
Map map = new HashMap();
map.put("1", "Mon.");
map.put("1", "Monday");
map.put("one", "Monday");
System.out.println("size : " + map.size());
System.out.println("map : " + map);
}
//遍历Map中的键值对
private void test3() {
Map map = new HashMap();
map.put("1", "Monday");
map.put("2", "Tuesday");
map.put("3", "wednesday");
map.put("4", "Thursday");
for(Object key : map.keySet()) {
System.out.println(key + " : " + map.get(key));
}
}
}
package sample.set;
import java.util.HashSet;
import java.util.Set;
/**
*
* @author CodingMyWorld
*/
public class TestSet {
// 说明Set中存放的是对象的引用,不能有重复对象
private void testElementStore() {
Set set = new HashSet();
String s1 = "hello";
String s2 = s1;
String s3 = "world";
set.add(s1);
set.add(s2);
set.add(s3);
System.out.println("set1 size : " + set.size());
System.out.println("set1 : " + set);
}
// Set使用equals比较两个元素是否相等
private void testElementEqual() {
Set set = new HashSet();
String s1 = "hello";
String s2 = "hello";
set.add(s1);
set.add(s2);
System.out.println("set2 size : " + set.size());
System.out.println("set2 : " + set);
}
}
我现在只需要在任何一个main方法中这么写就行了,比之前手动挨个调用方法方便了很多。
public static void main(String[] args) {
new MyTestRunner()
.addTestCase(TestMap.class)
.addTestCase(TestSet.class)
.run();
}
最后,看到运行结果整齐的输出到控制台,心情大好so(≧v≦)o~~
开始执行。共有2个测试用例
==>TestCase:sample.set.TestSet
----方法testElementStore
set1 size : 2
set1 : [hello, world]
----方法testElementEqual
set2 size : 1
set2 : [hello]
<==完成!
==>TestCase:sample.map.TestMap
----方法test1
size : 4
map["2"] : Tuesday
----方法test2
size : 2
map : {1=Monday, one=Monday}
----方法test3
3 : wednesday
2 : Tuesday
1 : Monday
4 : Thursday
<==完成!
执行完毕。共有2个测试用例通过测试!
觉得还不错的话给个推荐哦亲O(∩_∩)O~