问题 TDD可以留下哪些失败模式?


请注意我尚未在TDD上看到“光明”,也不是真的 得到 为什么它拥有主要支持者宣传的所有好处。我并没有解雇它 - 我只是保留了可能由于无知而产生的保留。所以无论如何都要嘲笑下面的问题,只要你能纠正我:-)

使用TDD可以让您自己接受实施的意外副作用吗? “满足测试的最少量代码”的概念建议用最狭隘的术语思考特定问题,而不必考虑更大的图景。

我正在考虑持有或依赖于状态的对象(例如内部字段值)。如果您有单独实例化对象的测试,初始化该对象然后调用测试中的方法,您会如何发现 不同 方法留下了一个无效状态,会对第一种方法的行为产生不利影响?如果我已经正确理解了问题,那么你就不应该依赖于测试执行的顺序。

我能想象的其他失败包括非关闭流,不处理GDI +对象等。

这甚至是TDD的问题领域,还是整合和系统测试应该抓住这些问题?

在期待中感谢....


8044
2017-08-31 13:44


起源

谢谢大家的回答,评论和参与。非常感激。 - Neil Moss


答案:


其中一些属于TDD领域。

Dan North说没有测试驱动开发这样的东西;我们真正做的是示例驱动的开发,只有在被测系统实现后,这些示例才会成为回归测试。

这意味着在设计一段代码时,您需要考虑示例场景并为每个案例设置测试。这些案例应该包括数据无效的可能性,而不考虑 为什么 数据可能无效。

在练习TDD时,可以并且绝对应该涵盖关闭流的内容。

我们使用类似函数的结构不仅可以减少重复,还可以封装功能。我们通过保持封装来减少副作用。我认为我们从设计角度考虑更大的图景,但是当涉及到实现方法时,我们应该能够将重点缩小到该范围 - 功能单元。当我们开始玩弄外部性时,我们可能会引入缺陷。

无论如何,这是我的看法;其他人可能会有不同看法。


7
2017-08-31 14:12



您如何编写测试来检测方法是否已打开流?例如一个测试,解密文件并返回明文内容,但恰好在文件上留下了一个独占锁,直到解密流被垃圾收集?我认为测试测试接口,而不是内部实现。我在这里误解了什么? - Neil Moss
@Neil隔离测试单元的一部分是控制反转。实现依赖注入,从加密文件获取内容的类将在其构造函数中接受流。通过抽象而不是具体的实现,比如 MemoryStream 要么 FileStream 等等,您(1)使代码可重复使用,(2)允许您自己在测试中传递您选择的流。在测试中运行单元后,您可以验证流已关闭。最好,您可以使用流的模拟实现,您可以在其上简单地验证 Close() 被称为。 - Jay
我不会写一个通用的解密(流)函数,它关闭()给它的流。鉴于要求“获取指定文件的纯文本内容”,我将编写一个方法:string GetPlaintextFromFile(string filename){...}该方法将打开FileStream,将其传递给您在上面描述的类然后(理想情况下)关闭FileStream。是否考虑了这样的功能 单元-testable?如果没有,TDD如何让我写这样的方法?我将如何以TDD方式验证流是否已关闭? - Neil Moss
@Neil TDD真正倾向于(并帮助强制执行)单一责任原则。如上所述,该方法不易于进行测试,因为它实例化了一个 FileStream。如果您可以将实例化提取到工厂或容器,那么您可以减少方法的职责并验证其相对于流的行为。 - Jay
“示例驱动开发”的+1 - Johnsyweb


TDD不是智能的替代品。 使用TDD,最好的程序员会变得更好。最糟糕的程序员仍然很糟糕。

您提出这些问题的事实是一个好兆头:这意味着您认真对待编程。

“最少量的概念”   满足测试的代码“建议   用最狭隘的语言思考   一个特殊的问题没有   必须考虑更大   图片。

采取这种态度很容易,就像“我不需要对此进行测试;我确信它只是有效”。两者都很天真。

这实际上是关于采取小步骤,而不是提前退出。你仍然会追求一个伟大的最终结果,但一路上你要小心 辩解 和 校验 你编写的每一段代码都带有测试。

TDD的近期目标非常狭窄:“我怎么能确定我正在编写的代码是否符合我的意图?“如果你有其他问题需要回答(例如,”这会在加纳好转吗?“并且”我的程序足够快吗?“)那么你需要不同的方法来回答它们。

我正在考虑持有或的物体   依赖于国家。

你怎么看到那个? 不同   方法留下了无效   州?

