2011-02-12 56 views
19

我想在我的应用程序中开始进行更多的单元测试,但在我看来,我所做的大部分工作都不适合单元测试。我知道单元测试如何在教科书示例中起作用,但在真实世界的应用程序中,它们似乎没有多大用处。主要与外部资源交互的单元测试程序

我写的一些应用程序具有非常简单的逻辑和与我无法控制的事物之间的复杂交互。例如,我想编写一个守护进程,它对某些应用程序发送的信号作出反应,并更改操作系统中的某些用户设置。我可以看到三个难点:

  • 首先,我必须能够与应用程序进行交谈并通知其事件;
  • 然后我需要与操作系统交互每当我收到一个信号,为了改变适当的用户设置;
  • 终于所有这些应该作为守护进程。

所有这些事情都可能很微妙:我将不得不浏览可能复杂的API,并且可能会引入错误,比如错误地解释一些参数。单元测试能为我做什么?我可以嘲笑外部应用程序和操作系统,并检查给定来自应用程序的信号,我将在OS上调用适当的API方法。这是...嗯,应用程序的微不足道的部分。

其实我所做的大部分事情都涉及与数据库,文件系统或其他应用程序的交互,而这些是最脆弱的部分。

又如看看my build tool PHPmake。我想重构它,因为它的写法不是很好,但我害怕这样做,因为我没有测试。所以我想补充一些。问题的关键是,它可以通过重构被打破的东西可能不被单元测试捕获:

  • 一个要做的事情是决定这一切都是要建立和哪一个是已经是最新的,这取决于上次修改文件的时间。这一次实际上是由外部进程改变的,当一些构建命令被触发时。
  • 我想确保外部过程的输出正确显示。 buikd命令有时需要一些输入,并且应该也可以正确管理。但我不知道先验哪些流程会运行 - 可能是任何事情。
  • 模式匹配中涉及一些逻辑,这似乎是可测试的部分。但是模式匹配的函数使用(除了它们自己的逻辑)PHP函数glob,它与文件系统一起工作。如果我只是嘲笑一棵树来代替实际的文件系统,glob将不起作用。

我可以继续更多的例子,但重点是以下几点。除非我有一些精巧的算法,否则我所做的大部分工作都涉及到与外部资源的交互,而这不适用于单元测试。更多的是,这种交互通常实际上是非平凡的部分。仍然有很多人将单元测试视为一种基本工具。我错过了什么?我怎样才能学会更好的测试者?

+0

另请参见http://stackoverflow.com/questions/4980825/real-world-unit-tests – Raedwald 2013-09-06 12:35:52

回答

8

我想你在你的问题中打开了一些问题。首先,当您的应用程序与外部环境(如OS,其他线程等)集成时,则必须将(1)与外部环境绑定的逻辑与(2)业务代码分开。也就是你的应用程序所做的事情。这与在应用程序(或Web应用程序)中将GUI和SERVER分开的方式没有什么不同。

其次,你问你是否应该测试简单的逻辑。我会说,这取决于。通常简单的获取/存储功能很适合进行测试。这就像你的应用程序的基础,因此它很重要。其他业务内容建立在您的基础上,非常简单,您可能很容易发现自己都觉得自己在浪费时间,主要是:-)

第三,对现有程序进行重做并对其现有状态进行测试可能是一个问题。如果您的PHP程序基于某些输入生成一组文件,那么也许这就是您测试的入口点。确保测试可能是高级的,但是确保在重构​​之后,程序产生相同的输出是一种简单的方法。因此,在重构工作的开始阶段,要针对这种情况进行更高级别的测试。

我想推荐一些文献,但我只能拿出一个标题。 “使用遗留代码有效地工作”作者:Micheal Feathers。这是一个好的开始。另一个是“xUnit测试模式:重构测试代码” Gerard Meszaros(虽然这本书更加草率和复制粘贴文本)。

+1

+1:如果你的程序依赖于外部的东西,你只需在单元测试中模拟外部的东西,将I/O代码从逻辑和测试逻辑单元。单元测试不应该给你100%的错误覆盖率,没有测试方法。只要不打扰,尽你所能。 – 2011-02-12 22:29:31

+0

是的,这就是我将如何编程:首先让逻辑工作,然后处理与外部环境的整合。 – 2011-02-12 22:30:42

2

“单元测试”测试一个代码单元。不应该涉及外部工具。这对你的第一个应用程序来说似乎很复杂(不知道这件事多么);但是phpMake是单元测试的 - 我敢肯定...因为蚂蚁,gradle和maven也可以单元测试;)!

但是,当然您也可以自动测试您的第一个应用程序。有several different layers可以测试一个应用程序。

