TDD(测试驱动开发)死了吗?

简介: 很早之前,曾在网络上见到过 TDD 这 3 个大写的英文字母,它是 Test Driven Development 这三个单词的缩写,也就是“测试驱动开发”的意思——听起来很不错的一种理念。

01、前言

很早之前,曾在网络上见到过 TDD 这 3 个大写的英文字母,它是 Test Driven Development 这三个单词的缩写,也就是“测试驱动开发”的意思——听起来很不错的一种理念。

其理念主要是确保两件事:

  • 确保所有的需求都能被照顾到
  • 在代码不断增加和重构的过程中,可以检查所有的功能是否正确

但后来很长一段时间里,都没再听过 TDD 的消息。有人说,TDD 已经死了,给出的意见如下:

1)通常来说,开发人员不应该在没有失败的测试用例下编写代码——这似乎是合理的,但是它可能导致过度测试。例如,为了保证一行生产代码的正确性,你不由得写了 4 行测试代码,这意味着一旦这一行生产代码需要修改,你也得修改那 4 行测试代码。

2)为了遵循 TDD 而写的代码,容易进入一个误区:代码是为了满足测试用的,而忽略了实际需求。

02、TDD 到底是什么?

不管 TDD 到底死了没有,先让我们来回顾一下 TDD 到底是什么。

TDD 的基本思想就是在开发功能代码之前,先编写测试代码。也就是说在明确要开发某个功能后,首先思考如何对这个功能进行测试,并完成测试代码的编写,然后编写相关的代码满足这些测试用例。然后循环进行添加其他功能,直到完全部功能的开发

TDD 的基本过程可以拆解为以下 6 个步骤:

1) 分析需求,把需求拆分为具体的任务。

2) 从任务列表中取出一个任务,并对其编写测试用例。

3) 由于没有实际的功能代码,测试代码不大可能会通过(红)。

4) 编写对应的功能代码,尽快让测试代码通过(绿)。

5) 对代码进行重构,并保证测试通过(重构)。

6) 重复以上步骤。

可以用下图来表示上述过程。

2.jpg

03、TDD 的实践过程

通常情况下,我们都习惯在需求分析完成之后,尽快地投入功能代码的编写工作中,之后再去调用和测试。

而 TDD 则不同,它假设我们已经有了一个“测试用户”了,它是功能代码的第一个使用者,尽管功能代码还不太完善。

当我们站在“测试用户”的角度去写测试代码的时候,我们要考虑的是,这个“测试用户”该如何使用功能代码呢?是通过一个类直接调用方法呢(静态方法),还是构建类的实例去调用方法呢(实例方法)?这个方法如何传参呢?方法如何命名呢?方法有返回值吗?

有了测试代码后,我们开始编写功能代码,并且要以最快地速度让测试由“红”变为“绿”,可能此时的功能代码很不优雅,不过没关系

当测试通过以后,我们就可以放心大胆的对功能代码进行“重构”了——优化原来比较丑陋、臃肿、性能偏差的代码。

接下来,假设我们接到了一个开发需求:

汪汪队要到小镇冒险岛进行表演,门票为 99 元,冒险岛上唯一的一个程序员王二需要开发一款可以计算门票收入的小程序。

按照 TDD 的流程,王二需要先使用 Junit 编写一个简单的测试用例,测试预期是:销售一张门票的收入是 99 元。

public class TicketTest {
    private Ticket ticket;
    @Before
    public void setUp() throws Exception {
        ticket = new Ticket();
    }
    @Test
    public void test() {
        BigDecimal total = new BigDecimal("99");
        assertEquals(total, ticket.sale(1));
    }
}

为了便于编译能够顺利通过,王二需要一个简单的 Ticket 类:

public class Ticket {
    public BigDecimal sale(int count) {
        return BigDecimal.ZERO;
    }
}

测试用例运行结果如下图所示,红色表示测试没有通过:预期结果是 99,实际结果是 0。

3.jpg

那接下来,王二需要快速让测试通过,Ticket.sale() 方法修改后的结果如下:

public class Ticket {
    public BigDecimal sale(int count) {
        if (count == 1) {
            return new BigDecimal("99");
        }
        return BigDecimal.ZERO;
    }
}

再运行一下测试用例,结果如下图所示,绿色表示测试通过了:预期结果是 99,实际结果是 99。

4.jpg

绿了,绿了,测试通过了,到了该重构功能代码的时候了。99 元是个魔法数字,至少应该声明成常量,对吧?

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));
    }
}

销量为负数的时候,王二希望功能代码能够抛出异常;销量为零的时候,功能代码的计算结果应该为零;销量为一千的时候,计算结果应该为 99000。

重新运行一下测试用例,结果如下图所示:销量为负数的时候,王二希望功能代码能够抛出异常;销量为零的时候,功能代码的计算结果应该为零;销量为一千的时候,计算结果应该为 99000。

重新运行一下测试用例,结果如下图所示:

5.jpg

