JUNIT之COMMAND模式学习笔记

文章摘要:前篇文章大体介绍了JUNIT的运行原理的冰山一角,当然对于大婶写的代码要仔细的研读才会有收获。前文对于COMMAND模式在JUNIT中的体现也只是一笔带过,没有多提,现在回味起来难免赶脚有很多地方没有交代清楚,所以,今天又把JUNIT源码翻出来仔细看了一看,希望有所收获,所以有了此文记录下来。

一,什么是COMMAND模式(略)

二,JUNIT中使用COMMAND模式

对于第一个问题,笔者也是找度娘请教,也有很多参考书籍可以参考,这里就不浪费敲键盘时间大笑;本文重点就放在第二个问题上。这里还是从JUNIT自带案例入手,来看看JUNIT笔者理解的COMMAND模式,案例部分代码如下:

    public static void main(String[] args) {
        junit.textui.TestRunner.run(suite());
    }


    public static Test suite() {
        TestSuite suite = new TestSuite("Framework Tests");
        suite.addTestSuite(TestCaseTest.class);
        suite.addTest(SuiteTest.suite()); // Tests suite building, so can't use automatic test extraction
        suite.addTestSuite(TestListenerTest.class);
        suite.addTestSuite(AssertionFailedErrorTest.class);
        suite.addTestSuite(AssertTest.class);
        suite.addTestSuite(TestImplementorTest.class);
        suite.addTestSuite(NoArgTestCaseTest.class);
        suite.addTestSuite(ComparisonCompactorTest.class);
        suite.addTestSuite(ComparisonFailureTest.class);
        suite.addTestSuite(DoublePrecisionAssertTest.class);
        suite.addTestSuite(FloatAssertTest.class);
        return suite;
    }
对这一段代码笔者简述一下自己的认识,首先,一个入口 junit.textui.TestRunner的run方法去执行对应的execute,在JUNIT中的execute是一个Test接口,代码如下:

package junit.framework;

/**
 * A <em>Test</em> can be run and collect its results.
 *
 * @see TestResult
 */
public interface Test {
    /**
     * Counts the number of test cases that will be run by this test.
     */
    public abstract int countTestCases();

    /**
     * Runs a test and collects its result in a TestResult instance.
     */
    public abstract void run(TestResult result);
}
Test接口只有两个方法,一个是countTestCases用于统计已执行的方法个数;第二个是run用于执行对应的方法。那么实现Test接口有哪些组件呢?有:TestSuite和TestCase
至此,JUNIT中COMMAND命令模式我们应该有点点感觉了,也就是说,所有suite和testcase都有对应需要执行的命令方法run,通过一个入口run,依次执行。
<span style="white-space:pre">	</span>回到我们的案例代码中来,结合具体实例我们才能说清楚问题
在实例部分代码中,有一个suite方法和一个main方法,这个junit自带的实例可以直接运行,运行结果就不贴出。
<pre name="code" class="java">public static void main(String[] args) {
        junit.textui.TestRunner.run(suite());
}


 


这段代码和相关的执行测试方法代码已经在上一篇文章里面提到过,这里就不再重复。这里笔者认为我们已经有了JUNIT的运行机制感觉,了解TestSuite类如果添加测试方法基本上对于JUNIT的COMMAND模式也会有个入门认识。

对于TestSuite实例代码中添加需要测试类的方法有两种类型:1,直接添加类.class;2,通过TestSuite添加。我们来看看TestSuite的完整代码:

package junit.framework;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Vector;

import org.junit.internal.MethodSorter;

/**
 * A <code>TestSuite</code> is a <code>Composite</code> of Tests.
 * It runs a collection of test cases. Here is an example using
 * the dynamic test definition.
 * <pre>
 * TestSuite suite= new TestSuite();
 * suite.addTest(new MathTest("testAdd"));
 * suite.addTest(new MathTest("testDivideByZero"));
 * </pre>
 * <p>
 * Alternatively, a TestSuite can extract the tests to be run automatically.
 * To do so you pass the class of your TestCase class to the
 * TestSuite constructor.
 * <pre>
 * TestSuite suite= new TestSuite(MathTest.class);
 * </pre>
 * <p>
 * This constructor creates a suite with all the methods
 * starting with "test" that take no arguments.
 * <p>
 * A final option is to do the same for a large array of test classes.
 * <pre>
 * Class[] testClasses = { MathTest.class, AnotherTest.class }
 * TestSuite suite= new TestSuite(testClasses);
 * </pre>
 *
 * @see Test
 */
public class TestSuite implements Test {

