FrankKai / FrankKai.github.io

FE blog
https://frankkai.github.io/
363 stars 39 forks source link

[译]单元测试,TDD和BDD之间的区别是什么? #123

Open FrankKai opened 5 years ago

FrankKai commented 5 years ago

原文链接:What’s the difference between Unit Testing, TDD and BDD?

FrankKai commented 5 years ago

当你想对js代码进入自动化测试的时候,大脑里会有很多问题。你也许看到人们在谈论单元测试,TDD或者测试-驱动-开发,BDD或者行为-驱动-开发。但是哪一种是最好的方法?它们都可以被使用吗?

我曾经和许多JS开发者聊过,多多少少都会对这个问题有困惑。所以,让我们来探索一下单元测试,TDD和BDD,并且来纠正几个人们对它们的误解。

FrankKai commented 5 years ago

单元测试

单元测试的关注点在单个"unit of code"-通常是一个对象或模块中的函数。 通过为单个函数创建测试用例,测试起来很方便。这也就意味着你可以有更多单元测试,单元测试越多,bug越容易捕获。这对于你修改代码很有用:当你有一组单元测试保证你的代码正常工作后,你可以安全地改变代码并且相信这次改变不会导致程序的其他部分崩溃。

单元测试需要与依赖分离,非常独立-例如,没有网络权限,没有数据库权限。有很多工具可以帮助你用假的替换掉这些依赖。这样就不用在测试之前进行大量的安装配置了。例如,想想一下自己需要安装一个完整的数据库去运行一个测试。算了,放过我吧。

有一个错误的观念是:单元测试需要特殊语法才能写。这种被称作"xUnit style" 的语法通常在一些轻量工具里有用到。下面的就是Mocha"xUnit 风格"的一个例子:

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

但是这只是一个工具的例子。你不需要为了单元测试写特别的语法-事实上,你可以用纯js写单元测试:

var user = new User();
if(user.getName()!==''){
    throw new Error('User name should start as empty');
}
var user = new user();
user.setPassword('hello');
if(user.getPassword()!=bcrypt('hello')){
    throw new Error('User password should be hashed with bcrypt');
}

单元测试的核心在于:

有人认为任何自动化测试就是单元测试。并不是。他们与自动化测试不同,每个类型都有自己的目的,解决不同的痛点。

这里有3种主要类型的自动化测试:

如果你感觉写单元测试很困难,可能他根本就不是单元测试。所有的集成测试和验收测试都是很复杂并且运行起来很慢的。他们通常要比单元测试更难维护,所以如果你有问题,那么确保你自己写的是正确的测试类型。

FrankKai commented 5 years ago

TDD

TDD或者Test-Driven Development是你写和运行自己的测试时的一个步骤。遵循这些步骤,可以使得你有非常高的测试覆盖率。测试覆盖率代表着你的代码自动化测试的比率,所以数字越大越好。TDD同时也降低代码中出现错误的可能性,因为bug真的很难追踪。

TDD步骤包括以下的步骤: 1.开始写测试。 2.运行当前测试和其它测试。在这个环节,你的新加进来的测试会失败。如果他没有失败,它也许没有测试对内容,也许有一个bug在其中。 3.写最少的代码去让测试通过。 4.运行测试区检查新的测试通过。 5.选择性重构代码。 6.重复第一步。

努力一些学得更好些,花时间赚大钱。TDD项目通常会有90到100的测试覆盖率,这也就意味着维护代码和新特性非常容易。这因为你可以有很大规模的测试集,所以你可以信任你的代码变化后仍可以工作。

有些人认为必须用xUnit 方式 测试工具去做TDD。其实并不是,TDD在没有单元测试的情况下也可以,但是你可以将其应用在其他的测试方法上。也可以不依赖任何特定工具或语法。

对于许多开发者来说,TDD最难的事情在于,你要在写你的代码之前写测试

FrankKai commented 5 years ago

BDD

BDD-Behavior-Driven Development - 这是最容易让人误解的一个部分。 当应用自动化测试的时候,BDD是写出好的测试的最佳实践集合。BDD应该和TDD以及单元测试方法一起使用。 BDD地址的最重要的部分在于:单元测试的实现细节。一个通常的问题在于,不好的单元测试会依赖太多的测试函数。这也就意味着如果你更新了函数,在没有改变输入和输出的情况下,你必须更新测试。这会很费劲。 BDD解决了如果测试的问题。你不能测试实现,而是应该测试行为。我这里给出一个测试实现和测试行为的对比:

suite('Counter',function(){
    test('tick increase count to 1', function() {
        var counter = new Counter();
        counter.tick();
        assert.equal(counter.count, 1);
    });
});

这是一个想象中的计数对象。我们会在调用一次tick后测试这个,值应该是1,这听起来很有道理。但是测试中是有问题的。Test完全取决于计数器从0开始的事实,换句话说,这个测试依赖两件事: 1.Counter 从 0开始 2.每次tick增加1

从0开始和每次tick加1是不相干的。因此在测试中不能有任何容忍。我们写BDD类型的测试,要考虑实现,而不是考虑行为。

BDD建议测试行为,所以与考虑代码实现不同,我们需要花些时间考虑场景是什么。 典型的情况是:BDD test的格式是"它应该做什么"。所以当ticking一个counter的时候,应该只增加1。

这个场景最重要的部分在于,与实现不同,可以让你设计一个更好的测试。

describe('Counter',function(){
    if('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);
    })
})

我们这次不从0开始了,而是从 counter.count + 1 开始,这样让测试更有意义。 BDD模式下,适应性更强。

FrankKai commented 5 years ago

总结

单元测试-> what
独立的测试,与其他的内容相独立 测试驱动开发 -> when 在写你的代码之前写测试 行为驱动开发 -> how BDD建议测试行为,所以与考虑代码实现不同,我们需要花些时间考虑场景是什么 虽然您可以单独使用它们,但您应该将它们组合起来以获得最佳效果,因为它们可以非常好地相互补充。