测试驱动开发之初窥门径

什么是测试驱动开发

测试驱动开发是指在编写实现代码之前先写测试代码的开发方式。JUnit的作者Kent Beck说过:编写测试驱动代码的重要原因是消除开发中的恐惧和不确定性,因为编写代码时的恐惧会让你小心试探,让你回避沟通,让你羞于得到反馈,让你变得焦躁不安,而TDD是消除恐惧、让Java开发者更加自信更加乐于沟通的重要手段。TDD会带来的好处可能不会马上呈现,但是你在某个时候一定会发现,这些好处包括:

  1. 更清晰的代码 — 只写需要的代码
  2. 更好的设计
  3. 更出色的灵活性 — 鼓励程序员面向接口编程
  4. 更快速的反馈 — 不会到系统上线时才知道bug的存在

TDD可以在多个测试级别上使用,如下表所示:

测试级别描述
单元测试测试类中的代码
集成测试测试类之间的交互
系统测试测试运行中的系统
系统集成测试测试运行中的系统包括第三方组件

测试驱动开发的例子

现在我们需要一段代码来计算某个电影放映厅的门票收入,当前的业务规则非常简单,包括:

  • 每张票售价(单价)¥30
  • 收入=门票销售数量*单价
  • 放映厅最多容纳100人

这里还有一个假设:目前因为没有专业的设备或系统来统计门票销售的数量,在计算门票收入时,门票销售数量是由使用者手动录入的。
TDD的基本步骤是:红色-绿色-重构。

  1. 红色 - 编写无法通过的测试
  2. 绿色 - 编写实现代码并尽快让测试可以通过
  3. 重构 - 重构代码并再次让测试通过

接下来我们按照上述步骤完成门票收入计算的功能。

package com.lovo;

import java.math.BigDecimal;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

public class TicketRevenueTest {
    private TicketRevenue ticketRevenue;
    private BigDecimal expectedRevenue;

    @Before
    public void setUp() {
        ticketRevenue= new TicketRevenue();
    }

    @Test
    public void oneTicketSoldIsThirtyInRevenue() {
        expectedRevenue = new BigDecimal("30");
        Assert.assertEquals(expectedRevenue,   ticketRevenue.estimateTotalRevenue(1));
    }
}

上述测试代码不仅不能通过测试,甚至连编译都无法通过,因为TicketRevenue类还不存在呢。接下来我们可以利用IDE的代码修复功能(Eclipse和IntelliJ都有这样的功能)创建出TicketRevenue类以及该类中计算门票收入的estimateTotalRevenue方法。

package com.lovo;

import java.math.BigDecimal;

public class TicketRevenue {

    public BigDecimal estimateTotalRevenue(int i) {
        return BigDecimal.ZERO;
    }

}

现在可以运行你的单元测试用例了,但是由于我们还没有实现真正的业务逻辑,这个测试是不可能通过的,如下图所示。
在Eclipse运行上面的单元测试的结果
但是,迄今为止我们已经完成了“红色“这个步骤。接下来我们修改TicketRevenue类的estimateTotalRevenue方法来让测试通过。

package com.lovo;

import java.math.BigDecimal;

public class TicketRevenue {

    public BigDecimal estimateTotalRevenue(int numberOfTicketsSold) {
        BigDecimal totalRevenue = BigDecimal.ZERO;
        if(numberOfTicketsSold == 1) {
            totalRevenue = new BigDecimal(30);
        }
        return totalRevenue;
    }

}

再次运行单元测试,结果如下图所示。
在Eclipse中再次运行刚才单元测试的结果
到这里,第二个步骤”绿色“就完成了。
接下来我们开始重构TicketRevenue类的代码。

package com.lovo;

import java.math.BigDecimal;

public class TicketRevenue {
    private final static int TICKET_PRICE = 30;

    public BigDecimal estimateTotalRevenue(int numberOfTicketsSold) {
        BigDecimal totalRevenue = null;
        totalRevenue = new BigDecimal(TICKET_PRICE * numberOfTicketsSold);
        return totalRevenue;
    }

}

重构后的代码可以根据输入的门票销售数量计算出对应的收入,较之之前的硬代码(hard code)它已经前进了一大步,但是很明显它没有考虑到输入小于0或者大于100的情况。因此我们需要更多的测试例来模拟实际工作环境中可能的输入,我们对刚才的测试代码进行了如下改进。

package com.lovo;

import java.math.BigDecimal;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

public class TicketRevenueTest {
    private TicketRevenue ticketRevenue;
    private BigDecimal expectedRevenue;

    @Before
    public void setUp() {
        ticketRevenue = new TicketRevenue();
    }

    @Test(expected = IllegalArgumentException.class)
    public void failIfLessThanZeroTicketsAreSold() {
        ticketRevenue.estimateTotalRevenue(-1);
    }

    @Test
    public void zeroSalesEqualsZeroRevenue() {
        Assert.assertEquals(BigDecimal.ZERO, ticketRevenue.estimateTotalRevenue(0));
    }

    @Test
    public void oneTicketSoldIsThirtyInRevenue() {
        expectedRevenue = new BigDecimal("30");
        Assert.assertEquals(expectedRevenue, ticketRevenue.estimateTotalRevenue(1));
    }

    @Test
    public void tenTicketsSoldIsThreeHundredInRevenue() {
        expectedRevenue = new BigDecimal("300");
        Assert.assertEquals(expectedRevenue, ticketRevenue.estimateTotalRevenue(10));
    }

