测试驱动开发总结

Malak ·
更新时间:2024-09-21
· 876 次阅读

   1、何为测试驱动开发        简而言之,测试驱动开发是:        先定义代码的行为,再定义行为的实现        2、为什么要进行测试驱动开发        2.1 需求与设计驱动        代码应该具有什么样的行为由什么决定:        由客户的需求决定(这里的客户可以是使用产品的终客户,更多的情况下是其他的程序员)        用什么反映需求,用什么来验证需求是否被充分满足:        测试用例        先写测试用例说白了是先用可验证的方式表达出客户的需求,表达出“通过这样的验证,代码的实现是能充分满足客户需求的”,从而提升开发者的勇气,让开发者明确目标,而且目标是可以达到的,因为目标已经通过测试用例表达出来了,是一个完全看得见摸得着的东西。        需求可不可能有问题?        当然可能有问题,如果你发现在编码之前测试用例无法写,而你所考虑到的一切设计手段都用过了,也许问题出在需求那里,比如一个实现起来很困难的需求其实是可以通过另一个容易实现的需求来满足的,因此这个需求也许并不必要。测试驱动方法是一个在早期发现需求缺陷的手段。        设计可不可能有问题?        更可能有问题,测试用例无法写, 但需求是合理而且必须的情况下,也许需要检验一下你的设计是否合理,是否需要考虑用更简单的设计、更加松耦合紧内聚的设计来达到需求目标。一个紧耦合松内聚的设计,要对之进行测试无疑是很困难的。测试驱动方法是一个在早期发现设计缺陷的手段,是让开发者在编码前仔细推敲自己的设计是否足够、是否合理,从而减少编码已经大半才发现重大设计缺陷的几率。        测试用例是什么:        代码将长成的样子,通过这些用例,我们通过客户的视角来审视设计、实现是否合理。        需求决定一切,        在需求都没有考虑清楚的情况下开始编码是不明智的。        2.2 后期限(DeadLine)终结者        因为已经到了后期限,所以没空作开发者测试了吗?        测试驱动开发的实践结果是:        测试驱动加快了软件开发的速度,因为它大大缩减了调试的时间        主要的开发时间不是耗费在编码上,而是debug。如果你经常运行测试,bug可以更早的显露出来。如果只是距离你后一次写测试代码不久,你能知道是代码的哪一部分有问题。        2.3 重构的安全保证        重构是一项日常工作,而不是阶段性工作,不要等东西坏到无法再修只能丢掉,而是一旦发现损坏马上维修。 如何保证重构不引入新的问题:        一个完备的、可自动化运行的用例库无疑是重构好的安全保证。        2.4 明确编码的目标        关键在于 “用户怎么来用这个新特性”        先写测试代码的另一个好处在于测试帮助你集中在新特性的接口上而不是其实现。你会问自己“用户怎么来用 个新特性?” 让测试跑起来是给这个特性作了结论,你也清楚了自己将要作的是什么。        3、如何进行测试驱动开发        下面结合我在端口管理模块的开发工作中实践测试驱动开发的经验谈谈几点关于“如何进行测试驱动开发”的看法。        3.1 测试驱动开发,作的是单元测试        单元测试和集成测试、系统测试不同,单元测试是直接验证开发者代码实现内部逻辑性、外部需求特性是否正常的测试方法。这些是在编码之前一个开发者需要考虑的事情,也是“开发前测试”需要考虑的事情。单元测试应该脱离一个正常运作的系统、一个已经集成好了的功能子系统来进行。不要以集成测试、系统测试的角度来看待单元测试。

    单元测试关心的是:        (1)单元外部需求的满足性。注重契约式编程中外部接口的前后契约条件检验,如前置契约不满足,外部接口应该返回失败,前置契约满足,则外部接口的输出、返回、对代码上下文语义环境的结果影响应该满足后置契约的要求。        (2)单元内部实现应满足的设计语义假定(包括值域的、代码逻辑的),这些假定在编码时通过断言来表达(ASSERT),测试时可以直接作为检测手段。        3.2 根据需求与设计方案设计用例        设计用例时需要考虑的无非也是如下两点:        (1)需求,这个是外部特性,在用例设计上必须以需求作为检查目标。这是首要的。        (2)设计,这个是内部特性,在用例设计上应该对设计完备性(已经能直接指导编码)、以及设计的语义假设进行检查,如果设计不够完备,那么当然无从检验编码该达到什么目标,因为连蓝图都没有,无从校对起。语义假设是什么? 语义假设,是设计的内部逻辑特性。比如顺序内聚性是一种语义假设(在作A之前,必须先满足条件B,那么你需要在调用A时检测条件B是否满足)。语义假设检查主要通过ASSERT进行,下面会谈到。设计太粗则不够完备,无法针对设计方案进行细致的用例设计,必然导致测试得太粗。        如果觉得用例写不下去,那么不要硬写,而是需要好好琢磨一下以上两点是否合理。在编码之前先写用例意味着:此时还没有“实现代码”,不存在实现的合理性问题,如果有不合理,则只会是需求或者设计上的不合理,测试驱动的一大好处也是能在开发的前期发现这两个起着决定性作用的研发环节的缺陷。        3.3 自动化测试        CUnit测试框架是一个比较好的自动化运行测试用例的手段。它可以用一个明显的方式规范化的向你表达:你的测试用例是都pass了,还是有的fail了。        如果你的项目还没有集成CUnit测试框架了的VC版本,请尽快集成。        必须做到能够自动化运行你的测试用例,不要干需要人工核对才能确认正确与否的傻事。        每一次对代码进行了修改,都要运行已有的所有测试用例确保无误。        3.4 将单元测试对实现代码的影响减低到少        必须将单元测试对实现代码的影响减低到少,在实现代码中直接嵌入测试代码不是一个好的方法。好的情况是:测试代码、测试用例与实现代码完全分离,完全可拆分,在测试用例容易规范化的情况下尽量用规范化的表达方式(比如用xml格式的纯文本记录测试用例)。在这方面可以动很多脑筋,下面我主要提两点:。        (1)打桩布局。进行单元测试必不可少的是对本单元引用的外部接口进行打桩。如果系统比较简单,所开发的模块与其他模块关联性不强,如果很容易提取出来自编译、自运行。那么按标准的单元测试打桩方法即可。        如果系统比较复杂,所开发的模块与其他模块关联性很强,不容易脱离集成环境进行单元测试,可以参考我写的《集成系统中进行单元测试的静态打桩布局方式》一文,通过这种布局方法可以减少单元测试对实现代码的干扰。        (2)活用ASSERT,ASSERT检查 么? 上面提到了,ASSERT检查的是设计上的语义假定。我们经常发现的“控制逻辑问题”是语义假定被违背了。ASSERT的触发意味着测试fail。ASSERT是位于实现代码之中的很好的测试手段。        3.5 测试与开发同步进行        测试工作与开发工作应该是并发进行。每次都只作一小步的工作,模块详细设计之后的开发工作是这样的:测试用例设计->编码->单元测试 的 迭代式增量开发。        测试用例库的积累是聚沙成塔式的,不要编完1K甚至1W行代码再来测试,这么多行代码里藏着多少bug可能数都数不清,如果等到代码都已经写了这么多了才开始测试,设计测试用例、调试debug的困难与耗时良久是可想而知(这可能也是为什么大家觉得“单元测试困难”,不知道该怎么操作的主要原因)。应该一次多写100行编码,写这100行代码的测试用例,立刻验证这些代码能否通过测试,不行则必须马上解决,解决完了才进行下一轮迭代。当问题发生在刚刚编码的100行代码中时,要定位它是非常容易的。        每一次新增代码,执行测试时,应该是执行目前积累下来的所有测试用例,这样可以判断,当前新增代码对既有代码逻辑是否有破坏性的影响。        3.6 如何判断测试用例已经足够完备了        用例设计覆盖程度应不仅达到必须的语句覆盖度,还需满足一定的分支覆盖度、路径覆盖度要求,我的经验是:应该达到任何一句代码判断逻辑“不小心写反了”(出现逻辑错误了)均能被用例自动测试出来。        3.7 试点        空谈无益,要体验一个新技术的好处,必须要亲身尝试。可以先找一些感兴趣的开发者来试验一下,一旦体会到测试驱动开发的好处,他们将会是这项技术好的宣传员与播种机。



测试驱动 测试驱动开发 测试

需要 登录 后方可回复, 如果你还没有账号请 注册新账号
相关文章