测试驱动开发之可执行的文档
 

2009-12-03 作者:Nick Wang 来源:cnblogs

 

 TDD已经发展多年,然而却仍未能普遍成为日常开发的实践之一,很多人在尝试使用TDD时遭遇了困难,并对TDD心存疑惑。我并非TDD方面的权威或专家,但希望能将我的经验和感想记录下来,希望能对某些仍对TDD有所困惑的人有所帮助,同时也希望能够听到不同的声音,共同交流与讨论。

本教程不是工具的使用教程,也不是TDD入门知识普及教程,因此这里不会告诉你TDD是什么,TDD的工具如何使用。关于TDD的基本概念和工具的使用,已经有很多文档可以参考,请自行学习

测试驱动开发之可执行的文档

“编写单元测试更多是一种设计行为、文档行为而不是单纯的验证行为。 编写单元测试缩短了很多反馈周期;其中至少缩短了功能的验证周期。”   - Robert C. Martin

对于TDD的验证功能(测试功能)大家都不陌生,也很好理解,可是对于TDD的文档功能却有很多人不了解。

项目中的文档可以分很多种,例如需求文档、设计文档、用户手册、维护手册等等。这些文档可能是打印出来的,也可能以电子形式存在,而其内容可能是文字、图表、图形甚至是视频。今天我们讨论的文档偏向于设计文档,其他文档并不涉及。

设计文档一般有两种用途,在程序开发出来之前,设计文档用于指导编码,而在编码之后,设计文档用于帮助理解代码。MSDN中的类库参考也是一种设计文档(同时也是帮助文档),它描述了类库的使用方式和它们之间的联系,更重要的是其中有很多的例子可以帮我们理解如何使用这些类库。那么这和TDD有什么关系呢?

我们先看一个例子:

public void twelve_inches_should_equal_to_one_foot()
{
Inch twelveInches = 
new Inch(12);
Foot oneFoot = 
new Foot(1);

Assert.AreEqual(twelveInches, oneFoot);
}

public void one_foot_plus_two_inches_should_equal_to_fourteen_inches()
{
Foot oneFoot = 
new Foot(1);
Inch twoInches = 
new Inch(2);
Inch fourteenInches = 
new Inch(14);

Assert.AreEqual(oneFoot.Plus(twoInches), fourteenInches);
}
 

可以看到,TDD的测试用例实际上也是一个个使用这些类和函数的例子,测试函数的名称就是测试所要表达的意图,测试代码就是实现这种意图的方式。你可以从测试代码中得到以下信息:这个函数或类能干什么,约束条件是什么,何时会引发异常,合理的输入范围是什么,对应于输入的输出是什么,如何调用这些API等等。

那么为什么要用TDD来执行文档的功能呢?这就要说说文档的一些问题了。

首先要说的是,文档有很多的好处,这已经被广泛认可了,否则我也不用在这里说TDD的文档功能了;其次文档不一定非要是文字或图表的形式,我们的最终目的是要用文档来表达一些信息,一些从代码里无法得到或不容易得到的信息。传统文档如UML图,或是用NDoc之类的软件生成的API参考已经可以传达这些信息了,但是他们也有一些缺点:

1. 文档与代码不同步。通常代码永远都是最新的,而文档则会有一个滞后期,维护文档和代码的同步成本非常高,许多项目会因为这个成本而导致文档和代码不同步。这时候文档根本描述不了代码,而文档的意义也已经不存在了,要么你从文档中得到错误信息,要么用更多的时间去看代码。如果使用TDD的方式写代码,由于实现每个函数之前必须要先写相关的测试,保证了每个(public)函数都有文档说明,同时当函数行为发生变化时,也必须先添加或修改测试,保证了测试这份文档永远是最新的。

2. 项目进行中没有API文档。API的说明文档很多都是在代码完成后才写成的,项目进行时,经常会出现某个人误用另一个人写的函数而导致bug的情况。使用 TDD的方式,可以通过阅读测试代码来学习如何使用其他人写的类或方法,比直接读实现代码更有效,可以很好的避免误用的情况,有助于提高质量。

3. 写文档成本太高,没时间写,程序员不喜欢或不擅长写文档。TDD作为一个开发实践有多种功能,文档只是其中之一,同样时间内用TDD可以获得更多的收益,降低了文档的成本。同时,测试也是用程序员熟悉的语言写成,程序员比较容易掌握。

那是不是只要TDD就好了,不需要其他的文档了呢?当然不是,TDD的文档功能有很多好处,但是什么东西都不是万能的,我们还是需要配合多用形式的文档来达到我们的目的。具体实践时可根据对TDD的掌握程度,项目对文档的要求,团队对文档的要求,来决定用什么形式来撰写文档。

使用TDD的文档功能时需要非常注意代码的可读性,这是它能否执行文档功能的一个重要条件。

再来一个UAT测试的例子:

 public void should_not_create_publication_in_offline_mode_when_click_no()
 {
    using (ribbonTestUtility = new RibbonTestUtility())
    {
        ribbonTestUtility.StartWordInOfflineMode();
        ribbonTestUtility.ClickCreatePublication();

        OfflineWarningDialogModel warningDialogModel = ribbonTestUtility.GetOfflineWarningDialog();
        warningDialogModel.ClickNo();

        EftAssert.IsWindowNotExists(ribbonTestUtility.EftApplication,
                                            RibbonElementName.SETUP_WIZZARD_WINDOW_TITLE);
    }
 }

 

用下划线来分割单词虽然不符合C#的命名规范,可是提高了可读性,因为Pascal Case的方法名很长时可读性会很差,毕竟命名规范是为了提高可读性而设计的。方法名应该表示该测试的意图,并符合英语语法,可以当成普通的英语句子阅读。

我的一个同事曾经参加过一个项目,在这个项目中有很多领域词汇,很难用英语表达出来,即使表达出来看的人也很难理解。考虑到沟通成本以及项目中并没有老外存在的情况,他们决定使用中文TDD,用中文给测试用例命名。虽然用中文写代码并不是很好的实践,但在这个项目中确实能够解决问题,可以说应变的非常好。也希望大家能够认清技术和实践的本质和目的,灵活运用,避免盲从。

总结

TDD除了可以验证功能外,还具有文档功能。它永远保持最新,可以表达程序意图,并且可以编译执行,是可执行的文档。但是决不能用TDD文档代替所有文档,需要在项目中进行权衡。在实践中,代码可读性影响着TDD文档功能的效果,表达不了意图的代码是无法执行文档功能的。

火龙果软件/UML软件工程组织致力于提高您的软件工程实践能力,我们不断地吸取业界的宝贵经验,向您提供经过数百家企业验证的有效的工程技术实践经验,同时关注最新的理论进展,帮助您“领跑您所在行业的软件世界”。

资源网站: UML软件工程组织