本文共 1437 字,大约阅读时间需要 4 分钟。
3.5 先测试驱动接口再测试驱动内部实现
好的接口对于设计良好的模块来讲很关键。前面几个测试会驱动接口设计。关注于接口意味着我们是从外向内开发代码的。测试作为接口的首个用户,从调用者(或客户端代码)的角度给出了开发代码的使用方式。从使用者的角度出发会产生可用性更强的接口。我通常也会让前面的几个测试来检验一些产品代码的边界条件。选择一个带边界检查的简单用例。为了消除这个编译错误,在模块的接口声明头文件中增加这个接口函数原型:
写出并且通过这些测试能帮助我们实现以下目标:它定义了驱动程序的一个接口函数并确保我们的做法在硬件上可行。但我保证你肯定还是很困惑。
实现是错的正如很多工程师一样,你很可能会对硬编码一些明显错误的东西感到很不安。这样的最终实现也应该只能够设置最低的那一位。但是,你仔细想想,到目前我们写的测试已经通过。如果我们不是在练习TDD,那么还要写更多的测试,否则这样的错误实现会导致软件缺陷。但我们正是在使用TDD并且还会写更多的测试来暴露这些弱点。我很难想象这样的实现,即让我们测试列表中的测试都通过之后还能在原来的位置保持这些错误。但如果你发现硬编码了一些东西并且不能被现有的测试列表所覆盖,那么马上写一个测试来暴露这个弱点或者在测试列表中加上一个新条目。测试是对的在实现还不完整的时候,你可能会认为什么也没有测试到。当然有!现在的测试确保将变量置为1!请尝试从另一个角度来思考这个问题。测试是对的!它们是TDD产生的非常有价值的副产品。这些简单的实现测试了我们的测试用例。测试用例失败表明这些测试可以发现错误的结果。把正确答案硬编码进去能证明测试用例可以发现正确的结果。这些测试是正确的并且具有价值,尽管产品代码还不完整。稍后,随着实现的演进,这些看似琐碎的测试用例会测试重要的行为和边界条件。从根本上来讲,我们是在收紧一个夹在被测代码上的老虎钳,把代码的行为紧紧夹住。别担心,产品代码不会一直这样硬编码很久的。一旦你需要打开一个不同的LED,就不需要硬编码了。真实的实现并不是很难,但我要求你坚持抵制住不去写那些不是现在的测试所需要的代码。我们在演进设计。增加那些不是现在的测试所需要的代码的问题是,你大概就不会再去写能让代码不产生bug所需的所有测试了。
在有测试要求之前就增加代码会增加复杂度。有时你对需求的理解可能是错的,其结果是引入了不必要的复杂度。并且,“我会用到这个”的想法永无止境。那么什么时候才要停下来?在TDD实践中,在当前的测试不再对代码有新的要求时我们就会停下来。大概的终点列举在测试列表中。TDD是一种结构化的延迟。它推迟产生正确的产品代码,直到由测试强迫我们完成实现。其最终的目标在所有正确的测试都就位之后才实现。选择下一个测试下一步要测试什么?我们当然可以写一个新测试来迫使我们移除由简单思维而生成的实现。但我更倾向于进一步演进接口以使在建的模块的样子更清楚些。让我们把刚刚打开的LED关掉。打开和关闭刚好互相补充,并且可以很方便地在后面验证LED的操作时不会互相影响。另外,我们甚至可以真的把这个代码发布到目标系统中,如果我们需要做的只是打开LED 1。就这么定了——让我们写个测试来关闭LED 1。现在所有的测试又都通过了。
此时此刻,LED驱动程序的接口已经初具规模。我们有了三个测试和一个驱动程序的骨架实现。我们会在第4章再回到这段代码上来。但首先让我们来讨论一下我们做的这几个小的步骤。
转载地址:http://mxnax.baihongyu.com/