因此,您的任务是找到一种自动化的方式来测试您的应用程序 - 无论是集成测试还是其他任何方式。

E.g.你可以编写shell脚本,它会声明一些输出!有了这个你确保你的应用程序行为正确...

4

至于你对现有的未目前在你想开始重构测试覆盖了代码库的问题,我建议阅读:

Working Effectively with Legacy Code由迈克尔羽毛。

这本书为您提供了如何处理PHPMake可能面临的问题的技巧。它提供了引入测试的接缝的方法,以前没有。


此外,随着代码触及说,文件系统,可以抽象出文件系统调用后面的瘦包装,使用适配器模式。单元测试是针对包装类实现的抽象接口的假实现。

在某些时候,您会达到足够低的水平,因为这些依赖于库或API调用(例如在包装器的生产实现中),因此单元测试无法隔离代码单元。一旦发生这种情况,集成测试实际上是唯一可以编写的自动开发人员测试。

1

与外部资源的交互测试是集成测试,而不是单元测试。

如果发生特定的外部交互,您的代码的测试可以是单元测试。这些应该通过编写代码来使用依赖注入来完成,然后在单元测试中将注入模拟对象注入为依赖关系。

例如,考虑一段代码,增加了一个调用的结果,以一个服务调用的结果到另一个服务:

public int AddResults(IService1 svc1, IService2 svc2, int parameter) 
{ 
    return svc1.Call(parameter) + svc2.Call(parameter); 
} 

你可以通过在模拟对象测试这两项服务:

private class Service1Returns1 : IService1 
{ 
    public int Call(int parameter){return 1;} 
} 

private class Service2Returns1 : IService2 
{ 
    public int Call(int parameter){return 1;} 
} 

public void Test1And1() 
{ 
    Assert.AreEqual(2, AddResults(new Service1Returns1(), new Service2Returns1(), 0)); 
} 
2

我推荐这个google tech-talk on unit testing

视频归结为

  • 编写代码,以便它知道少这件事将如何使用成为可能。代码的假设越少,测试就越容易。避免构造函数中的复杂逻辑,使用单例,静态类成员等等。
  • isolation您的代码来自外部世界(通信,数据库,实时),并确保您的代码只与您的隔离层进行通信。否则,就'假环境'设置而言,编写测试将是一场噩梦。
  • 单元测试应测试故事;这是我们真正理解和关心的;给定一个方法foo()testFoo()是无用信息。他们实际上推荐测试名称,如itShouldCloseConnectionEvenWhenExceptionThrown()。理想情况下,您的故事应涵盖足够的功能,以便您可以从故事中重建规范。

注意:本视频和本文以Java为例;然而,主要观点代表任何语言。

1

首先,如果单元测试看起来不像在应用程序中使用太多,为什么你甚至想要开始做更多的事情呢?什么激励你关心它?如果a)你第一次做完所有事情,而且没有任何变化,或者b)你认为这是浪费时间,做得不好,那绝对是浪费时间。

如果你认为你确实想做单元测试,那么你的问题的答案都是一样的:封装。在守护程序示例中,您可以创建一个ApplcationEventObeservationProxy,它具有一个非常窄的接口,只是实现了传递方法。这个类的目的是做没有什么,但完全封装来自第三方事件观察库的其余代码(没什么意思,没有任何意义 - 这里没有逻辑)。为OS设置做同样的事情。然后,您可以完全单元测试基于事件执行操作的类。我建议为守护进程提供一个单独的类来包装你的主类 - 它会使测试更容易。

除了单元测试之外,这种方法还有一些好处。一个是如果你封装直接与操作系统交互的代码,则更容易切换。这种代码特别容易在您的控制之外被破坏(即MS补丁集)。您也可能想要支持多个操作系统,并且如果特定于操作系统的逻辑与其他逻辑没有纠缠,则会更容易。另一个好处是,您将被迫意识到应用程序中存在比您想象的更多的业务逻辑。:)

最后,不要忘记,单元测试是一个好产品的基础,但不是唯一的成分。探索和验证您将使用的OS API调用的一组测试对于此问题的“困难”部分是一个很好的策略。您还应该进行端到端测试,以确保应用程序中的事件导致操作系统设置更改。

0

其他答案建议有效地使用遗留代码Micheal羽毛是一个很好的阅读。如果您必须处理遗留代码,并且您希望确保系统交互按预期工作,请先尝试编写集成测试。然后编写单元测试来测试从需求角度来看价值的方法的行为更为合适。您的测试与集成测试完全不同。单元测试更有可能改进系统的设计,而不是测试每件事情如何收集。