如何设计可测试性代码

时间:2008-11-25 06:15:04

标签: .net unit-testing testing tdd

我一直在寻找使用TDD并在我将来创建的任何项目中实施适当的测试(才开始了解它能让你的生活有多好)。因此,在过去的几天里,我一直在努力学习如何设计可测试性的应用程序,但我似乎仍然在努力解决一些想法。

我已经阅读了很多你应该编程对接口而不是。我遇到的主要问题是,你应该创建多少个接口?您是否应该为要测试的所有内容提供一个?或者我读错了吗?

另一件事是使用大量的依赖注入,因此你可以模拟你注入的部分而不是使用真实的东西。它是否正确?还是我离开这里了?

7 个答案:

答案 0 :(得分:7)

我认为你有正确的想法,但我认为你正在把它变成一个比它更大的交易。如果你开始做TDD,你的第一反应可能就是'这是吗?'。然后,你应该有希望说'啊哈'!

主要的是你获得nUnit,学习教程,然后确保在编写实现之前为你所做的一切编写测试。您可以跳过为属性访问器编写测试,但是任何需要进行任何计算的测试都应该先编写测试。

所以,假装你正在测试一个计算器的Add(int 1,int 2),你认为首先是'我怎么能打破这个'。事情可能出错的我的想法是:负数,零和溢出。诀窍是想象创建Add()方法的人可能犯的每个错误,然后针对它编写测试。所以,我可能会写:

Assert.AreEqual(5, calc.Add(2, 3), "Adding positives not as expected");
Assert.AreEqual(-5, calc.Add(-2, -3), "Adding negatives not as expected");
Assert.AreEqual(-2, calc.Add(-3, 2), "Adding one positive and one negative not as expected");

// your framework might provide a cleaner way of doing this:
try {
  int result = calc.Add(Int32.Max, 5);
  Assert.Fail("Expected overflow error. Received: " + result);
} catch(Exception e) {
  // This should be a more specific error that I'm not looking up
}

因此,正如您所看到的,我尝试做的是弄清楚Add()方法可能不起作用,然后对其进行测试。我也寻找有趣的角落案例并明确定义了我期待的行为。然后现在我可以自由地编写Add()方法。

现在,虽然这并不是那么好,但是你知道当你开始创建复杂的数学函数时,你的Add()方法将是坚如磐石的,这些函数将你的Sin()方法与你的Sqrt()方法和你的Add相结合()方法。

无论好坏,都是测试驱动开发。暂时不要过于依赖接口或依赖注入。如果你需要的话,这可以在以后发生。

答案 1 :(得分:6)

首先关闭.. TDD是No Magic / Silver Bullet。 用这个投入一些时间......这将是值得的。不要试图在即时或离开SO-post上接收TDD。拿起一本好书(肯特贝克/戴夫阿斯特尔斯),然后继续努力......(看到人们在他们需要的时候匆匆忙忙地退后一步并阅读,这令人沮丧。)

可测试性设计:

  • 首先编写代码测试..您的可测试设计应该出现..一次通过测试。你把它当成副产品......更像是一种正确的做法。
  • 一厢情愿:想象一下,你想要建立的东西已经存在。你会怎么跟它说话?这个简单的模拟应该为您提供'the thing'的外部接口定义
  • 保持简单/ YAGNI /抓住锤子:投入一些时间来查看实践(模式,DI,模拟,你会发现很多等等),以找出它们存在的原因。评估您是否需要它们,然后继续使用它们。另一个值得关注的好处是“哪里不使用它们?”。如果这听起来像个坏主意......可能就是这样。 (例如,我是否需要一个接口来为我编写的每个类做PR ...可能不是。我创建的每个类都需要Spring.net吗?)在每个检查点询问以下问题(K. beck对简单设计的定义) )

    • 代码和测试是否沟通了我需要沟通的所有内容?
    • 有没有重复? (消除,如果有的话)
    • 它是否包含尽可能少的类?...尽可能少的方法?有什么我可以从这个设计中拿走并保持行为。
  • 无情地重构:听Martin Fowler并保持书的方便

(这是一个有趣的过程,试图提炼你所做的几个步骤:)感谢有机会反思)

答案 2 :(得分:3)

我喜欢使用Kent Beck的经验法则。

编写描述理想Object API的测试。换句话说,编写您希望其他人可以编写的代码来实现某些功能。您最终可能无法以这种方式实际实现它,但您也可以在开始时使用BEST界面而不是其他东西。

另外,只是一点点测试提示......不要过分思考它。有很多人会告诉你如何测试,但根据我的经验,你是唯一能够真正做出决定的人。只要知道您正在测试的事实是让您的代码更好,并做正确的事情。当您查看其他经过良好测试的代码时,您将开始开发一种风格并立即成为专业人士。

希望这有帮助。

答案 3 :(得分:3)

首先,阅读11月24日刚刚发布的Google Guide to Writing Testable Code

这是MiškoHevery在Google测试传播者时对可测试性最佳实践的精彩汇编。

答案 4 :(得分:1)

你正朝着正确的方向前进,但不要太疯狂。例如,您不需要为要测试的每个对象都使用接口。

了解您描述的两种技术的主要动机是降低系统中的耦合。紧耦合系统很难测试,因为我无法检测到正确/不正确的行为,我无法阻止真实系统中的副作用(访问文件,数据库等)。

(事实证明,紧耦合也会使系统难以维护,因此为了测试目的而破坏耦合的努力也带来了其他好处,这就是为什么这么多人强调一般单元测试和TDD的设计优势的原因特别是。)

答案 5 :(得分:1)

如果您应用TDD,您将获得可测试且经过测试的设计,因为您将在代码旁边扩展单元测试套件。设计出现,由测试驱动。

找到一个粗略的设计(*)来解决手头的问题,然后从一个不依赖于另一个类的类开始,实现它,先测试。然后,您既可以实现一个没有依赖关系的类,也可以实现依赖于已经实现的类的类。

当实现依赖于另一个类的类时,您有两种可能性来测试它:要么使用另一个类的实例,要么模拟它,在这种情况下有一个接口可能会有所帮助。使用不支持接口的语言,您可以将要模拟的类子类化。

当您不希望您编码的类依赖于另一个类时,最好对接口进行编程,而不是依赖于另一个类属于另一个层或另一个组件。

(*)您不必设计所有内容,因为测试驱动您的代码可能会引导您进入另一个方向:请参阅演示 TDD 紧急设计的bowling game文章几页。

答案 6 :(得分:0)

如果你真的按照你所说的去做,你对Mocking部分是正确的。

至于接口,考虑到这一点,我的个人开发方法是我首先编写我的应用程序的主路径应该是什么样的(使用我的理想模拟API,什么都不做)。然后在查看我的Mock实现并对其进行几次精炼之后,我开始实际的类设计。在这个阶段“知道”我如何测试每个方法,并为它编写测试,如果由于某种原因我遇到一个方法我不能轻易弄清楚如何测试,这意味着方法不好,大多数可能它有太多的责任,太多的中间状态我无法测试,所以我打破了方法(界面赢得了类设计)。继续滴流,直到我有一个可行的应用程序。