美文网首页
Lite-junit设计归纳

Lite-junit设计归纳

作者: ThomasYoungK | 来源:发表于2017-10-03 10:57 被阅读32次

代码见lite-junit源代码

简介

Lite-junit是模仿junit3.8写的一个测试框架,里面用到了以下设计模式,是很好的编程练习:

  1. 命令模式: 每个TestCase都有个run方法,run内部包含了业务代码,但是对TestCase来讲不用管, 只要运行run即可
  2. 观察者模式: TestResult是主题, Listener是观察者, 解耦了测试结果和结果显示逻辑
  3. 参数收集: TestResult收集了测试结果, 和测试用例的运行进行了解耦
  4. 组合模式: TestSuite组合了TestCase, 因此可以对类、包、模块进行测试
  5. 装饰器模式: 在Test外面包裹了装饰器, 从而实现重复运行测试用例或者对包整体进行setUp, tearDown
  6. 模板方法: setUp, runTest(), tearDown()
  7. 协议: 规定了public static Test suite(), 测试用例以test开头, 从而方便反射的运用

乍一看,是不是很晕菜,没事,我一点一点讲给大家听。
Lite-junit中的类主要有这几种:

TestCase

测试的最小单元,可以认为每个测试用例都是一个TestCase对象,这里的测试用例是以test开头的方法。在进行测试的时候,测试框架会为每个以test开头的方法创建一个TestCase,这样各个测试方法互相在内存上就是隔离的。任何测试类都需要继承TestCase
我们可以大致看一下TestCase的定义, 这里面用到了命令模式,不用管测试方法的业务代码,都是直接调用run方法即可以运行测试用例。模板方法也是一眼就能看出来的,在doRun里面,运行顺序是setUp->runTest->tearDown,那么任何继承自TestCase的测试类都会按这个顺序来执行,setUptearDown的名字是定好的,不能随意修改:

public abstract class TestCase extends Assert implements Test {
    public void run(TestResult tr) {
        tr.run(this);
    }
    public void doRun() throws Throwable {
        setUp();
        try{
            runTest();
        }
        finally{
            tearDown();
        }
    }
}

而测试类一般写成这样:

public class CalculatorTest extends TestCase {
    public void setUp(){
        cal = new Calculator();
    }
    public void tearDown(){
    }
    public void testAdd()
    {
        Calculator cal = new Calculator();
        int result = cal.add(1, 2);
        //断言assert
        Assert.assertEquals(3, result);
    }
    public void testMultiply()
    {
        Calculator cal = new Calculator();
        int result = cal.multiply(4, 2);
        Assert.assertEquals(8,result);
    }
}

TestSuite

测试用例一定是好几个放在一起执行的,因此需要有个容器来存放测试类,这个容器就是TestSuite,它是组合模式应用的范例,它内部有个 Test的列表,装了一堆测试用例,可以是TestCase,也可以是TestSuite,当调用run方法的时候,就能递归得执行到最底层的测试用例了:

public class TestSuite implements Test {
    private List<Test> tests = new ArrayList<>(10);

    @Override
    public void run(TestResult tr) {
        for (Test test: tests) {
            if (tr.shouldStop()) break;
            test.run(tr);
        }
    }

    public void addTest(Test test) {
        tests.add(test);
    }
}

TestResult

测试用例的执行过程中,往往需要将测试结果保存到一个地方,并且希望测试过程和测试结果能够相对隔离,TestResult做的就是这件事。仔细看 TestCaserun(TestResult tr)方法,发现传入一个TestResult,而后会运行tr.run(this),在这里面TestResult又回调了TestCasedoRun方法:

public class TestResult {
    protected List<TestFailure> failures;  // 存储assert失败
    protected List<TestFailure> errors;  // 存储业务代码异常,比如空指针
    private List<TestListener> listeners;  // 观察者列表
    private int testCount;  // 执行用例个数
    public void run(TestCase testCase) {
        startTest(testCase);
        try {
            testCase.doRun();  // 回调TestCase的doRun()方法
        } catch (AssertionFailedError e) {
            addFailure(testCase, e);
        } catch (Throwable e) {
            addError(testCase, e);
        }
        endTest(testCase);
    }    

这种把错误收集到一个专门类的方法,叫收集参数模式

Listener

测试用例在执行时,可能会展示为图表等内容,最好的方式就是观察者模式了,仔细看TestResultrun方法里面有几个方法addError, addFailure, startTest, endTest,它们就是观察者的方法, 其实现也定义在TestResult里面,一眼就看出这是最基本的观察者模式:

    private void addError(TestCase testCase, Throwable e) {
        errors.add(new TestFailure(testCase, e));
        for (TestListener listener: listeners) {
            listener.addError(testCase, e);
        }
    }

    private void addFailure(TestCase testCase, AssertionFailedError e) {
        failures.add(new TestFailure(testCase, e));
        for (TestListener listener: listeners) {
            listener.addFailure(testCase, e);
        }
    }

    private void startTest(TestCase testCase) {
        int count= testCase.countTestCases();
        testCount+= count;
        for (TestListener listener: listeners) {
            listener.startTest(testCase);
        }
    }

