public class Ticket { private final static int PRICE = 99; public BigDecimal sale(int count) { if (count == 1) { return new BigDecimal(PRICE); } return BigDecimal.ZERO; } }
重构完后再运行一下测试用例,确保测试通过的情况下,再增加几个测试用例,比如说门票销量为负数、零甚至一千的情况。
public class TicketTest { private Ticket ticket; @Before public void setUp() throws Exception { ticket = new Ticket(); } @Test public void testOne() { BigDecimal total = new BigDecimal("99"); assertEquals(total, ticket.sale(1)); } @Test(expected=IllegalArgumentException.class) public void testNegative() { ticket.sale(-1); } @Test public void testZero() { assertEquals(BigDecimal.ZERO, ticket.sale(0)); } @Test public void test1000() { assertEquals(new BigDecimal(99000), ticket.sale(1000)); } }
重构完后再运行一下测试用例,确保测试通过的情况下,再增加几个测试用例,比如说门票销量为负数、零甚至一千的情况。
public class TicketTest { private Ticket ticket; @Before public void setUp() throws Exception { ticket = new Ticket(); } @Test public void testOne() { BigDecimal total = new BigDecimal("99"); assertEquals(total, ticket.sale(1)); } @Test(expected=IllegalArgumentException.class) public void testNegative() { ticket.sale(-1); } @Test public void testZero() { assertEquals(BigDecimal.ZERO, ticket.sale(0)); } @Test public void test1000() { assertEquals(new BigDecimal(99000), ticket.sale(1000)); } }
销量为负数的时候,王二希望功能代码能够抛出异常;销量为零的时候,功能代码的计算结果应该为零;销量为一千的时候,计算结果应该为 99000。
重新运行一下测试用例,结果如下图所示:
有两个测试用例没有通过,那么王二需要继续修改功能代码,调整如下:
public class Ticket { private final static int PRICE = 99; public BigDecimal sale(int count) { if (count < 0) { throw new IllegalArgumentException("销量不能为负数"); } if (count == 0) { return BigDecimal.ZERO; } if (count == 1) { return new BigDecimal(PRICE); } return new BigDecimal(PRICE * count); } }
再运行一下测试用例,发现都通过了。又到了重构的时候了,销量为零、或者大于等于一的时候,代码可以合并,于是重构结果如下:
public class Ticket { private final static int PRICE = 99; public BigDecimal sale(int count) { if (count < 0) { throw new IllegalArgumentException("销量不能为负数"); } return new BigDecimal(PRICE * count); } }
重构结束后,再运行测试用例,确保重构后的代码依然可用。
04、最后
从上面的实践过程可以得出如下结论:
TDD 想要做的就是让我们对自己的代码充满信心,因为我们可以通过测试代码来判断这段代码是否正确无误。
也就是说,TDD 流程比较关键的一环在于如何写出有效的测试代码,这里有 4 个原则可以参考:
1)测试过程应该尽量模拟正常使用的过程。
2)应该尽量做到分支覆盖。
3)测试数据应该尽量包括真实数据,以及边界数据。
4)测试语句和测试数据应该尽量简单,容易理解。
注意,这 4 个原则不仅适用于 TDD,同样适用于任何流程下的单元测试。
最后,我想说的是,不管 TDD 有没有死,TDD 都不是银弹,不可能适合所有的场景,但这不应该成为我们拒绝它的理由。