    /**
     * ...as the moon sets over the early morning Merlin, Oregon
     * mountains, our intrepid adventurers type...
     */
    static public Test createTest(Class<?> theClass, String name) {
        Constructor<?> constructor;
        try {
            constructor = getTestConstructor(theClass);//获得需要测试方法的构造器
        } catch (NoSuchMethodException e) {
            return warning("Class " + theClass.getName() + " has no public constructor TestCase(String name) or TestCase()");
        }
        Object test;
        try {
            if (constructor.getParameterTypes().length == 0) {
                test = constructor.newInstance(new Object[0]);
                if (test instanceof TestCase) {
                    ((TestCase) test).setName(name);//这里通过获得的构造器生成一个测试类并设置需要测试的方法
                }
            } else {
                test = constructor.newInstance(new Object[]{name});//构造方法有参的时候
            }
        } catch (InstantiationException e) {
            return (warning("Cannot instantiate test case: " + name + " (" + exceptionToString(e) + ")"));
        } catch (InvocationTargetException e) {
            return (warning("Exception in constructor: " + name + " (" + exceptionToString(e.getTargetException()) + ")"));
        } catch (IllegalAccessException e) {
            return (warning("Cannot access test case: " + name + " (" + exceptionToString(e) + ")"));
        }
        return (Test) test;
    }

    /**
     * Gets a constructor which takes a single String as
     * its argument or a no arg constructor.
     */
    public static Constructor<?> getTestConstructor(Class<?> theClass) throws NoSuchMethodException {
        try {
            return theClass.getConstructor(String.class);
        } catch (NoSuchMethodException e) {
            // fall through
        }
        return theClass.getConstructor();
    }

    /**
     * Returns a test which will fail and log a warning message.
     */
    public static Test warning(final String message) {
        return new TestCase("warning") {
            @Override
            protected void runTest() {
                fail(message);
            }
        };
    }

    /**
     * Converts the stack trace into a string
     */
    private static String exceptionToString(Throwable e) {
        StringWriter stringWriter = new StringWriter();
        PrintWriter writer = new PrintWriter(stringWriter);
        e.printStackTrace(writer);
        return stringWriter.toString();
    }

    private String fName;

    private Vector<Test> fTests = new Vector<Test>(10); // 这个是没一个run命令所需要执行的所有测试Test

    /**
     * Constructs an empty TestSuite.
     */
    public TestSuite() {
    }

    /**
     * Constructs a TestSuite from the given class. Adds all the methods
     * starting with "test" as test cases to the suite.
     * Parts of this method were written at 2337 meters in the Hueffihuette,
     * Kanton Uri
     */
    public TestSuite(final Class<?> theClass) {
        addTestsFromTestCase(theClass);
    }

    private void addTestsFromTestCase(final Class<?> theClass) {
        fName = theClass.getName();
        try {
            getTestConstructor(theClass); // Avoid generating multiple error messages
        } catch (NoSuchMethodException e) {
            addTest(warning("Class " + theClass.getName() + " has no public constructor TestCase(String name) or TestCase()"));
            return;
        }

        if (!Modifier.isPublic(theClass.getModifiers())) {
            addTest(warning("Class " + theClass.getName() + " is not public"));
            return;
        }

        Class<?> superClass = theClass;
        List<String> names = new ArrayList<String>();
        while (Test.class.isAssignableFrom(superClass)) {
            for (Method each : MethodSorter.getDeclaredMethods(superClass)) {
                addTestMethod(each, names, theClass);
            }
            superClass = superClass.getSuperclass();
        }
        if (fTests.size() == 0) {
            addTest(warning("No tests found in " + theClass.getName()));
        }
    }

    /**
     * Constructs a TestSuite from the given class with the given name.
     *
     * @see TestSuite#TestSuite(Class)
     */
    public TestSuite(Class<? extends TestCase> theClass, String name) {
        this(theClass);
        setName(name);
    }

    /**
     * Constructs an empty TestSuite.
     */
    public TestSuite(String name) {
        setName(name);
    }

    /**
     * Constructs a TestSuite from the given array of classes.
     * 通过class数组对象构造一个TestSuite
     * @param classes {@link TestCase}s
     */
    public TestSuite(Class<?>... classes) {
        for (Class<?> each : classes) {
            addTest(testCaseForClass(each));
        }
    }

    private Test testCaseForClass(Class<?> each) {
        if (TestCase.class.isAssignableFrom(each)) {
            return new TestSuite(each.asSubclass(TestCase.class));
        } else {
            return warning(each.getCanonicalName() + " does not extend TestCase");
        }
    }

    /**
     * Constructs a TestSuite from the given array of classes with the given name.
     *
     * @see TestSuite#TestSuite(Class[])
     */
    public TestSuite(Class<? extends TestCase>[] classes, String name) {
        this(classes);
        setName(name);
    }

    /**
     * Adds a test to the suite.
     */
    public void addTest(Test test) {
        fTests.add(test);
    }