有两个测试用例没有通过,那么王二需要继续修改功能代码,调整如下:

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 都不是银弹,不可能适合所有的场景,但这不应该成为我们拒绝它的理由。


相关文章
|
2月前
|
Devops Java 测试技术
软件测试/测试开发|常见软件测试框架类型:TDD、BDD、DDD、ATDD、DevOps介绍
软件测试/测试开发|常见软件测试框架类型:TDD、BDD、DDD、ATDD、DevOps介绍
96 0
|
2月前
|
监控 前端开发 JavaScript
如何测试前端代码? 知道 BDD, TDD, Unit Test 么? 知道怎么测试你的前端工程么(mocha, sinon, jasmin, qUnit..)?
如何测试前端代码? 知道 BDD, TDD, Unit Test 么? 知道怎么测试你的前端工程么(mocha, sinon, jasmin, qUnit..)?
94 0
|
2月前
|
敏捷开发 测试技术
深入理解并应用测试驱动开发(TDD)
【5月更文挑战第30天】测试驱动开发(TDD)是一种先编写测试用例再写代码的方法论,核心是&quot;先测试,后开发&quot;。通过&quot;红-绿-重构&quot;的循环,确保代码正确性,提升简洁性和可维护性。TDD步骤包括编写测试用例、运行测试、编写实现代码、重构及循环迭代。它能提高代码质量,促进团队合作,降低维护成本,并适应敏捷开发。在开发实践中,应用TDD能有效提升效率和代码质量。
|
2月前
|
算法 测试技术 开发者
测试驱动开发(TDD)实战:从理论到实践
【5月更文挑战第8天】TDD实战指南:先测试后开发,确保代码质量与可维护性。核心思想是编写测试用例→实现代码→验证→重构。优点包括提高代码质量、促进设计思考和增强可测试性。实战步骤包括编写独立、明确的测试用例,遵循最小可用原则编写代码,运行测试并分析失败原因,以及在验证通过后进行代码重构与优化。通过TDD,开发者能提升编程技能和项目成功率。
|
2月前
|
测试技术 持续交付 Swift
【Swift开发专栏】Swift中的测试驱动开发(TDD)
【4月更文挑战第30天】Test-Driven Development (TDD) 是一种软件开发方法,强调先编写测试用例再写代码。通过测试驱动代码、简明设计、重构和持续集成的循环过程,TDD助力构建高质量Swift软件。在Swift中,使用XCTest框架进行TDD实践,包括编写测试用例、实现功能、运行测试和重构。TDD的优势在于提升代码质量、减少调试时间,且与持续集成相结合。然而,学习曲线和确保测试覆盖率是挑战。TDD不仅是技术实践,也是思维方式,随着Swift的发展,其应用将更广泛。
|
2月前
|
测试技术 开发者
【专栏】测试驱动开发(TDD)和行为驱动开发(BDD)的核心概念与实践
【4月更文挑战第27天】本文探讨了测试驱动开发(TDD)和行为驱动开发(BDD)的核心概念与实践。TDD强调先写测试用例,通过测试推动设计,确保代码质量与可维护性。BDD侧重软件行为和业务价值,提倡使用通用语言描述行为,减少沟通障碍。选择TDD或BDD取决于项目复杂性、团队技能和业务需求。理解两者差异有助于团队做出合适的选择,发挥测试的最大价值。
|
2月前
|
IDE Java 测试技术
Java 测试驱动开发(TDD)实践指南
【4月更文挑战第19天】测试驱动开发(TDD)在Java中强调先写测试用例再写代码,以提升代码质量和可维护性。核心原则是先写测试,确保代码功能正确,降低风险并促进良好设计。实践包括选择IDE,安装JUnit,明确需求,编写失败测试,写最小代码使测试通过,重构并重复该过程。编写测试用例应明确、独立且可重复。TDD能增强开发信心,提高效率,保证代码维护性。遵循指南,开发者能写出高质量代码,为未来开发打下基础。
|
2月前
|
监控 前端开发 JavaScript
如何测试前端代码? 知道 BDD, TDD, Unit Test 么? 知道怎么测试你的前端工程么(mocha, sinon, jasmin, qUnit..)?
如何测试前端代码? 知道 BDD, TDD, Unit Test 么? 知道怎么测试你的前端工程么(mocha, sinon, jasmin, qUnit..)?
48 0
|
2月前
|
测试技术 Python
Python中的单元测试与测试驱动开发(TDD)实践
Python中的单元测试与测试驱动开发(TDD)实践
110 0
|
2月前
|
自然语言处理 测试技术
测试驱动开发(TDD)与行为驱动开发(BDD)的比较与选择
在软件开发中,测试驱动开发(TDD)与行为驱动开发(BDD)是两种常见的开发方法。虽然它们都强调测试在开发过程中的重要性,但是两者之间存在一些差异。本文将对TDD和BDD进行比较,分析它们各自的优点和缺点,以及在实际开发中如何选择最适合的方法。