TDD,新测试,旧测试尚未实施


13

我正在试验测试驱动的开发,发现经常遇到以下情况:

  1. 我为某些功能X编写测试。这些测试失败。
  2. 在尝试实现X时,我看到我需要在代码的下层实现某些功能Y。所以...
  3. 我为Y编写测试。现在针对X和Y的测试均失败。

一旦我同时处理了不同代码层中的4个功能,我就失去了对实际工作的关注(太多的测试同时失败)。

我认为即使在开始编写测试之前,也可以通过更加努力地计划任务来解决此问题。但是在某些情况下,我不知道需要更深入地研究,因为例如,我不太了解底层API。

在这种情况下我该怎么办?TDD有什么建议吗?

Answers:


9

好消息是您意识到被测代码需要帮助。与其立即实施,不如创建一个接口并使用模拟来确保您的测试正确输入了代码。通过这些测试后,您可以继续执行其依赖的代码。


我的测试通常不了解方法应在内部执行的操作(调用哪种较低级别的API)。我是否应该只是调整测试以模拟测试代码中需要的内容?
liori 2011年

2
同样,您测试过的课程也不必关心“底层”的作用。使用模拟/存根代替实际的类/对象。这可能需要在设计上花费更多的精力,但会导致代码耦合性降低并且易于重用。
Mchl 2011年

1
您在使用依赖注入吗?这样您可以轻松地将较低级别的关注点与较高级别的类分开。您的被测类具有一个构造函数,该构造函数带有用于其依赖项的参数(作为接口),在测试中,您将为接口创建模拟。基本上,您是在假装已经实现了较低级别的服务。
Michael Brown

@迈克·布朗,是的,我愿意。我知道我可以创建模拟对象。但是在功能测试中,X我必须知道X需要模拟依赖的哪一部分。我觉得这是实现细节的一部分,不应该是测试的一部分,否则我可能需要在重构实现时更改测试。我应该为此担心吗?
liori 2011年

1
一点也不...测试应该反映被测系统的假设。它还可以帮助您从系统所依赖的服务中了解所需信息。我曾经在这个问题上与您达成一致,但是我将其与了解递归编程的方式进行了比较。首先,假设您具有执行所需功能的代码,然后编写代码。然后,编写所需的代码。
Michael Brown

4

存根和模拟可用于模拟尚未修改/实现的功能。它们还可以帮助您解决导致这种“链式反应”的依赖性。

另一方面,最好只进行一次(失败)测试以驱动下一个更改。

可以暂时禁用其他针对依赖新功能的代码的测试,因为它们在此时并没有真正的意义。在您的情况下,请禁用X的测试,直到实现Y等。

我认为这样一来,您就可以只专注于您想要的下一个更改。


哈,我正在寻找一种功能来在IDE内进行测试时关闭测试,但没有找到。现在我发现python unittest已经跳过了测试。这对我来说可能就足够了。
liori 2011年

我们使用Google测试C ++框架-它具有禁用测试的选项。禁用的测试不会执行,而是会编译-在您需要的时候-它们可以随时运行(此外,您可以“强制执行”禁用的测试-一种“运行时启用”)-出色的功能...
ratkok

3

停止

临时看来,这里可能存在两个独立的问题:

  1. 您忘记了一些故事和测试场景,直到开始处理特定的测试场景时才发现它们,和/或

  2. 您实际上是在进行单元测试,而不是TDD 功能测试

对于#1,请停止,返回并更新故事和测试方案,然后从其他方案重新开始。

对于#2,请停止,并记住您正在测试功能而不是单元,因此请使用模拟掩盖其他接口和/或实施更多代码以使测试通过而不添加新的测试方案。假定您没有丢失测试场景,而是(实际上很常见)混合单元测试和TDD。


我非常喜欢您的回答,它可以更好地解释实际情况。
maple_shaft

...话虽这么说,但我不认识世界上的一位总理,他不会完全对“停止,我们需要回溯”一词失去理智。他们会尽一切努力牺牲自己的长子在祭坛上,以保持项目的进展,技术债务和不完整的单元测试将受到谴责。我猜您不能责怪他们,因为他们对组织的唯一衡量标准是按时完成项目。一些组织只是重视时间而不是质量,这就是为什么我可能从未见过TDD在这些类型的组织中成功工作的原因,不幸的是,这是IMO中最重要的。
maple_shaft

@maple_shaft:您停止重新组合的时间可能只有几个小时-除非您的过程很遥远,很不正常,在这种情况下,停止几天以使其重回正轨将更有可能该项目将成功。在错误的轨道上全力以赴是没有意义的!
Steven A. Lowe

0

对于TDD和我来说,这是一个很大的问题,对我来说是巨大的挫败感。我感觉在这种情况下TDD缺乏,您无法在开始开发之前就不知道需要哪些较低级别的组件或功能。

我个人发现,TDD仅在您完全知道执行某项功能所需的内容以及调用该功能所需的内容后才能起作用。开发人员在开始之前并不总是了解所有内容,因此我发现减轻自己所描述情况的最佳方法是:

原型

当我制作简单的原型应用程序以探索和发现技术问题的方法时,我会发现很多相关工作,并在开始之前就将这些研究排除在外。设计和估算也变得容易得多。

如果原型必须涉及到它成为应用程序,那么我敦促您不要懒惰,而是在事后为原型构建单元测试。

届时,您应该对低级API有所了解,并能够在高级别组件中成功模拟低级API。


因此,您实际上建议通过以非正式的方式进行探索性编码来获取有关规划阶段的更多信息(=不用某种正式的方法论)。然后假设它将提供足够的信息来计划实际代码。我对吗?
liori 2011年

您为什么认为原型制作是一个非正式的过程?每个估算都应说明原型,项目进度表应说明它以及必要的开发任务。我将其视为与“设计”或“代码审查”相同。关于这一点,它已经正式化并应予以考虑,甚至在许多未知的任务上也应予以考虑。在没有原型和执行概念验证的能力的情况下,追求TDD只是假设开发人员对具有所有功能的一切一无所知。现实世界不是那样工作的,我不在乎你有多聪明或经验。
maple_shaft

通过“非正式的方式”,我并不是说不应考虑原型制作的时间,而是在您制作原型时,您不会遵循TDD或任何其他代码方法。
liori 2011年

TDD是用于单元测试和开发的方法。对代码审查进行TDD有意义吗?TDD对设计,编写技术规范或白板有意义吗?原型制作本身就是一项任务,是研究,概念证明和教育的探索性开发。
maple_shaft

1
TDD 对于原型制造来说非常有意义。它使您能够以可重复,可执行的一组需求的形式快速展示您的所有事物(对象,函数,API,整个程序)。帮个忙,阅读测试指导的成长型面向对象软件 ; 它需要您以测试优先的方式逐步构建整个应用程序(包括集成)。
Frank Shearar 2011年

0

这取决于您在进行TDD时进行何种测试。

经典模型是编写单元测试,并使用模拟或存根将测试与其他“单元”代码分离。

还有许多其他替代模型,例如ATDD,可以在其中测试全栈或几乎全栈测试。在这种特殊情况下,您的编写测试可以断言所需的程序行为,而不是单个代码单元,因此您不会编写其他测试。您将获得工具往返来满足测试。然后,您为其他功能/行为添加其他测试。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.