    /**
     * Adds the tests from the given class to the suite
     */
    public void addTestSuite(Class<? extends TestCase> testClass) {
        addTest(new TestSuite(testClass));
    }

    /**
     * Counts the number of test cases that will be run by this test.
     */
    public int countTestCases() {
        int count = 0;
        for (Test each : fTests) {
            count += each.countTestCases();
        }
        return count;
    }

    /**
     * Returns the name of the suite. Not all
     * test suites have a name and this method
     * can return null.
     */
    public String getName() {
        return fName;
    }

    /**
     * Runs the tests and collects their result in a TestResult.
     */
    public void run(TestResult result) {
        for (Test each : fTests) {
            if (result.shouldStop()) {
                break;
            }
            runTest(each, result);
        }
    }

    public void runTest(Test test, TestResult result) {
        test.run(result);
    }

    /**
     * Sets the name of the suite.
     *
     * @param name the name to set
     */
    public void setName(String name) {
        fName = name;
    }

    /**
     * Returns the test at the given index
     */
    public Test testAt(int index) {
        return fTests.get(index);
    }

    /**
     * Returns the number of tests in this suite
     */
    public int testCount() {
        return fTests.size();
    }

    /**
     * Returns the tests as an enumeration
     */
    public Enumeration<Test> tests() {
        return fTests.elements();
    }

    /**
     */
    @Override
    public String toString() {
        if (getName() != null) {
            return getName();
        }
        return super.toString();
    }

    private void addTestMethod(Method m, List<String> names, Class<?> theClass) {
        String name = m.getName();
        if (names.contains(name)) {
            return;
        }
        if (!isPublicTestMethod(m)) {
            if (isTestMethod(m)) {
                addTest(warning("Test method isn't public: " + m.getName() + "(" + theClass.getCanonicalName() + ")"));
            }
            return;
        }
        names.add(name);
        addTest(createTest(theClass, name));//通过构造器生成测试方法的类并通过fName属性保存待测试的方法
    }

    private boolean isPublicTestMethod(Method m) {
        return isTestMethod(m) && Modifier.isPublic(m.getModifiers());
    }

    private boolean isTestMethod(Method m) {
        return m.getParameterTypes().length == 0 &&
                m.getName().startsWith("test") &&
                m.getReturnType().equals(Void.TYPE);
    }
}
源码中有几个地方我们需要注意,笔者直接在源码中添加备注。
我们重点看看TestSuite类的run方法,其中runTest方法中参数test参数是COMMAND命令的关键,它可以是一个TestSuite也可以是一个TestCase,好,到了这里这篇文章进入高潮部分了,这里贴出TestCase和COMMAND相关源代码:

    public TestResult run() {
        TestResult result = createResult();
        run(result);
        return result;
    }

    /**
     * Runs the test case and collects the results in TestResult.
     */
    public void run(TestResult result) {
        result.run(this);
    }

    /**
     * Runs the bare test sequence.
     *
     * @throws Throwable if any exception is thrown
     */
    public void runBare() throws Throwable {
        Throwable exception = null;
        setUp();
        try {
            runTest();
        } catch (Throwable running) {
            exception = running;
        } finally {
            try {
                tearDown();
            } catch (Throwable tearingDown) {
                if (exception == null) exception = tearingDown;
            }
        }
        if (exception != null) throw exception;
    }

    /**
     * Override to run the test and assert its state.
     *
     * @throws Throwable if any exception is thrown
     */
    protected void runTest() throws Throwable {
        assertNotNull("TestCase.fName cannot be null", fName); // Some VMs crash when calling getMethod(null,null);
        Method runMethod = null;
        try {
            // use getMethod to get all public inherited
            // methods. getDeclaredMethods returns all
            // methods of this class but excludes the
            // inherited ones.
            runMethod = getClass().getMethod(fName, (Class[]) null);
        } catch (NoSuchMethodException e) {
            fail("Method \"" + fName + "\" not found");
        }
        if (!Modifier.isPublic(runMethod.getModifiers())) {
            fail("Method \"" + fName + "\" should be public");
        }

        try {
            runMethod.invoke(this);
        } catch (InvocationTargetException e) {
            e.fillInStackTrace();
            throw e.getTargetException();
        } catch (IllegalAccessException e) {
            e.fillInStackTrace();
            throw e;
        }
    }
在TestCase类中关于run的方法最多,最终实际运行的也是runTest这个方法,其他相关run相关方法也体现了JUNIT一些机制,有兴趣读者可以自己慢慢研读。

到了这里我们先来做一个总结,经过上面两个重要类的代码阅读理解,COMMAND模式笔者用自己语言这样理解:所有COMMAND都是Test作为顶级接口,通过TestSuite进行包装需要执行的下一步COMMAND,最终都是通过构造TestCase这个类并添加待测试方法执行COMMAND。





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值