    @Test(expected = IllegalArgumentException.class)
    public void failIfMoreThanOneHundredTicketsAreSold() {
        ticketRevenue.estimateTotalRevenue(101);
    }
}

再次运行测试会发现5个测试中有两个无效输入的测试没有通过(销售数量为-1和101的测试),原因很简单,我们的代码中还没有处理无效输入的代码。接下来继续重构我们的代码。

package com.lovo;

import java.math.BigDecimal;

public class TicketRevenue {
    private final static int TICKET_PRICE = 30;

    public BigDecimal estimateTotalRevenue(int numberOfTicketsSold) 
            throws IllegalArgumentException {
        BigDecimal totalRevenue = null;
        if(numberOfTicketsSold < 0) {
            throw new IllegalArgumentException("门票销售数量必须大于等于0");
        }
        else if(numberOfTicketsSold > 100) {
            throw new IllegalArgumentException("门票销售数量必须小于等于100");
        }
        else {
            totalRevenue = new BigDecimal(TICKET_PRICE * numberOfTicketsSold);
        }
        return totalRevenue;
    }

}

再次运行刚才的测试代码,检查一下你的bar是不是绿色的(JUnit的名言是:“Keep your bar green”)。当然,对于有代码洁癖的人来说,上述代码仍然稍显臃肿,没关系,再来一次重构吧。

package com.lovo;

import java.math.BigDecimal;

public class TicketRevenue {
    private final static int TICKET_PRICE = 30;

    public BigDecimal estimateTotalRevenue(int numberOfTicketsSold) 
            throws IllegalArgumentException {
        if(numberOfTicketsSold < 0 || numberOfTicketsSold > 100) {
            throw new IllegalArgumentException("门票销售数量必须在0到100之间");
        }

        return  new BigDecimal(TICKET_PRICE * numberOfTicketsSold);
    }

}

当你完成对代码的修改后,永远都不要忘记再来一次刚才的测试,仍然需要Keep your bar green。
如果我们使用面向对象的编程范式,那么对代码的重构应当遵循面向对象的设计原则。大神Robert Matin将这些原则总结为SOLID原则。

原则英文描述
单一职责原则(S)Single Responsibility Principle每个对象只做自己该做的事情
开闭原则(O)Open-Closed Principle接受扩展但不接受修改
里氏替换原则(L)Liskov Substitution Principle可以用子类型替换父类型
接口隔离原则(I)Interface Segregation Principle接口要小而专
依赖倒转原则(D)Dependency Inversion Principle依赖接口而不依赖实现

说明:上面的例子来自The Well-Grounded Java Developer一书(中文名《Java程序员修炼之道》),这本书覆盖了Java开发中很多实用的技术以及Java新的语言特性,有兴趣的可以阅读此书,相信你会从中得到很多收获。

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
测试驱动的编程是 XP 困扰程序员的一个方面。对于测试驱动的编程意味着什么以及如何去做,大多数人都做出了不正确的假设。这个月,XP 方面的讲师兼 Java 开发人员 Roy Miller 谈论了测试驱动的编程是什么,它为什么可以使程序员的生产力和质量发生巨大变化,以及编写测试的原理。请在与本文相随的 论坛中提出您就本文的想法,以飨笔者和其他读者。(您也可以单击本文顶部或底部的“讨论”来访问该论坛。) 最近 50 年来,测试一直被视为项目结束时要做的事。当然,可以在项目进行之中结合测试测试通常并不是在 所有编码工作结束后才开始,而是一般在稍后阶段进行测试。然而,XP 的提倡者建议完全逆转这个模型。作为一名程序员,应该在编写代码 之前编写测试,然后只编写足以让测试通过的代码即可。这样做将有助于使您的系统尽可能的简单。 先编写测试 XP 涉及两种测试: 程序员测试和 客户测试测试驱动的编程(也称为 测试为先编程)最常指第一种测试,至少我使用这个术语时是这样。测试驱动的编程是让 程序员测试(即单元测试 ― 重申一下,只是换用一个术语)决定您所编写的代码。这意味着您必须在编写代码之前进行测试测试指出您 需要编写的代码,从而也 决定了您要编写的代码。您只需编写足够通过测试代码即可 ― 不用多,也不用少。XP 规则很简单:如果不进行程序员测试,则您不知道要编写什么代码,所以您不会去编写任何代码测试驱动开发(TDD)是极限编程的重要特点,它以不断的测试推动代码开发,既简化了代码,又保证了软件质量。本文从开发人员使用的角度,介绍了 TDD 优势、原理、过程、原则测试技术、Tips 等方面。 背景 一个高效的软件开发过程对软件开发人员来说是至关重要的,决定着开发是痛苦的挣扎,还是不断进步的喜悦。国人对软件蓝领的不屑,对繁琐冗长的传统开发过程的不耐,使大多数开发人员无所适从。最近兴起的一些软件开发过程相关的技术,提供一些比较高效、实用的软件过程开发方法。其中比较基础、关键的一个技术就是测试驱动开发(Test-Driven Development)。虽然TDD光大于极限编程,但测试驱动开发完全可以单独应用。下面就从开发人员使用的角度进行介绍,使开发人员用最少的代价尽快理解、掌握、应用这种技术。下面分优势,原理,过程,原则测试技术,Tips等方面进行讨论。 1. 优势 TDD的基本思路就是通过测试来推动整个开发的进行。而测试驱动开发技术并不只是单纯的测试工作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值