    private void endTest(TestCase testCase) {
        for (TestListener listener: listeners) {
            listener.endTest(testCase);
        }
    }

我们可以定义一个观察者接口:

public interface TestListener {
    void addError(Test test, Throwable t);
    void addFailure(Test test, Throwable t);
    void startTest(Test test);
    void endTest(Test test);
}

然后自己实现几个观察者,把它们加入到TestResult里面去即可。

TestRunner

Lite-junit的基础类都介绍完了,最后肯定要介绍一下如何触发测试,代码中是通过TestRunner来做到的,该类同时继承了TestListener,会顺便把自己加入到TestResult观察者列表中:

public class TestRunner implements TestListener {
    private static final String SUITE = "suite";
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        TestRunner testRunner = new TestRunner();
        TestResult tr = new TestResult();
        tr.addListener(testRunner);  // 添加listener
        String className = "com.coderising.myood.litejunit.v2.example_test.AllTest";
        Class caseClazz = Class.forName(className);
        Method suiteMethod = caseClazz.getMethod(SUITE, new Class[0]);
        Test suite = (Test) suiteMethod.invoke(null);
        testRunner.tryTest(suite, tr);
    }

    private void tryTest(Test suite, TestResult tr) {
        suite.run(tr);
        System.out.println();
        print(tr);
    }
}

代码很简单,不过有一个很值得观察的点:就是Case是如何组合到一起来执行的呢?代码中的AllTest类有个suite方法,返回的是TestSuite对象,调用它的run方法,也就触发了所有的测试,那么立即可以联想到AllTest里面肯定包含了许许多多的测试用例了。
不错,Lite-junit希望能够把测试用例按照java文件的组织方式把测试用例一个一个加到suite里面,然后一股脑儿进行测试,最后由TestResultTestListener来把结果统计出来。

测试用例的准备

image.png
如上图所示的java文件,我们要测试三个类Calculator, Byebye, HelloWorld,因此我们写了三个测试类(都继承了TestCase): CalculatorTest, ByebyeTest, HelloWorldTest,我又约定了需要实现suite方法的函数(暂时不用理会RepeatedTestSetUpTest,后面会讲到):
public class AAllTest {
    public static Test suite() {
        TestSuite suite = new TestSuite();
        suite.addTestSuite(CalculatorTest.class);
        return new SetUpTest(suite) {

            @Override
            protected void setUp() {
                System.out.println("AAll start!");
            }

            @Override
            protected void tearDown() {
                System.out.println("AAll end!");
            }
        };
    }
}
public class BAllTest {
    public static Test suite() {
        TestSuite suite = new TestSuite();
        suite.addTestSuite(HelloWorldTest.class);
        suite.addTestSuite(ByebyeTest.class);
        return suite;
    }
}
public class AllTest {
    public static Test suite() {
        TestSuite suite = new TestSuite();
        suite.addTest(new RepeatedTest(AAllTest.suite(), 2));  // 5 * 2个用例
        suite.addTest(BAllTest.suite());  //  2个用例
        return new SetUpTest(suite) {
            @Override
            protected void setUp() {
                System.out.printf("All start!\n");
            }

            @Override
            protected void tearDown() {
                System.out.printf("All end!");
            }
        };
    }
}

AAllTestBAllTestAllTest就把这几层文件的测试用例都组织起来了,最后在前文中的TestRunner中传入AllTest类,调用它的suite方法就可以对所有类进行测试了。

TestDecorate

有时候,想重复执行某个用例,或者相对某个测试集合有个整体的setUp,那么可以用装饰器来做到这一点。
类图如下所示:


image.png

实现很简单,就不展示代码了。

用例图和类图

下图是类图:



下图是用例图,???是TestRunner


image.png

总结

感觉写文字很难,别人看我写的文字估计是一头雾水,而且写到后面自己都不太高兴写了,用例图和类图写的很粗浅,以后慢慢增强写作能力,希望大家能逐渐看懂我的文字。

相关文章

  • Lite-junit设计归纳

    代码见lite-junit源代码 简介 Lite-junit是模仿junit3.8写的一个测试框架,里面用到了以下...

  • 归纳设计需求

    归纳设计需求 去掉重复部分 把动机、担忧和障碍关键因素放到一块,然后去掉重复的解决方案。 区分解决方案 看看有哪些...

  • 页面设计归纳

    1、从整体到部分的层层渐进的设计页面 2、根据页面关系流程进行渐进完善,比如根据任务流程图设计页面简图,然后根据具...

  • 设计模式归纳

    一、设计模式的分类 23 种经典的设计模式分为三类:创建型、结构型、行为型。 1.创建型设计模式主要解决“对象的创...

  • ATM机设计归纳

    源代码见ATM源码 需求 一个ATM是一台机器,包含读卡设备,显示屏,吐钞口,存钞口,键盘,打印机当机器空闲时,会...

  • 设计模式归纳总结

    1、前言 前两年工作中多有用到一些设计模式,但是除了早年在大学里学过一遍设计模式之后,一些不常用的设计模式渐渐忘记...

  • 平面设计的分类有哪些,这些你都知道吗?——黎乙丙

    平面设计的分类,目前常见的平面设计可以归纳为8 大类:网页设计、包装设计、DM广告设计、海报设计、POP广告设计、...

  • 产品设计原则归纳

    一直记不住尼尔森十大可用性原则,这次从新归纳感觉好记多了: 1、易学:状态可见、场景贴切、帮助提示; 2、易记:一...

  • 注册登录设计方法归纳

    下面将从产品类别进行总结: 1.资讯类:MONO(游客模式、三方)、 2.社交:火柴盒(必须登录、支持三方登录(可...

  • 吉祥物设计绒言绒语:色彩讲究直接关乎企业利益

    无论什么设计,工业设计、室内设计、UI设计,以及包含卡通形象在内的平面设计也好,其实归纳起来都离不开形、色、质、构...

网友评论

      本文标题:Lite-junit设计归纳

      本文链接:https://www.haomeiwen.com/subject/zeptyxtx.html