利用反射模仿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  

你可能感兴趣的:(JUnit)