测试驱动开发系列之一--嵌入式TDD基础知识

测试驱动的嵌入式C语言开发的名言名句

以动手实践为荣,以只看不练为耻。
以打印日志为荣,以单步跟踪为耻。
以空格缩进为荣,以制表缩进为耻。
以单元测试为荣,以人工测试为耻。
以模块复用为荣,以复制粘贴为耻。
以多态应用为荣,以分支判断为耻。
以pythonic为荣,以冗余拖沓为耻。
以总结分享为荣,以跪求其解为耻。

问题1:为什么在开发过程中,TDD提倡以单步调试为耻呢。

单步跟踪,对于面对代码中的bug一筹莫展的程序员来讲的确是最后一根救命稻草,但同时也意味着程序员对于代码的行为不能完全掌控。如果需要请出调试器(或者用在代码中加printf的方式来调试,其作用和结果是一样的),那么往往就要花上几分钟甚至几天的时间才能定位到问题所在。如果能够根据系统或者测试所提供的日志(包括出错消息等)马上定位到问题,那么这几分钟到几天的时间就节省下来了。TDD的确意味着你要深入到某个特定的方法或者特定的测试细节中去,这样往往会使手头的测试方向变得迷惑不清。TDD的强项之一是对边界条件的测试。我的嵌入式灾难档案中充满了棘手的代码错误,这些代码错误是由溢出、“偏了一个”或者诸如此类的原因造成的。嵌入式TDD围绕着创建一个测试框架而开展,也就是一个让程序员可以描述代码应该有怎样的行为的软件包。
问题2:TDD测试驱动开发存在的价值

调试代码比写代码难一倍。因此从这个意义上讲,调试你穷思竭虑写出的代码时你将才尽思穷。
代码评审是有必要的,但只有代码运行起来才是唯一能确认它正确的方式。
TDD并不是一种测试技术,它是解决编程问题的一种方法。TDD还很有趣,它就像一个游戏,你在技术迷宫中将软件引向高可靠的方向,同时还避免了无聊的调试过程。

问题3:TDD的机理是怎么样的

TDD会给出立即的反馈!对失误的立刻通知将帮助我们避免BUG。TDD是预防BUG,后期调试式的编程(DLP)则是把浪费制度化。
TDD不是花一个小时一天或者一个星期来写一大堆的测试代码,然后再来实现产品代码的开发方式。TDD是写一个小小的测试,然后写够让这一个测试通过的产品代码。同时不能破坏已有的测试。TDD要求你在构建一个东西之前先决定你到底要什么。TDD的核心是由小步骤来不断重复的循环组成,称之为TDD微循环。
TDD循环中每一步都只需要几秒到几分钟的时间,新的代码和测试以增量的方式加入进来,并且立刻得到关于我们新写的代码是否和我们预期一致的反馈。在这个过程中,你不仅了解到了解决方案,同时也对所解决的问题有了进一步了解。测试变成了对需求细节的翔实描述。
问题4:TDD的好处有哪些呢

产生更少的bug
调试时间更短
边际效应所带来的bug更少
单元测试是不会说谎的文档
内心的平静(全面的回归测试给于我们信心)
改善设计(好的设计一定是可测试的设计)
对进度的监控(它对于完成给出了新的估计方法和定义)
有趣且回报丰厚(TDD不断给开发者以成就感)
问题5:TDD对于嵌入式开发的益处
在硬件可用之前或者硬件很贵或者很稀少时,通过独立于硬件的方式校验产品代码,从而降低风险。
在开发环境中执行并移除bug,从而减少长时间的编译,链接和上传这样的循环的次数。
对于在目标硬件中既难发现又难修改的问题,可以减少调试的时间。
通过在测试中模型化硬件交互,使硬件软件的交互彼此独立。
通过解开模块之间以及模块与硬件之间的耦合来改进设计。可测试的代码首先必须是模块化的。

保持代码整洁和富有表现力。红灯⇒绿灯⇒重构被称之为TDD的节奏。当所有的测试都通过时,就可以安全的重够了。

问题6:TDD的四阶段模式是什么

建立:创建测试的前置条件
运行:对系统进行操作
验证:检查预期的输出
拆卸:把被测系统恢复到测试前的初始状态
为了让测试过程清晰,明了,要让测试中的这种模式清晰可见。当这种模式被打破时,测试作为文档的价值就打了折扣,阅读测试代码的人将很难读懂测试要表达的需求是什么。运行测试还有一种结果,那就是崩溃。

