利用反射模仿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了,我直接贴上我的代码,欢迎各位在下面的评论中指正

package sample.runner;

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这两个类

View Code
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));
}
}
}
View Code
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~


欢迎转载,但是转载请注明出处http://www.cnblogs.com/codingmyworld/archive/2011/10/03/2198539.html  
posted @ 2011-10-03 16:02  CodingMyWorld  阅读(3341)  评论(2编辑  收藏  举报