[译]单元测试、TDD和BDD区别

当我开始学习前端测试知识的时候,我经常听到很多概念,单元测试(Unit testing)、TDD(Test-Driven Development 测试驱动开发)、BDD(Behavior-Driven Development 行为驱动开发)。其中最让我困惑的是TDD和BDD,两者有什么区别,应该如何使用呢 ?

Unit testing(单元测试)

单元测试集中于一个“代码单元”——通常是对象或模块中的一个函数。 单元测试简单、编写快速、运行快速。 这意味着你可以有许多单元测试,更多的单元测试意味着可以捕获更多的错误。 如果你需要更改代码,它们特别有用: 当你有一组单元测试来验证代码的工作时,你可以安全地更改代码,并确保程序的其他部分不会出错。
单元测试应该与依赖关系隔离——例如,不允许网络访问和数据库访问。 你可以用过一些工具去模拟替代这些依赖关系。 这使得测试各种需要大量第三方模块的场景变得非常简单。
有一种误解,认为单元测试需要特定的语法来编写。 这种所谓的“xUnit style”语法在许多稍微老一点的测试工具中很常见。 下面是一个使用Mocha的“xUnit style”的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
suite('My test suite name', function() {
setup(function() {
//do setup before tests
});

teardown(function() {
//clean up after tests
});

test('x should do y', function() {
//test something
});
});

但是这只是一个工具的例子。 你不必为单元测试使用任何特定的语法。实际上,你甚至可以用纯 JavaScript 编写单元测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//suite: User

//test: Name should start empty
var user = new User();
if(user.getName() !== '') {
throw new Error('User name should start as empty');
}

//test: Password should be hashed
var user = new user();
user.setPassword('hello');
if(user.getPassword() != bcrypt('hello')) {
throw new Error('User password should be hashed with bcrypt');
}

单元测试的基本部分有: 单个测试,它测试一个东西,并且它们彼此独立。你可以使用这种原始的代码构建基本的单元测试。 但是使用一个单元测试工具,比如 Mocha 或者 Jasmine,将使编写测试变得更加容易,而且它们还有其他有用的特性,比如在测试失败时更好地报告(这使得发现错误更加容易)
有些人认为任何自动化测试都是单元测试。 这是不对的。不同类型的自动化测试都有自己的目的。
以下是三种最常见的自动化测试类型:

  • 单元测试: 测试一段代码(通常是一个对象或函数) ,与其他部分隔离开来
  • 集成测试: 将多个部分放在一起进行测试
  • 验收测试(也称为功能测试) : 对整个应用程序进行自动测试,例如使用像 Selenium 这样的工具来自动运行浏览器
    如果你觉得编写单元测试并不容易,那么很可能它根本就不是单元测试。集成测试和验收测试都更复杂,并且通常运行得更慢。它们也比单元测试更难维护,因此如果你遇到问题,请确保你编写的测试类型是正确的。

    TDD(测试驱动开发)

    测试测试驱动开发是一个编写和运行测试的过程。遵循它可以获得非常高的测试覆盖率。测试覆盖率指的是自动测试的代码的百分比,覆盖率越高越好。Tdd还减少了测试中出现错误的可能性,否则很难跟踪这些错误。
    Tdd 过程包括以下步骤:
  1. 写一个单元测试去描述程序的一个方面。
  2. 运行它应该会失败,因为程序还缺少这个特性。
  3. 编写使测试通过所需的最小代码量
  4. 运行测试以检查新测试通过情况
  5. 可以选择重构代码
  6. 重复第一个步骤,重新完善测试代码
    按照上面的思维方式去实现需要花不少时间,但是也有好处。 Tdd 项目的代码覆盖率通常为90%-100% ,这意味着维护代码和添加新特性很容易。 这是因为你有大量的测试集,因此你可以信任代码和更改工作,并且不会破坏任何其他代码。
    有些人认为你必须使用“xUnit style”测试工具来使用 TDD 过程。 事实并非如此—— TDD 在单元测试中工作得很好,但是你也可以将它应用到其他测试方法中。 它也不需要任何特定的工具或语法。
    对于许多开发人员来说,TDD 最困难的事情是在编写代码之前必须编写测试。

    BDD(行为驱动开发)

    BDD(行为驱动开发)可能是最大的困惑来源。当应用于自动化测试时,BDD 是编写优秀测试的一组最佳实践。 Bdd 应该与 TDD 和单元测试方法一起使用。
    Bdd 解决的一个关键问题是单元测试中的实现细节。糟糕的单元测试的一个常见问题是它们过于依赖测试函数的实现方式。这意味着,如果更新函数,即使不更改输入和输出,也必须更新测试。这是一个问题,因为它使做改变变得单调乏味。
    Bdd通过向你展示如何测试来解决这个问题。 你不应该测试实现,而应该测试行为。让我给你举一个例子,当你思考实现时,当你思考行为时,会发生什么:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    suite('Counter', function() {
    test('tick increases count to 1', function() {
    var counter = new Counter();

    counter.tick();

    assert.equal(counter.count, 1);
    });
    });
    这是一个虚构的计数器对象的单元测试。我们测试在调用 tick 之后,这个值应该是1,这听起来很合理。 但是在测试中有一个问题。测试完全依赖于计数器从0开始这一事实。 所以换句话说,这个测试依赖于两件事情。
  7. 计数器从0开始
  8. tick调用增量1
    计数器从0开始是一个与 tick ()函数的行为无关的实现细节。因此,它不应该对测试有任何影响。我们这样编写测试的唯一原因是我们考虑的是实现,而不是行为。
    Bdd 建议测试行为,所以我们不去考虑代码是如何实现的,而是花一点时间考虑场景是什么。 通常,BDD 测试的表达形式是“它应该做些什么”。 因此,当tick调用时,它应该增加一个计数。
    这里重要的部分是考虑场景,而不是实现,可以引导你设计一个更好的测试。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    describe('Counter', function() {
    it('should increase count by 1 after calling tick', function() {
    var counter = new Counter();
    var expectedCount = counter.count + 1;

    counter.tick();

    assert.equal(counter.count, expectedCount);
    });
    });
    在这个版本的测试中,使用了 Mocha 的 BDD 风格函数,我们去掉了实现细节。我们不是依赖于从0开始的计数器,而是与 counter.count + 1进行比较,后者在测试行为方面更有意义。
    有时候你的需求会改变。让我们想象一下,由于某种原因,计数器必须从其他值开始。在此之前,我们必须更改测试以适应这一点,但是对于BDD编写的测试代码而言,没有必要这样做。

总结

单元测试是做测试的基础。测试驱动开发讨论的是什么时候写测试的。行为驱动开发告诉我们测试用例应该怎么做好。尽管你可以单独使用它们,但是你应该将它们组合起来以获得最佳结果,因为它们彼此很好地相辅相成。

原文地址:What’s the difference between Unit Testing, TDD and BDD