问题7:如何写一个测试列表,测试列表有什么用
在开发新功能之前先创建一个测试列表会很有帮助。测试列表由需求衍生而来。测试列表定义了你对将需要完成的功能的最好的理解。这个列表不需要很完美。你可以直接吧它当做注释输入到测试文件中。随着每个测试的添加,对应的注释将被删除。
请当心在创建测试列表时的报酬递减。一旦你写下几个测试,其他的会很容易想到。当进展很慢时,你可能已达到了报酬递减的边界,这可能就是一个停止工作在测试列表转而去测试驱动设计的好时机。在驱动设计的时候你会想到其他的测试。
还有应该从能把工作向目标推进的很小或很简单的地方开始。这个过程中的每一步都是可验证的。产品代码的功能随着一次一个的测试而增加,朝着健壮的并且被很好测试过的解决方案前进。

问题8:通过TDD驱动出来的依赖注入(dependency injection)模式
控制反转(Inversion of Control,英文缩写为IoC)是一个重要的面向对象编程的法则来削减计算机程序的耦合问题,也是轻量级的Spring框架的核心。 控制反转还有一个名字叫做依赖注入(Dependency Injection)。简称DI。
对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用,传递给它。也可以说,依赖被注入到对象中。所以,控制反转是,关于一个对象如何获取他所依赖的对象的引用,这个责任的反转。

先测试驱动接口再测试驱动内部实现
 好的接口对于设计良好的模块来讲很关键。前面几个测试会驱动接口设计。关注于接口意味着我们是从外向内开发代码的。测试作为接口的首个用户,从调用者的角度给出了开发代码的使用方式。从使用者的角度出发会产生可用性更强的接口。

用三个简单的法则来描述测试驱动开发
除非是让一个失败的单元测试通过,否则不要写产品代码
不要写比足以失败更多的单元测试,构建失败也可以
不要写比足以让单元测试通过更多的代码
尽管听上去很有约束感,但是它却是很高产并很有趣的软件开发方式

问题9:测试要做到FIRST,什么是FIRST。如何做到FIRST

FIRST是单元测试的关键属性。
F(Fast)测试要快速,快到程序员可以在每个微小的改动后都运行它们而且不会打断工作流。
I(IsoLated)测试用例要独立的。一个测试不会为另一个测试创建环境。测试中的失败也要分离开。
R(Repeatable)测试要可以重复地去运行。可重复意味着自动化。一个循环中的测试总是给出同样的结果。
S(Self-verifying)测试会自己检查产出。通过时给出简单的OK,失败时给出详细的细节。
T(Timely)测试要及时做。程序员要及时写测试,与写产品代码紧连(但必须在前面)来防止软件BUG的产生。
这对于用C来做测试驱动开发来讲很有挑战。TDD最容易应用在完备地功能上,而对于通常的C编程来讲,常常缺失完备的功能单元,模块的边界不是很清楚,并且语言本身的结构对此的帮助很有限。
在面向对象语言中,函数围绕着通用的数据而聚集在一起,数据由函数定义的接口来访问。语言本身直接支持完备单元。完备单元的可测性更好。如果我们的测试做到了FIRST,那么它将引导我们产出模块化的设计,这样的设计支持当前的测试。
从某种意义上讲,TDD就像从湍急的水中踩着突出的势头穿过一条山中小溪。这条路径并不是完美的直线。每个测试都把代码一点点地引向完成的目标,TDD曲折的路线风险更小,因为代码总是能通过现有已经定义的测试。因软件的BUG而阻碍我们的风险更小些。

问题10:TDD是如何增量式前进
DTSTTCPW先仿冒再建造
DTSTTCPW:Do The Simplest Thing That Could Possibly Work(只做简单够用的东西)
一旦去仿冒他比作出他还要麻烦的时候就做出它。

保持小而专注的测试
刚刚做TDD的程序员往往在每个测试中放入太多东西,这会破坏可读性和关注点。我们要保持测试小而专注。四阶段的每一步(建立,执行,验证,拆除)应该在每个测试用例中都清晰可见。当测试变得很大或者很不清晰时,他们作为文档的价值就不存在了。

绿了以后就重构
TDD中另一个不可分割的部分是重构。唯一可以安全地进行重构的时刻是当所有的测试都通过时。
测试即文档,应当小心地命名他们,一旦测试通过后,请确保名字能表达测试的意图。

问题11:测试驱动开发者的状态机
首先你要决定下一个增量开发的行为是什么并用测试表达你所期望的产出。然后你要满足编译的要求,也就是设计借口并让头文件和测试之间达成一致。在接口和测试之间达成一致后,预期会得到一个链接错误,之后就要增加一个骨架实现,但并不期望它是正确的实现。测试失败是一个好的信号,说明你的测试可以发现代码中的错误。一旦测试通过,你就知道你已经拥有了期望的行为了,但你的工作还没有完成,还要进行重构。为什么要做这些小步骤,因为它们让我们每次都只关注解决一个问题。

你可能感兴趣的:(测试驱动开发系列之一--嵌入式TDD基础知识)