依赖性和状态很麻烦。它们会在最糟糕的时候出现微妙的错误。他们使重构和未来的改进更加困难。他们使单元测试变得不可行。

幸运的是,TDD非常善于帮助您生成将逻辑与依赖关系和状态隔离开来的代码。这是“TDD”中的第二个“D”。


3
2017-08-31 15:01



TDD如何帮助我确保状态一致?最终产品是一个将逻辑应用于状态的程序,如果状态没有得到准确维护,我会有一套测试表明一切都很好,但程序已经破损。有中间地带吗? - Neil Moss
TDD并没有真正做到这一点。它的范围是单位,理想情况下是一个简单的类。它并没有说“一切都很好”,它说“你编写的每一行代码都符合你的意图。”验收测试是组件和整个程序验证的常用方法。当然,你必须使用你的程序。 - Jay Bazuzi


“最少量的概念”   满足测试的代码“建议   用最狭隘的语言思考   一个特殊的问题没有   必须考虑更大   图片。

它表明,但这不是它的意思。这意味着强大的眼罩 暂时的。更大的图景在那里,但干扰了当前的紧迫任务 - 因此完全专注于当前的任务,并且 然后 担心接下来会发生什么。大局出现,在TDD中占了一席之地,但我们在红色阶段暂停对它的关注。只要测试失败,我们的工作就是让测试通过。一旦它和所有其他测试正在通过,那么就应该考虑全局,观察缺点,预测新的故障模式,新的输入 - 并编写测试来表达它们。这让我们重新回到了红色,重新缩小了我们的注意力。让新测试通过,然后将眼罩放在一边,以便下一步前进。

是的,TDD给了我们一些眼罩。但它并没有使我们失明。


2
2017-08-31 16:09



我再没有 得到 那。我看不到哪里 设计 进入那个循环。为什么不在第一次编写代码时屏住呼吸并考虑这些可能性?在决定“足够好”之前,你还要花多少时间来重构(即再做一遍)? - Neil Moss
@Neil,因为我们做大事,比我们的头脑更容易立刻握住。重构 不 “再做一次”。做一件事,做得对,可验证。现在添加它,而不会破坏它。对于我们中的一些人来说,这比尝试一次完成所有事情更容易。与我们按照您建议的方式工作时相比,我们犯的错误更少,错误也更少。 - Carl Manaster
@Neil Carl的最后评论是黄金。事实是,一开始只需要喝Kool-Aid并给它一个诚实的去。经过一些练习后,这些好处比我们能够清晰表达的更清楚。许多好处不是特定于TDD的,而是更普遍的只是进行一系列单元测试,但更多时候它被证明是“先测试或根本不测试”。 - Jay


好问题。 根据我的个人经验,这是我的两分钱:

可以使用TDD让自己开放   你的意想不到的副作用   实施?

是的,它确实。 TDD不是一个“完全成熟”的选择。它应该与其他技术一起使用,你应该牢记大局(无论你是否对此负责)。

我正在考虑持有或的物体   取决于国家(例如内部领域   值)。如果你有测试   单独实例化一个对象,   初始化该对象,然后调用   在测试的方法,你会怎么样   发现另一种方法已经离开了   在一个无效的国家背后   对行为产生不利影响   第一种方法?如果我明白了   事情是正确的,那么你不应该   依赖于测试执行的顺序。

每个测试方法都应该执行,不考虑之前执行的内容,或者之后执行的内容。如果情况并非如此,则出现问题(从TDD的角度来看)。

谈到你的例子,当你写一个测试时,你应该知道你的输入将是什么以及预期的输出是什么。您从已定义状态的已定义输入开始,然后检查所需的输出。你没有100%保证在另一个状态下使用相同的方法可以毫无错误地完成它的工作。但是,“意外”应该减少到最低限度。

如果你设计了这个类,你肯定应该知道两个方法是否可以改变一些共享的内部状态和方式;更重要的是,如果这样的话 应该 真的发生了,或者如果存在低凝聚力的问题。

无论如何,“tdd”级别的好设计并不一定意味着你的软件很好,你需要更多,因为Bob叔叔在这里解释得很好:

http://blog.objectmentor.com/articles/2007/10/17/tdd-with-acceptance-tests-and-unit-tests

Martin Fowler写了一篇关于Mocks vs Stubs测试的有趣文章,其中涵盖了您正在讨论的一些主题:

http://martinfowler.com/articles/mocksArentStubs.html#ClassicalAndMockistTesting


1
2017-08-31 14:42