今天邀请公司的Yuheng同学给我们组和Zynx组的两个新同事进行TDD的培训。Yuheng同学首先针对我们这次活动的内容和方式做了介绍,并让所有参与的人提出了在使用TDD时候碰到的问题和对这次session的期望。从结果来看呢,大家的问题可以分为三类,第一TDD的基本知识,为什么做,怎么做,它的好处是什么,以及适用的场景与范围;第二类是如何用TDD驱动实现帮助设计,如何掌握TDD的节奏感,每一步的大小粒度等;第三类是测试用例相关,如何写测试用例等。然后Yuheng同学讲了这次的题目,猜数字。
猜数字游戏会产生一个非重复的四位数字,参与游戏的人给出四位非重复数字;对于游戏者给出的每一个数字,如果出现在游戏服务器给出的四位数字中但位置不对,就返回B;如果数字相同并且位置也对,就返回A;比如游戏服务器的数字是1234,而参与游戏的人给出的是1567,那么就返回"1A0B"。游戏如果返回4A0B,意味着四个数字都才对了,那么就打印"You Win';如果尝试超过六次,游戏就结束。
好,let's start write our first testcase!!
[TestMethod]
public void should_return_0A0B_given_no_number_match(){
var guessNumber = New GuessNumber();
var result = guessNumber.Validate("1234")
Assert("0A0B", result);
}
各位看官,从这里能看到什么?我和我的Pair按照这种方式不断的写我们的用例和实现,到第三个的时候就发现有问题了。我们的随机数呢,按照这样下去不可能驱动出这部分代码?我们卡住了!!! Yuheng点评的时候提出了一个问题,这个测试用例如何保证它的正确性呢? 哇偶,Yes。其实我和我的Pair在讨论的时候已经有一个隐含的知识写在纸上,那就是我们的测试用例默认的随机数为5678,但是这个知识没有体现在任何一个地方。那么让我们来改一下,如下面所示。
[TestMethod]
public void should_return_0A0B_given_no_number_match(){
var guessNumber = New GuessNumber("5678");
var result = guessNumber.Validate("1234")
Assert("0A0B", result);
}
那么这个测试用例是否就是完备的呢,我想是的。测试用例正确从而保证设计和实现的正确性,如果测试用例都是错的,那么设计和实现又怎么能得到质量的保证呢?所以测试用例是TDD中很重要的一部分。
另,其实我们在写测试用例候也是一个帮助分析测试用例的过程。我觉得对于刚开始者也许可以这样尝试,should_return_0A0B_when_no_number_match_given_input_as_1234_and_random_number_as_5678, 从某种程度上可以保证测试用例的正确性和数据准备的完备。 这也是一个设计的过程,因为当你在写测试用例的时候你已经在设计你的接口,就如上面例子中的GuessNumber.Validate();
在Review代码中,我们就如何选取第一个测试用例也做了讨论,大多数人在实现的时候都选取了GuessNumber, 有一个组选择了随机数的产生。那么如何选择测试用例呢?这和我们平常开发story实际上是一样的,在于它的优先级。影响它的因素不外乎两个,一个是价值一个是风险。在项目管理中,一个抉择一般是在项目初期尽量暴露项目的风险,并保证交付项目的价值。在这个原则指导下,我们就很容易得出结论,选择风险大价值大的需求作为开始。而GuessNumber这是这个游戏的核心功能,在开始前其实大家已经在内心做了评估,只是没有显示化。
那么到底TDD是什么呢。
TDD是一种帮助我们设计,驱动代码实现的手段,并且能够通过测试用例保证我们的软件交付质量。
TDD中的测试用例也是一种测试级别可运行的需求文档,反映项目的需求并保持同步。
另外,TDD要求先写测试用例,然后快速实现让测试用例通过,最后重构;逐步重复这个过程小步前进,直到完成需求。小步前进,通过测试用例保证每一步都是经过验证的,保证了代码的质量和开发人员的信心。快速是保证小步前进,如果一个测试用例超过15分钟还没有完成实现就说明测试用例过于复杂,就要考虑是不是步子迈的太大?重构是TDD的一个重要步骤,在重构过程中,如果目前的设计已经能够满足需求,那么就足够了,简单设计;在TDD中,由于有测试的保证,也使得重构成为可能,从而改进设计,也就是说TDD除了是一种测试软件的技术,更是一种设计的技术。
TDD的具体实践关于Fake Implementation的问题,它只是完成最终目标的一个中间过程。当我们看不到我们的实现方式或者称为算法的时候可以使用,我认为这也是TDD 强大的地方,通过不断的实现重构从而驱动出算法,这样的算法往往是正确无误的,而且看起来比较自然。如果一开始就应用某种Pattern或者某种算法,那么往往有可能导致错误的结果,增加了开发过程的复杂度。TDD让一切看起来是那么的自然。