首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

无聊写了个单元测试的基类,预备拿回公司用

2012-10-07 
无聊写了个单元测试的基类,准备拿回公司用package net.daniel.testimport java.lang.annotation.ElementT

无聊写了个单元测试的基类,准备拿回公司用

package net.daniel.test;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.util.ArrayList;import java.util.Arrays;import java.util.Collection;import java.util.HashMap;import java.util.HashSet;import java.util.Iterator;import java.util.List;import java.util.Map;import java.util.Set;import org.jmock.Mockery;import org.jmock.lib.legacy.ClassImposteriser;import org.junit.Before;import org.objenesis.Objenesis;import org.objenesis.ObjenesisStd;/** * 本测试用例适用于JUnit4,提供以下功能: * 1. 通过标注(Annotation)进行测试用例对象域的创建和注入。提供以下标注: *  a)@Mock private MyType myField; 自动创建MyType类型的Mock对象,赋值给myField; *  b) @Create private MyType myField; 自动创建MyType类型的对象实例,赋值给myField; *  c)@Create @Inject private MyType myField; 自动创建MyType类型的对象,赋值给myField,并且注入到被测试用例的同名对象域中。 * 2. 可通过反射机制直接访问被测试用例(或其它对象)的不可见对象域,支持继承的对象域; * 3. 可通过反射机制直接调用被测试用例(或其它对象)的不可见方法,支持继承的方法,支持自动匹配与参数表最接近的重载方法签名,无须另行指定参数类型列表。 * 4. 支持通过Objnesis机制创建类实例 * 5. 支持按测试用例对象域名称获取合适的mockery对象类型 * 6. 提供若干集合处理的便利方法 * * @author Daniel Deng *  * @param <CUT> 被测试的类型 *             */public abstract class AbstractTestCase<CUT> {    /**     * 被测试的对象     */    protected CUT toTest;        /**     * 用于Mock接口的Mockery     */    protected Mockery interfaceMockery = new Mockery();        /**     * 用于Mock具体类的Mockery     */    protected Mockery classMockery = new Mockery(){{        setImposteriser(ClassImposteriser.INSTANCE);    }};    /**     * 当前TestCase对象域的缓存,包括祖先类中的对象域。     */    private Map<String, Field> mockedFields,                               createFields,                               injectFields,                               allFields;        /**     * 被测试对象的对象域缓存,包括祖先类中的对象域。     */    private Map<String, Field> sutFields;        /**     * 用于强行创建类实例的Objenesis对象,详情参考 http://code.google.com/p/objenesis/     */    private Objenesis objenesis;    /**     * 每次测试前执行的初始化方法,JUnit将保证此方法会在子类的@Before方法之前执行。     * 在本基类中,此方法负责处理标注与注入     * @throws Exception     */    @Before    public void init() throws Exception {        applyAnntation();        toTest = createSUT();        applyInjection();    }        /**     * 子类应覆盖此方法,创建并返回被测试类的实例。     * 此方法将在@Mock与@Create标注处理完成后才被调用。如果需要的话,在创建被测试类实例时可使用这些被标注的对象域。     */    protected abstract CUT createSUT();    /**     * 处理注入({@link Inject})标注。此方法应在createSUT()方法之后调用。     *      *      * @throws Exception     */    protected void applyInjection() throws Exception {        prepareSUTFields();        for (Field f : injectFields.values()) {            Inject injectAnno = f.getAnnotation(Inject.class);            String injectFieldName = injectAnno.value();            if (injectFieldName == null) injectFieldName = f.getName();            Field fieldToInject = sutFields.get(injectFieldName);            if (fieldToInject == null) {                throw new NoSuchFieldException("Field [" + injectFieldName + "] is not found in Class [" + toTest.getClass().getName() + "].");            }             fieldToInject.set(toTest, f.get(this));        }    }    private void prepareSUTFields() {        if (sutFields == null) {            Class<?> clazz = toTest.getClass();            sutFields = newHashMap();            while (notAtTopLevel(clazz)) {                Field[] fields = clazz.getDeclaredFields();                for (Field f : fields) {                    f.setAccessible(true);                    String fname = f.getName();                    if (!sutFields.containsKey(fname)) {                        sutFields.put(fname, f);                    }                }                clazz = clazz.getSuperclass();            }        }    }        /**     * 通过 Objenesis机制来强制创建类实例,不会执行构造方法.     * @param clazz 需要创建实例的类     * @return     */    @SuppressWarnings("unchecked")    protected <T> T forceCreate(Class<T> clazz) {        if (objenesis == null) objenesis = new ObjenesisStd();        return ((T) objenesis.getInstantiatorOf(clazz).newInstance());    }    private void performCreateFields() throws Exception {        for (Field f : createFields.values()) {            Create create = f.getAnnotation(Create.class);            assert (create != null);            Class<?> clazz = create.value();            if (clazz == Object.class) {                clazz = f.getType();            }            Constructor<?> constructor = null;            Object newInstance = normalInstanciate(clazz, constructor);            if (newInstance == null) {                newInstance = forceCreate(clazz);            }            if (newInstance != null) {                f.set(this, newInstance);            } else {                throw new RuntimeException("Type [" + clazz.getName()                        + "] of field [" + f.getName()                        + "] cannot instanciate.");            }        }    }    private Object normalInstanciate(Class<?> clazz, Constructor<?> constructor) {        Object newInstance = null;        try {            constructor = clazz.getConstructor();        } catch (Exception e) {            // Will be handled by objenesis later        }        if (constructor != null) {            try {                newInstance = clazz.newInstance();            } catch (Exception e) {                // Will be handled by objenesis later            }        }        return newInstance;    }    /**     * 处理{@link Create}与{@link Mock}标注,实例化测试用例的对象域     * @throws Exception     */    protected void applyAnntation() throws Exception {        prepareTestCaseFields();        performCreateFields();        performMockFields();    }    private void performMockFields() throws Exception{        for (Field f : mockedFields.values()) {            Class<?> clazz = f.getType();            if (clazz.isInterface()) {                f.set(this, interfaceMockery.mock(clazz));            } else {                f.set(this, classMockery.mock(clazz));            }        }    }        /**     * JMock需要根据Mock对象的类型不同而选用不同的mockery实例,容易引起混淆和错误。此方法将根据传入的测试用例对象域名称自动选用合适的mockery实例。     * @param fieldName 采用了Mock对象的测试用例对象域名称     * @return 与指定对象域类型对应的Mockery实例     * @throws Exception     */    protected Mockery mockery(String fieldName) throws Exception {        prepareTestCaseFields();        Field f = allFields.get(fieldName);        if (f == null) throw new NoSuchFieldException("Field [" + fieldName + "] is not found in Class [" + this.getClass().getName() + "]");        if (f.getType().isInterface()) {            return interfaceMockery;        } else {            return classMockery;        }    }    private void prepareTestCaseFields() {        if (mockedFields == null) {            Class<?> clazz = this.getClass();            mockedFields = newHashMap();            createFields = newHashMap();            injectFields = newHashMap();            allFields = newHashMap();            while (notAtTopLevel(clazz)) {                Field[] fields = clazz.getDeclaredFields();                for (Field f : fields) {                    f.setAccessible(true);                    String fname = f.getName();                    if (!allFields.containsKey(fname)) {                        allFields.put(fname, f);                    }                    if (f.getAnnotation(Mock.class) != null                            && !mockedFields.containsKey(fname)) {                        mockedFields.put(fname, f);                    }                    if (f.getAnnotation(Create.class) != null                            && !createFields.containsKey(fname)) {                        createFields.put(fname, f);                    }                    if (f.getAnnotation(Inject.class) != null                            && !injectFields.containsKey(fname)) {                        injectFields.put(fname, f);                    }                }                clazz = clazz.getSuperclass();            }        }    }        /**     * 绕过可见性限制,为被测试对象的对象域赋值     * @param fieldName 要赋值的对象域名称     * @param value 将要赋予对象域的值     * @throws Exception     */    protected void setSUTField(String fieldName, Object value) throws Exception {        Field f = findSUTField(fieldName);        f.set(toTest, value);    }        /**     * 绕过可见性限制,获取被测试对象的对象域取值     * @param fieldName 要获取的对象域名称     * @return 被测试对象中指定对象域的取值     * @throws Exception     */    @SuppressWarnings("unchecked")    protected <T> T getSUTField(String fieldName) throws Exception {        Field f = findSUTField(fieldName);        return (T) f.get(toTest);    }    private Field findSUTField(String fieldName) throws NoSuchFieldException {        prepareSUTFields();        Field f = sutFields.get(fieldName);        if (f == null) throw new NoSuchFieldException("Field [" + fieldName + "] is not found in Class [" + toTest.getClass().getName() + "]");        f.setAccessible(true);        return f;    }        /**     * 绕过可见性限制,为一个指定对象的对象域赋值     * @param obj 需要赋值的对象实例     * @param fieldName 要赋值的对象域名称     * @param value 将要赋予对象域的值     * @throws Exception     */    protected void setField(Object obj, String fieldName, Object value) throws Exception {        Field f = findField(obj, fieldName);        f.set(obj, value);    }        /**     * 绕过可见性限制,获取一个指定对象的对象域取值     * @param obj 需要获取对象域取值的对象实例     * @param fieldName 要获取的对象域名称     * @return 指定对象中指定对象域的取值     * @throws Exception     */    @SuppressWarnings("unchecked")    protected <T> T getField(Object obj, String fieldName) throws Exception {        Field f = findField(obj, fieldName);        return (T) f.get(obj);    }        /**     * 获取指定对象中指定名称的对象域反射引用     * @param obj 指定的对象实例     * @param fieldName 对象域名称     * @return     * @throws Exception     */    protected Field findField(Object obj, String fieldName) throws Exception {        Class<?> clazz = obj.getClass();        Field field = null;        while (notAtTopLevel(clazz)) {            try {                field = clazz.getDeclaredField(fieldName);            }catch (NoSuchFieldException e) {                //leave the field as null            }            if (field != null) {                field.setAccessible(true);                return field;            }            clazz = clazz.getSuperclass();        }        throw new NoSuchFieldException("Field [" + fieldName + "] is not found in Class [" + toTest.getClass().getName() + "]");    }    private boolean notAtTopLevel(Class<?> clazz) {        return clazz != null && clazz != Object.class;    }        /**     * 绕过可见性限制,调用被测试对象实例中的指定方法(包括父类中的方法)。本方法将根据传入的参数表自动选择匹配的重载方法。     * @param methodName 方法名称     * @param parameters 调用阐述表     * @return 调用方法的返回值     * @throws Exception     */    protected <T> T invokeSUTMethod(String methodName, Object... parameters) throws Exception {        return invokeMethod(toTest, methodName, parameters);    }        /**     * 绕过可见性限制,调用指定对象实例中的指定方法(包括父类中的方法)。本方法将根据传入的参数表自动选择匹配的重载方法。     * @param obj 指定的对象实例     * @param methodName 方法名称     * @param parameters 调用阐述表     * @return 调用方法的返回值     * @throws Exception     */    @SuppressWarnings("unchecked")    protected <T> T invokeMethod(Object obj, String methodName, Object... parameters) throws Exception {        Method m = findMethod(obj.getClass(), methodName, map(Arrays.asList(parameters), new Fun1<Object, Class<?>>(){            public Class<?> f(Object arg0) throws Exception {                return arg0 != null ? arg0.getClass() : null;            }        }));        return (T) m.invoke(obj, parameters);    }        /**     * 获取指定基础类型的对应装箱类型。     * @param clazz 需要装箱的类型     * @return 如果clazz为java基础类型,返回其对应的装箱类型。否则,返回clazz本身。     */    protected Class<?> Boxing(Class<?> clazz) {        if (clazz == null) return null;        if (clazz == Boolean.TYPE) return Boolean.class;        if (clazz == Byte.TYPE) return Byte.class;        if (clazz == Short.TYPE) return Short.class;        if (clazz == Integer.TYPE) return Integer.class;        if (clazz == Long.TYPE) return Long.class;        if (clazz == Float.TYPE) return Float.class;        if (clazz == Double.TYPE) return Double.class;        return clazz;    }        private Method findMethod(final Class<? extends Object> clazz, final String methodName, final List<Class<?>> argumentClasses) throws Exception {        Class<?> c = clazz;        while (notAtTopLevel(c)) {            List<Method> methods = filter(Arrays.asList(c.getDeclaredMethods()), new Fun1<Method, Boolean>() {                public Boolean f(Method arg0) throws Exception {                    return methodName.equals(arg0.getName()) && (arg0.getParameterTypes().length == argumentClasses.size());                }            });            for (Method m : methods) {               Class<?>[] paramTypes = m.getParameterTypes();               boolean matched = true;               for (Pair<Class<?>, Class<?>> paramPair : zip(argumentClasses, Arrays.asList(paramTypes))) {                   if (paramPair.first != null && !Boxing(paramPair.second).isAssignableFrom(paramPair.first)) {                       matched = false;                       break;                   }               }               if (matched) {                   m.setAccessible(true);                   return m;               }            }            c = c.getSuperclass();        }        throw new NoSuchMethodException("Method [" + methodName + "] is not found in class [" + clazz.getName() + "].");    }    //一些便利的集合初始方法。可免去在创建时重复指定泛型参数的麻烦。    /**     * 创建一个{@link HashMap}实例     * @return     */    protected static <K, V> Map<K, V> newHashMap() {        return new HashMap<K, V>();    }        /**     * 创建一个{@link ArrayList}实例     */    protected static <E> List<E> newArrayList() {        return new ArrayList<E>();    }        /**     * 创建一个{@link HashSet}实例     * @return     */    protected static <E> Set<E> newHashSet() {        return new HashSet<E>();    }    //标注定义        /**     * 标记了此标注的测试用例对象域会由{@link AbstractTestCase#applyAnntation()}方法自动赋予对应的Mock对象实例。     * @author Daniel Deng     *     */    @Retention(RetentionPolicy.RUNTIME)    @Target(ElementType.FIELD)    protected @interface Mock {    }    /**     * 标记了此标注的测试用例对象域会由{@link AbstractTestCase#applyAnntation()}方法自动创建一个对象实例。如果指定了value属性,     * 则创建由该属性所指定类型的对象实例,否则将创建被标注的对象域所定义类型的对象实例。如果该类型无法通过{@link Class#newInstance()}直     * 接实例化,则将尝试采用Objnesis机制强制实例化。     * @author Daniel     *     */    @Retention(RetentionPolicy.RUNTIME)    @Target(ElementType.FIELD)    protected @interface Create {        public Class<?> value() default Object.class;    }    /**     * 标记了此标注的测试用例对象域会由{@link AbstractTestCase#applyInjection()}方法自动注入到被测试对象实例中。可以用value属性     * 来指定被测试对象的对象域名称。如果未指定该属性,则默认注入到与被标注的测试用例对象域同名的对象域中。     * @author Daniel     *     */    @Retention(RetentionPolicy.RUNTIME)    @Target(ElementType.FIELD)    protected @interface Inject {        public String value() default "";    }        //一些进行简单的函数式集合处理的通用接口    protected static interface Fun<R> {        R f() throws Exception;    }        protected static interface Fun1<P0, R> {        R f(P0 arg0) throws Exception;    }        protected static interface Fun2<P0, P1, R> {        R f(P0 arg0, P1 arg1) throws Exception;    }        protected static interface VoidFun {        void f() throws Exception;    }        protected static interface VoidFun1<P0> {        void f(P0 arg0) throws Exception;    }        protected static interface VoidFun2<P0, P1> {        void f(P0 arg0, P1 arg1) throws Exception;    }        protected static class Pair<P1, P2> {        public P1 first;        public P2 second;    }        // 一些简单的模拟函数式集合处理的方法    /**     * 对原始集合中的所有元素进行转换,映射成新的集合     * @param collection 原始集合实例     * @param mapper 一个接受原始集合中的元素,将其转换为结果结合中的元素的函数对象     * @return 经过转换后的新集合     * @throws Exception     */    protected static <S, T> List<T> map(Collection<S> collection, Fun1<S, T> mapper) throws Exception {        List<T> converted = newArrayList();        if (collection != null && collection.size() > 0) {            Iterator<S> it = collection.iterator();            while (it.hasNext()) {                S element = it.next();                converted.add(mapper.f(element));            }        }        return converted;    }        /**     * 返回由原始集合中满足特定条件的元素所组成的新集合     * @param collection 原始集合实例     * @param condition 一个接受原始集合中的元素,返回该元素是否满足条件的函数对象。如果该函数对象返回true,则对应的元素会被保留到结果集合中     * @return 由原始集合中满足特定条件的元素所组成的新集合     * @throws Exception     */    protected static <S> List<S> filter(Collection<S> collection, Fun1<S, Boolean> condition) throws Exception {        List<S> filtered = newArrayList();        if (collection != null && collection.size() > 0) {            Iterator<S> it = collection.iterator();            while (it.hasNext()) {                S el = it.next();                if (Boolean.TRUE.equals(condition.f(el))) {                    filtered.add(el);                }            }        }        return filtered;    }        /**     * 将两个原始集合的元素按顺序两两配对,将配对结果形成新的集合     * @param collection1 原始集合1     * @param collection2 原始集合2     * @return 一个由集合1与集合2中的元素两两配对所形成的新集合。该集合的元素为{@link Pair}类型,其中first域为集合1中的对应元素,second域为集合2中的对应元素。     *         如果两个原始集合长度不同,则结果集合的长度等于最长的原始集合长度。在结果集合中,超出较短的原始集合长度部分对应的值{@link Pair}域值为null。     * @throws Exception     */    protected static <P1, P2> List<Pair<P1, P2>> zip(Collection<P1> collection1, Collection<P2> collection2) throws Exception {        Iterator<P1> it1 = collection1.iterator();        Iterator<P2> it2 = collection2.iterator();        List<Pair<P1, P2>> result = newArrayList();        boolean is1HasNext = it1.hasNext();        boolean is2HasNext = it2.hasNext();        while (is1HasNext || is2HasNext) {            Pair<P1, P2> pair = new Pair<P1, P2>();            if (is1HasNext) pair.first = it1.next();            if (is2HasNext) pair.second = it2.next();            result.add(pair);            is1HasNext = it1.hasNext();            is2HasNext = it2.hasNext();        }        return result;    }}


pom.xml
<dependencies><dependency>    <groupId>junit</groupId>    <artifactId>junit</artifactId>    <version>4.10</version></dependency><dependency><groupId>org.hamcrest</groupId><artifactId>hamcrest-all</artifactId><version>1.1</version><scope>test</scope></dependency><dependency><groupId>org.jmock</groupId><artifactId>jmock</artifactId><version>2.5.1</version><scope>test</scope></dependency><dependency>  <groupId>org.jmock</groupId>  <artifactId>jmock-legacy</artifactId>  <version>2.5.1</version>  <scope>test</scope></dependency><dependency><groupId>org.objenesis</groupId><artifactId>objenesis</artifactId><version>1.2</version><scope>test</scope></dependency></dependencies>
1 楼 mfkvfn 2011-11-07   已阅  

热点排行