理解单元测试(Unit Testing)、TDD、BDD

当开始自动化测试你的代码的时候,多数人都会遇到很多疑问,你可能听说过各种人谈论单元测试、TDD、BDD等一堆高大上的方法,但是哪种方法更适合你呢?有没有办法可以同时使用他们呢?关于这些问题我和很多开发人员聊过,发现大家对这些方法或多或少都存在一些疑惑或误解,所以接下来我们来分别谈一下Unit Testing、TDD和BDD,希望借助这篇文章能给大家做出详细的梳理。

理解单元测试(Unit Testing)、TDD、BDD

单元测试—Unit Testing

单元测试一般关注的是单一的代码单元,一般可能是一个对象或者类的一个具体函数,这类关注单个函数的测试通常是简单的、容易书写并且可以快速运行的。你可以编写并运行很多单元测试来确保尽可能多的BUG被发现,特别是当你需要修改或者重构你的代码的时候,有一组可靠的单元测试做保护可以让你的操作更安全、更有信心不会带来对系统的未知破坏。

单元测试编写一般需要具有独立性,我们可以通过一些Mock工具来模拟一些外部依赖的条件,以便于确保单元测试的独立性,例如模拟网络访问、数据库访问等环境依赖。这可以让我们更集中精力到我们当前需要关注的逻辑和代码上,而不会花过多精力去维护各种不可控的场景和数据依赖,想象一下如果你为了跑一个单元测试需要对整个数据库做一次初始化或者清理操作,这样的单元测试,还是算了吧。

有一些人会把自动化测试等同于单元测试,这是一种错误的理解,实际有多个不同类型的自动化测试方法,并且每种方法都有其适用的场景和目标。下面列举了几种常见的自动化测试类型:

单元测试(Unit Tests):是对最小的软件设计单元的验证工作,测试对象通常是一个对象或一个函数。

集成测试(Integration tests):  一组需要运行多个测试进行验证的过程,通常是为了验证各个程序单元一起工作时的结果是否符合预期。

验收测试(Acceptance tests)/功能测试(Functional tests):  从用户视角对整个系统进行的测试,判断系统是否满足用户的验收条件。例如使用Selenium对Web应用进行自动化测试。

如果你觉得编写单元测试并不容易,最大的可能是你写的并不是单元测试。自动化的集成测试和验收测试通常更复杂并且运行更慢,也比单元测试更难维护,所以当你遇到问题时候应该首先判断一下,我是在写哪种类型的测试。

TDD(Test-Driven Development)

TDD是一个开发测试代码和业务代码的工作流程,基于这个流程你可以写出具有极高测试覆盖率(通常接近90%)的代码。TDD还可以减少测试中发现比较难以定位的BUG的可能性。

TDD的一般过程是:

  1. 写一个测试
  2. 运行这个测试,看到预期的失败
  3. 编写尽可能少的业务代码,让测试通过
  4. 重构代码
  5. 不断重复以上过程

要学习好TDD需要付出很多努力,但是相对于收获你会发现一切付出都是值得的。使用TDD开发的代码通常能达到90%甚至100%的测试覆盖率,也就是说这部分代码的可维护性是极高的,这组测试可以有效的构造一堵安全防线,让你对代码的任何修改、扩展、重构都能得到及时的反馈,不用担心会无意中破坏系统。

对大多数人来说,学习TDD的最大障碍在于你需要先写测试代码,然后才是产品代码,这是个思维转换和习惯养成的过程,需要不断的重复练习才能逐步掌握。

BDD

BDD(Behavior-Driven Development),往往是给我们带来最大困惑的原因。单元测试、TDD我们都还比较容易理解,那BDD是干嘛的呢?和以上两种方法的关系是什么呢?

BDD是一组编写优秀自动化测试的最佳实践,可以单独使用,但是更多情况下是与TDD 单元测试配合使用的。

BDD解决的一个关键问题就是如何定义TDD或单元测试过程中的细节。一些不良的单元测试的一个常见问题是过于依赖被测试功能的实现逻辑。这通常意味着如果你要修改实现逻辑,即使输入输出没有变,通常也需要去更新测试代码。这就造成了一个问题,让开发人员对测试代码的维护感觉乏味和厌烦。

BDD则通过向你展示如何设计测试来解决这个问题,你不应该再面向实现细节设计测试,取而代之的是面向行为来设计测试。例如下面这个测试代码就显示了在测试过程中关注实现细节时带来的问题:

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

这段代码是一个针对虚构的计数器对象的单元测试,我们这里验证的是调用一次后,计数器的值应该是1,看起来很合理吧?但是这个测试生效需要满足两个前提:

  1. 计算器起始值必须为0
  2. 计数器计数步长必须为1

以上这两个前提条件都是实现细节,而事实上这两个条件不应该对我们的测试代码带来影响,我们之所以这样测试的唯一原因是我们在写测试代码时候考虑的是代码如何实现而不是代码该有的行为。

BDD建议针对行为进行测试,我们不考虑如何实现代码,取而代之的是我们花时间考虑场景是什么,会有什么行为,针对行为代码应该有什么反应。所以针对这个例子我们需要确保:“如果我们触发了计数器,计数器应该加1”。所以这里重要是考虑方案而不是实现,它可以引导您设计更好的测试。基于这个思路上面的测试代码我们修改为下面的形式:

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

想象一种情况,假如我们遇到需求变更:计数器需要从一个已有的值基础开始计数,第一种实现方法需要我们修改测试代码,而基于BDD的测试方法则完全不需要修改测试代码。

结论

单元测试回答的是What的问题,TDD回答的是When的问题,BDD回答的是How的问题。可以把BDD看作是在需求与TDD之间架起一座桥梁,它将需求进一步场景化,更具体的描述系统应该满足哪些行为和场景,让TDD的输入更优雅、更可靠。你可以选择单独使用其中一种方法,也可以综合使用这几个方法以取得更好的效果。

来源:敏捷工坊,本文观点不代表自营销立场,网址:https://www.zyxiao.com/p/126158

发表评论

登录后才能评论
侵权联系
返回顶部