关闭

Java TDD介绍-1

标签: javatdd
1300人阅读 评论(0) 收藏 举报
分类:

原文:Introduction in Java TDD – part 1
翻译:get-set


欢迎来到关于测试驱动开发(TDD)的简介。我们会谈到关于Java和JUnit在TDD中的应用,不过这些都只是工具而已。这篇文章的主要目的是能给您一个关于TDD的深入的理解,而不管什么编程语言和测试框架。

如果你不在你的项目中使用TDD,那么说明你要么懒,要么就是不懂TDD,缺少时间这种理由是站不住脚的。

关于这篇文章

在这篇文章中,我会解释什么是TDD,以及如何在Java中使用;在TDD的什么地方会用到单元测试;在单元测试中你需要做哪些事情;最后,你应该遵循什么原则来编写完美有效的单元测试代码。
如果你已经知道关于Java中的TDD,但是又对例子和教程比较感兴趣,建议你跳过本节直接阅读下一节。

什么是TDD?

如果有人让我简单解释一下什么是TDD,我会说TDD就是在你还没有实现一项功能之前就进行的测试。你可能会质疑:如果还没有实现那如何测试呢?估计肯特·贝克(估计你也猜到了他就是TDD的创始人)会赏你一巴掌。

那么具体如何来做呢?大概有如下几步:
1. 阅读并理解功能需求;
2. 开发一系列的测试来检查这项功能。因为还没有实现功能,因此所有的测试都是红色的;
3. 开发这项功能,直到所有的测试都变成绿色的;
4. 重构代码。
这里写图片描述
TDD需要的是不同的思路,所以为了基于TDD的理念开始开发,你需要首先忘掉之前的开发思路。这个过程非常痛苦,而且如果你不懂如何编写单元测试的话会更加痛苦,但却是值得的。

基于TDD开发有很大的优势:
1. 你对要实现的功能会有一个更好的理解;
2. 你有明确的“功能是否完成”的指标;
3. 基于测试开发的代码通常之后不需要更多的修改和完善。

得到这一优势的代价也很大——调整到一个新的开发模式的时候的阵痛,以及你在开发每一个新功能的时候可能会花费更多的时间。不过这是时间换质量的代价。

总之这就是TDD的工作原理——编写红色的单元测试,实现相关功能,让所有的测试变绿,重构。

TDD中单元测试的时机

单元测试是自动化测试金字塔中最小的元素,TDD也是基于此。单元测试可以帮助我们检查业务逻辑,编写单元测试代码也很容易。那么你如何使用单元测试呢?我会尽量通过简单的方式解释给你。
这里写图片描述
单元测试应该越小越好。但是不要认为一个方法对应一个测试,不过也有可能存在这种情况。真正的规则在于一个单元测试对应一组多个方法的调用,这叫做“基于行为的测试”(testing of behaviour)。
我们来看一下Account这个类:

public class Account {

    private String id = RandomStringUtils.randomAlphanumeric(6);
    private boolean status;
    private String zone;
    private BigDecimal amount;

    public Account() {
        status = true;
        zone = Zone.ZONE_1.name();
        amount = createBigDecimal(0.00);
    }

    public Account(boolean status, Zone zone, double amount) {
        this.status = status;
        this.zone = zone.name();
        this.amount = createBigDecimal(amount);
    }

    public enum Zone {
        ZONE_1, ZONE_2, ZONE_3
    }

    public static BigDecimal createBigDecimal(double total) {
        return new BigDecimal(total).setScale(2, BigDecimal.ROUND_HALF_UP);
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("id: ").append(getId())
                .append("\nstatus: ")
                .append(getStatus())
                .append("\nzone: ")
                .append(getZone())
                .append("\namount: ")
                .append(getAmount());
        return sb.toString();
    }

    public String getId() {
        return id;
    }

    public boolean getStatus() {
        return status;
    }

    public void setStatus(boolean status) {
        this.status = status;
    }

    public String getZone() {
        return zone;
    }

    public void setZone(String zone) {
        this.zone = zone;
    }

    public BigDecimal getAmount() {
        return amount;
    }

    public void setAmount(BigDecimal amount) {
        if (amount.signum() < 0)
            throw new IllegalArgumentException("The amount does not accept negative values");
        this.amount = amount;
    }
}

注意,这个类中有4个getter方法。如果我们为每个getter方法创建一个单独的单元测试,代码就会有很多冗余行。这种情况下基于行为的测试就比较有用了。试想我们现在需要测试某一个构造器在创建对象时是否正确,如何检查创建的对象是否如预期呢?我们需要检查每一个属性的值,getter方法就可以用于这样的场景。

创建小而精的单元测试,因为它们需要在每次提交到git或编译之前都应该执行。为了理解单元测试的速度的重要性,试想一个项目有1000个单元测试,每个测试花费100ms,那么跑完所有的测试将花费1分40秒的时间。
实际上对于单元测试来说,100ms的时间太长了,所以你应当通过采用不同的规则和技术来降低测试时间,比如,不要在单元测试中进行数据库连接的测试,也不要在@Before注解的地方运行大型对象的初始化。

要给单元测试起好名字。测试的名字可以很长,主要是它能够表达清楚测试什么。例如我需要测试Account类的一个构造方法,我会命名为defaultConstructorTest。另一方面,建议在命名之前先把测试逻辑写出来,这样可能会更容易命名。

