`
windflawlyq
  • 浏览: 4094 次
  • 性别: Icon_minigender_1
  • 来自: 厦门
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论

实践TDD的点滴——寻找可测的后置条件

阅读更多
    刚开始进行TDD的人,一开始着手写测试时,经常不知从何入手。《测试驱动开发》里有提到断言优先,也就是可以先写assert那句话。但要测什么呢,其实就是在测试后函数后置条件,也就是执行完函数后会产生什么结果。
   
    最简单的检测就类似add()这种计算类函数,通过返回值就可以判断了。但实际开发中,我们往往遇到更多的是没法通过返回值来检测后置条件的函数。寻找可测的后置条件成为了问题的焦点。但后置条件在哪里呢,还真不容易判断,同一个情况不同的人可能会找出不同的后置条件。

    例如:游戏世界需要将玩家周围发生的事件发送给该玩家客户端。这里用观察者模式实现,也就是客户端A预先向游戏世界订阅玩家A周围的事件,之后每当玩家A周围的其他玩家发生行为变化时,都会通知给客户端A。简化成函数就是:
class GameWorld
{
public:
    void subscribeSync(int playerId, GameWorld::Listener* listener)
    {
    }
};


    上面这个例子后置条件是什么?你可能会立即说需求里不是明摆写着,后置条件就是每次玩家A周围的其他玩家发生行为变化时,订阅者就会收到该事件。如果把这作为后置条件,我们来看看要做准备哪些东西,要为游戏世界添加玩家A和一个玩家B,玩家B在玩家A的身边,然后让玩家B做一个行为,再检测客户端A是否收到了这个事件。这里面又涉及东西就多了。要把玩家添加到游戏世界中;世界在刷新时才同步信息给客户端,你要去完成刷新函数中的同步代码;玩家B在玩家A身边怎么判断的,还需要计算一定距离内的玩家算为在玩家A身边。等等,老兄,我们现在才在写订阅,怎么一下子要去实现那么多功能。

    那怎么办呢?要不就创造一个简单可以让外面检测的后置条件吧。由world提供一支函数getSubscribedListeners返回所有订阅者,通过判断订阅者列表里是否存在之前添加进去的那个listener来测试。
class GameWorld
{
public:
    list<SubscribeListener> getSubscribedListeners()
    {
    }
};

或着更直接点,让world提供一支hasListener(),判断某个listener是否存在,来进行测试。
class GameWorld
{
public:
    virtual bool hasListener(GameWorld::Listener* listener)
    {
    }
};


    这样做不就简单解决了?的确这样做单从添加订阅者的角度OK了,但依然存在一些不妥的地方。
   
    首先需求里并不需要world提供getSubscribedListeners或hasListener,这种函数大多情况下最终只会在测试中调用到。由于要测试的逻辑很多,你很快会发现你写了一堆类似的检测函数,而且都要做成public,这样class的阅读者就不容易清晰的看出哪些是class的核心函数了,从可维护性的角度良好的设计一个评判的标准不就是让代码看起来更简洁吗。

    另外,如果为了定位快速,保存listener由list变成是map,那么你就需要改变getSubscribedListeners了。也就是world类内部的实现细节影响到了要修改测试代码。换句话说就是测试不只是依赖于类的外部功能,还依赖于类的内部实现。如果测试依赖于内部实现,你就会发现重构起来,很多的测试都需要修改甚至重写。也许我举的例子不足于让你感觉到麻烦,因为这个例子太简单了。但我确实在项目中遇到了很多重构时重写测试的麻烦,特别是测试代码往往写得很长,一堆的mock类,看起来很费神。有时候干脆就把整个测试去掉。

    这里其实涉及到一个问题,就是要从需求的角度去写测试,还是从实现的角度去写测试。前者往往没有明确的、直接可测的后置条件,测试代码不容易写。后者会导致测试依赖于内部实现造成测试代码不稳定经常要修改。我时常纠结于这个问题,不知道大家是否也有此困惑,一共来探讨吧!
分享到:
评论
8 楼 piao_bo_yi 2010-12-31  
windflawlyq 写道
mock1234 写道
TDD测试不是传统单元测试(虽然工具一样,但是思想是反叛的)。

传统的单元测试,其实完全可以用程序代码中的断言来实现,而没有必要使用单元测试工具(单元测试工具只是运行一些模拟使用功能的用例好让那些断言可以跑起来)。

而TDD是设计。你写的每一行代码,回想一下是先写手工文档然后直接代码还是用TDD代码驱动出来的,这才是TDD要的结果。而将传统的单元测试过多地移植到TDD中,可能移植了过多目的不明确的东西。


同意你的观点,但TDD总是建立在一个个的单元测试基础上的,既然是测试,免不了要考虑测什么,怎么测

如何单元测试是难点。
7 楼 windflawlyq 2010-04-19  
mock1234 写道
TDD测试不是传统单元测试(虽然工具一样,但是思想是反叛的)。

传统的单元测试,其实完全可以用程序代码中的断言来实现,而没有必要使用单元测试工具(单元测试工具只是运行一些模拟使用功能的用例好让那些断言可以跑起来)。

而TDD是设计。你写的每一行代码,回想一下是先写手工文档然后直接代码还是用TDD代码驱动出来的,这才是TDD要的结果。而将传统的单元测试过多地移植到TDD中,可能移植了过多目的不明确的东西。


同意你的观点,但TDD总是建立在一个个的单元测试基础上的,既然是测试,免不了要考虑测什么,怎么测
6 楼 windflawlyq 2010-04-19  
强强爱妍妍 写道
这个场景我会做一个桩玩家. 该桩可以知道发送给玩家A的事件. 用这个桩替换玩家A.
操作玩家B发生一个动作, 然后检测桩是否收到事件.


是呀,如果是这样的写法,比较扣住需求,不依赖于实现细节。但问题是,我现在只是写订阅,至于同步于玩家B的事件给A,并还没开始开发呀,同步的逻辑更加的复杂。TDD不是一直要我们把想到的事做放入TODO,然后继续朝着我们的目标前进吗?因此没法通过后续的真正后置条件——同步玩家周围的事件来测,我只能以是否添加成功来测试。

强强爱妍妍 写道
我觉得测试代码写的白盒一点,易于重构代码. 即通过public的接口来assert. 这样重构实现的时候不影响测试代码, 测试代码才能够成为一张自动保护你的代码行为的网.

“黑盒一点”是否是笔误?应该黑盒一点在重构时才不影响代码吧
5 楼 强强爱妍妍 2010-04-14  
"游戏世界需要将玩家周围发生的事件发送给该玩家客户端。这里用观察者模式实现,也就是客户端A预先向游戏世界订阅玩家A周围的事件,之后每当玩家A周围的其他玩家发生行为变化时,都会通知给客户端A。"

这个场景我会做一个桩玩家. 该桩可以知道发送给玩家A的事件. 用这个桩替换玩家A.
操作玩家B发生一个动作, 然后检测桩是否收到事件.

我觉得测试代码写的白盒(注:误. 应为黑盒)一点,易于重构代码. 即通过public的接口来assert. 这样重构实现的时候不影响测试代码, 测试代码才能够成为一张自动保护你的代码行为的网.
4 楼 tuti 2010-04-14  
bingo1018 写道
我这两天正在搞单元测试的东东,想整理一下哪些方法是可测试哪些是不需要测试的,可以方便测试代码的编写,同时起到编程规范的作用。目前我使用Jtest工具,一直在网上找不到专业一点的理论知识,要自己总结太郁闷了。


这是个风险驱动的东西。
简单的说,你觉得很放心的地方可以不测,你觉得总有点心神不宁那就要测。
3 楼 daquan198163 2010-04-13  
这个确实纠结,很难两全,
但是我宁愿选择去写啰嗦的黑盒单元测试,可以带来更多的安全感,而且测试本身不易碎。
2 楼 bingo1018 2010-04-13  
我这两天正在搞单元测试的东东,想整理一下哪些方法是可测试哪些是不需要测试的,可以方便测试代码的编写,同时起到编程规范的作用。目前我使用Jtest工具,一直在网上找不到专业一点的理论知识,要自己总结太郁闷了。
1 楼 windflawlyq 2010-02-28  
    感觉论坛里都在讨论TDD的概念,很少去讨论具体应用中遇到的问题,而且最近关于TDD的话题都很少了。我觉得TDD之所以用的人少,就是更多人只是观望,很少真正用到项目。或着稍涉足,遇到一堆的具体问题,不知如何是好,最后放弃。为什么大家不从一些具体实践遇到的细节问题展开探讨,逐步扫除实施上的障碍,最后才去领悟TDD的价值。

相关推荐

    单元测试与TDD实践

    单元测试与TDD实践 **一、单元测试之测试目的** 单元测试,作为软件开发过程中的重要环节,其核心目标在于确保代码的质量、可维护性和可扩展性。它通过独立测试软件中的最小可测试单元,如函数或方法,来验证其...

    [测试驱动开发的三项修炼——走出TDD丛林]

    在《测试驱动开发的三项修炼——走出TDD丛林》中,作者深入探讨了如何有效地实践TDD,以提高软件质量和开发效率。以下是关于TDD的三个关键修炼: 1. **理解测试金字塔**:测试金字塔是一个指导原则,它建议我们构建...

    工程与技术实践-TDD中常见的10大反模式

    ### 工程与技术实践-TDD中常见的10大反模式 #### 1. 引言 测试驱动开发(Test-Driven Development, TDD)是一种软件开发方法论,它要求在编写任何生产代码之前,先编写相关的测试用例。TDD的核心原则包括“先测试...

    敏捷建模:极限编程和统一过程的有效实践———— 好东西!

    《敏捷建模:极限编程和统一过程的有效实践》是一本深入探讨敏捷开发方法的书籍,主要聚焦于极限编程(XP)和统一过程(RUP)这两种广泛应用的敏捷框架。在这个快速变化的IT行业中,敏捷方法论已经成为软件开发的...

    [测试驱动开发的三项修炼——走出TDD丛林].rar

    在《测试驱动开发的三项修炼——走出TDD丛林》中,作者深入探讨了TDD的核心理念、实践技巧以及常见误区,帮助开发者更好地理解和应用TDD。 一、TDD的基本原则 1. **红灯原则**:首先编写一个不能通过的测试用例...

    TDD测试驱动开发

    - **提高代码质量**:TDD迫使开发者思考边界条件和异常情况,有助于发现和修复潜在的bug。 - **设计上的指导**:测试用例作为需求的表达,有助于形成清晰的接口和职责划分。 - **减少回归错误**:频繁的自动化...

    TDD_In_Practice ThoughtWorks

    #### TDD——从故事开始:构建软件开发的新范式 测试驱动开发(Test-Driven Development,简称TDD),是一种敏捷软件开发方法,它强调在编写实际代码之前先编写测试用例。这种方法的核心理念在于“先写测试”,即在...

    Test Driven: Practical TDD and Acceptance TDD for Java Developers (PDF英文版)

    《Test Driven: Practical TDD and Acceptance TDD for Java Developers》是一本专注于Java开发者进行测试驱动开发(TDD)和验收测试驱动开发(Acceptance TDD)的专业书籍。这本书以PDF英文版的形式提供,旨在帮助...

    简单之美——软件开发实践者的思考

    《简单之美——软件开发实践者的思考》一书,深入剖析了这一艺术与哲学的内涵,向我们展示了如何在纷繁复杂的IT世界中,寻找到简洁高效的软件开发之道。本书不仅为初入行业的新人提供了实用的指南,也为经验丰富的...

    Ruby-TDD实战TestDrivenDevelopmentinAction

    在软件开发领域,Test-Driven Development(TDD)是一种编程实践,它强调先编写测试用例,然后编写满足这些测试的最小功能代码。Ruby作为一种动态、灵活的编程语言,是TDD的理想选择,因为它允许快速迭代和高效的...

    UMTS-TDD手册

    本手册还提供了丰富的案例分析和实践指导,旨在帮助读者更好地理解和掌握UMTS-TDD仿真技术。例如,通过具体的场景设置,展示了如何在NS2环境中配置不同的网络参数、设置仿真场景以及分析仿真结果。此外,还包括了...

    TDD in PHP

    测试驱动开发(TDD,Test-Driven Development)是一种软件开发实践,强调先编写测试用例,然后根据测试用例来编写实际的代码。TDD的基本流程包括:先思考要实现的功能,设计测试用例,编写测试代码,运行测试并观察...

    嵌入式 TDD

    8. **最佳实践**:最后,本书总结了一系列TDD的最佳实践,帮助开发者避免常见错误,提升代码质量和开发效率。 #### 结语 通过《嵌入式TDD》,读者不仅能够深入了解TDD的核心原理,还能学习到如何将其有效地应用于...

    测试驱动开发的3项修炼:走出TDD丛林

    测试驱动开发(TDD)是一种敏捷...王晓毅的《测试驱动开发的3项修炼:走出TDD丛林》为读者提供了在实际开发中运用TDD的深入见解和实用技巧,帮助开发者在敏捷开发的道路上更好地实践TDD,提高软件开发的效率和质量。

    phpunit-TDD驱动开发

    ### 使用PHPUnit进行TDD驱动开发 #### 一、引言 测试驱动开发(TDD, Test-Driven Development)是一种软件开发方法论,它要求在编写实际功能代码之前先编写测试用例。通过这种方式,可以确保代码的质量,并且有助...

    TDD 测试驱动开发 文档 详细

    测试驱动开发(Test-Driven Development, 简称TDD)是一种软件开发实践,强调在编写实际代码之前先编写测试用例。这种方法的核心理念是通过编写能够失败的测试来定义需求,然后编写足够的代码使测试通过,最后重构...

    RIP TDD原文搬运

    失去TDD后,需要寻找新的方法来保持开发的焦点。 2. **API反馈(API feedback)**:TDD中的测试可以迅速验证API设计的有效性。如果没有TDD,需要找到另一种方式快速获取关于API决策的反馈。 3. **逻辑错误(Logic ...

    matlab论文《近场波束形成的ttd配置——并行、串行或混合》的代码.zip

    在本压缩包中,我们关注的是一个与MATLAB相关的研究论文,标题为“近场波束形成的TDD配置——并行、串行或混合”。这个主题涵盖了无线通信领域中的一个重要技术,即近场波束形成,它利用时间延迟器(Time-Delay ...

Global site tag (gtag.js) - Google Analytics