刚开始进行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类,看起来很费神。有时候干脆就把整个测试去掉。
这里其实涉及到一个问题,就是要从需求的角度去写测试,还是从实现的角度去写测试。前者往往没有明确的、直接可测的后置条件,测试代码不容易写。后者会导致测试依赖于内部实现造成测试代码不稳定经常要修改。我时常纠结于这个问题,不知道大家是否也有此困惑,一共来探讨吧!
分享到:
相关推荐
单元测试与TDD实践 **一、单元测试之测试目的** 单元测试,作为软件开发过程中的重要环节,其核心目标在于确保代码的质量、可维护性和可扩展性。它通过独立测试软件中的最小可测试单元,如函数或方法,来验证其...
在《测试驱动开发的三项修炼——走出TDD丛林》中,作者深入探讨了如何有效地实践TDD,以提高软件质量和开发效率。以下是关于TDD的三个关键修炼: 1. **理解测试金字塔**:测试金字塔是一个指导原则,它建议我们构建...
### 工程与技术实践-TDD中常见的10大反模式 #### 1. 引言 测试驱动开发(Test-Driven Development, TDD)是一种软件开发方法论,它要求在编写任何生产代码之前,先编写相关的测试用例。TDD的核心原则包括“先测试...
《敏捷建模:极限编程和统一过程的有效实践》是一本深入探讨敏捷开发方法的书籍,主要聚焦于极限编程(XP)和统一过程(RUP)这两种广泛应用的敏捷框架。在这个快速变化的IT行业中,敏捷方法论已经成为软件开发的...
在《测试驱动开发的三项修炼——走出TDD丛林》中,作者深入探讨了TDD的核心理念、实践技巧以及常见误区,帮助开发者更好地理解和应用TDD。 一、TDD的基本原则 1. **红灯原则**:首先编写一个不能通过的测试用例...
- **提高代码质量**:TDD迫使开发者思考边界条件和异常情况,有助于发现和修复潜在的bug。 - **设计上的指导**:测试用例作为需求的表达,有助于形成清晰的接口和职责划分。 - **减少回归错误**:频繁的自动化...
#### TDD——从故事开始:构建软件开发的新范式 测试驱动开发(Test-Driven Development,简称TDD),是一种敏捷软件开发方法,它强调在编写实际代码之前先编写测试用例。这种方法的核心理念在于“先写测试”,即在...
《Test Driven: Practical TDD and Acceptance TDD for Java Developers》是一本专注于Java开发者进行测试驱动开发(TDD)和验收测试驱动开发(Acceptance TDD)的专业书籍。这本书以PDF英文版的形式提供,旨在帮助...
《简单之美——软件开发实践者的思考》一书,深入剖析了这一艺术与哲学的内涵,向我们展示了如何在纷繁复杂的IT世界中,寻找到简洁高效的软件开发之道。本书不仅为初入行业的新人提供了实用的指南,也为经验丰富的...
在软件开发领域,Test-Driven Development(TDD)是一种编程实践,它强调先编写测试用例,然后编写满足这些测试的最小功能代码。Ruby作为一种动态、灵活的编程语言,是TDD的理想选择,因为它允许快速迭代和高效的...
本手册还提供了丰富的案例分析和实践指导,旨在帮助读者更好地理解和掌握UMTS-TDD仿真技术。例如,通过具体的场景设置,展示了如何在NS2环境中配置不同的网络参数、设置仿真场景以及分析仿真结果。此外,还包括了...
测试驱动开发(TDD,Test-Driven Development)是一种软件开发实践,强调先编写测试用例,然后根据测试用例来编写实际的代码。TDD的基本流程包括:先思考要实现的功能,设计测试用例,编写测试代码,运行测试并观察...
8. **最佳实践**:最后,本书总结了一系列TDD的最佳实践,帮助开发者避免常见错误,提升代码质量和开发效率。 #### 结语 通过《嵌入式TDD》,读者不仅能够深入了解TDD的核心原理,还能学习到如何将其有效地应用于...
测试驱动开发(TDD)是一种敏捷...王晓毅的《测试驱动开发的3项修炼:走出TDD丛林》为读者提供了在实际开发中运用TDD的深入见解和实用技巧,帮助开发者在敏捷开发的道路上更好地实践TDD,提高软件开发的效率和质量。
### 使用PHPUnit进行TDD驱动开发 #### 一、引言 测试驱动开发(TDD, Test-Driven Development)是一种软件开发方法论,它要求在编写实际功能代码之前先编写测试用例。通过这种方式,可以确保代码的质量,并且有助...
测试驱动开发(Test-Driven Development, 简称TDD)是一种软件开发实践,强调在编写实际代码之前先编写测试用例。这种方法的核心理念是通过编写能够失败的测试来定义需求,然后编写足够的代码使测试通过,最后重构...
失去TDD后,需要寻找新的方法来保持开发的焦点。 2. **API反馈(API feedback)**:TDD中的测试可以迅速验证API设计的有效性。如果没有TDD,需要找到另一种方式快速获取关于API决策的反馈。 3. **逻辑错误(Logic ...
在本压缩包中,我们关注的是一个与MATLAB相关的研究论文,标题为“近场波束形成的TDD配置——并行、串行或混合”。这个主题涵盖了无线通信领域中的一个重要技术,即近场波束形成,它利用时间延迟器(Time-Delay ...