单元测试应该是可预测的。这是最明显不过的了。例如,为了检查转账(5%手续费)操作,你需要知道转账的金额和作为输出的到账金额。比如转账100块而收到95块。

最后,单元测试结果是容易获得的。当你给每一个业务逻辑场景都构建一个测试的时候,可以得到很多测试结果反馈,从而能够精确定位到测试失败的场景。

所有的这些建议目的在于提高单元测试的设计水平,但是还有一点——基本的测试设计原理。

基本的测试设计原理

如果没有测试数据,那么测试也无从谈起。例如,当你测试转账业务逻辑时,你需要设置一些数字作为转账发起金额。这些金额就是测试数据。那么问题来了,你应该如何选择测试数据呢?为了回答这个问题,我们需要过一遍最主流的测试设计原理。其主要目的就是构建测试数据。

首先,我们假设转账金额只能为正整数,另外上限是1000元。那么可以表示为:

0 < amount <= 1000; amount in integer

我们所有的测试场景可以被分为两组:正确 & 错误。第一组测试数据是允许的,应该得到正确的结果;第二组就是错误场景。

依据同类数据原理(classes of equivalence technic),我们随机选取(0, 1000]范围内的数字进行测试,比如500,如果500成功的话,我们认为所有的位于该范围内的数字也是成功的。我们也可以选择区域内的其他类型数据,比如浮点型数字123.45。

然后我们依据边界测试原理(boundary testing technic),选择2个有效值,分别位于数字范围的两头,这里我们选择1作为最小值,1000作为最大值。

下一步就是选择2个无效值,比如0和1001.

最终我们有6个测试数据:
(1, 500, 1000)——作为正确场景
(0, 125.50, 1001)——作为错误场景

总结

在这篇文章中,我解释了TDD以及单元测试在TDD中的重要性。我希望在如此啰嗦的解释后,大家能够在实际的开发过程中进行应用。下一节我将会说明如何在功能代码开发前编写测试代码。我们会一步一步来,以一个文件分析的例子开始,直到最终完成代码重构。

一定让所有的测试都变成绿色的哟 : )

1
0
查看评论
发表评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场

TDD入门demo

OK,前面的博客整理了一系列的junit相关内容,这里举一个例子TDD实际的编码例子,不管实际编码中是否使用TDD,个人觉得这种思想必须要有。 我们不一定在写业务代码之前一定要说是把测试类都写出来,...
  • u011794238
  • u011794238
  • 2016-02-17 11:29
  • 715

java TDD

java TDD
  • saberming
  • saberming
  • 2011-02-04 17:54
  • 3417

浅谈TDD、BDD与ATDD软件开发

这些知识之前就了解了一点,还没来得急总结,现在总结一下。 1. 首先了解一下这三个开发模式都是什么意思: TDD:测试驱动开发(Test-Driven Development) 测试驱动开发是敏捷开发...
  • zhenyu5211314
  • zhenyu5211314
  • 2014-03-25 09:13
  • 9389

浅谈TDD、BDD与ATDD软件开发 (敏捷开发模式)

这些知识之前就了解了一点,还没来得急总结,现在总结一下。 1. 首先了解一下这三个开发模式都是什么意思: TDD:测试驱动开发(Test-Driven Development) 测试驱动开发...
  • tianyeming
  • tianyeming
  • 2015-04-07 13:43
  • 2920

测试驱动开发(TDD)实战小例子(JAVA版)

我们知道,测试驱动开发(TDD)的基本思想就是在开发功能代码之前,先编写测试代码。也就是说在明确要开发某个功能后,首先思考如何对这个功能进行测试,并完成测试代码的编写,然后编写相关的代码满足这些测试用...
  • sunli880127
  • sunli880127
  • 2013-10-17 15:24
  • 1669

LTE_TDD问题定位指导1

  • 2017-07-28 12:10
  • 3KB
  • 下载

LTE-TDD随机接入过程(3)-RAR(MSG2)以及MSG1的重传

参考文献 (1)3GPP TS 36.321 V9.6.0 (2012-03) Medium Access Control (MAC) protocol specification (2)3GPP T...
  • m_052148
  • m_052148
  • 2016-04-11 23:06
  • 13706

测试驱动开发-TDD(1)

测试:作为动词,它是评估的意思;作为名词,它是导致最终是接受还是不接受的过程。 测试是相互独立的。 测试列表,就跟你生活中记录你的工作计划一样。 测试优先:你应该在什么时候编写测试呢?在你编写要...
  • lulin27860
  • lulin27860
  • 2014-03-08 09:30
  • 1283

LTE-TDD随机接入过程(3)-RAR(MSG2)以及MSG1的重传

 转载  原文链接(http://blog.csdn.net/m_052148) 本文涉及到的内容有: (1)UE在什么时候开始接收RAR (2)怎么确定RA-RNTI (3)UE没有收...
  • twjy1314
  • twjy1314
  • 2017-05-04 10:22
  • 280

测试驱动开发TDD(1-3)

  • 2010-05-26 21:11
  • 62KB
  • 下载
    个人资料
    • 访问:148096次
    • 积分:1409
    • 等级:
    • 排名:千里之外
    • 原创:58篇
    • 转载:3篇
    • 译文:3篇
    • 评论:5条
    最新评论