我和我的同事昨晚在我们的PHP / MySQL应用程序中对单元测试有点意见不一。我们中的一半人认为,当对一个类中的函数进行单元测试时,你应该模拟该类及其父类之外的所有内容。我们的另一半人认为你不应该嘲笑任何直接依赖于班级的东西。
具体示例是我们的日志记录机制,它通过静态Logging类发生,我们在整个应用程序的不同位置进行了许多Logging :: log()调用。我们前半部分说Logging机制应该伪造(模拟),因为它将在Logging单元测试中进行测试。我们的下半部分认为我们应该在单元测试中包含原始的Logging类,这样如果我们对日志记录界面进行更改,我们将能够看到它是否因为失败而在应用程序的其他部分中产生问题更新通话界面。
所以我猜基本问题是 - 单元测试是用来测试封闭环境中单个单元的功能,还是显示更大环境中单个单元更改的后果?如果是其中之一,你如何完成另一个?
答案 0 :(得分:11)
您和您的同事偶然发现了单元测试和集成测试之间的区别。模仿一切都将为前者完成;不会嘲笑后者的依赖性。
当然,绘制粒度线的地方也是主观的 - 但是在单元测试的最精细程度上,你不应该担心每个测试的特定主题之外的任何事情。
答案 1 :(得分:5)
单元测试就像测试电子设备中的单个组件一样。
如果您想在吉他放大器中测试单个晶体管,则不要插入吉他,调高音量,并检查发出的声音。那将是愚蠢的。要检查晶体管,可以将设备连接到晶体管的引脚,并仅测量其输入和输出。
在某些时候你会测试整个事物(吉他会产生噪音等),但这与测试单个晶体管不同。
答案 2 :(得分:1)
这些词语给出了答案的一些提示:
单位意味着1:你试图测试一件事。当不是测试的主要焦点时,您可以模拟日志框架之类的依赖项。被测试的单元如何与它的依赖关系进行交互通常是测试用例的一部分,而模拟使得这更容易。
集成意味着不止一件事情在一起:你试图将一个以上的东西组合在一起作为一个整体的测试。模拟扮演较小的角色,但它仍然可以用于模拟在测试场景中难以设置的依赖项。
我想说的是,在单元测试中没有嘲笑日志系统,看看更改是否破坏任何东西的论点都相当薄弱。如果日志记录系统中的任何内容中断,则应该通过针对日志记录系统的失败单元测试来捕获该内容。
保持单元测试简单,清晰和专注是一个很好的经验法则。在扩大测试范围时,Yuu需要这种简单的基础。集成测试很快就会变得复杂,特别是当它们用作缺失单元测试的替代品时 - 就像“远距离测试”一样,越远离组件,测试和诊断失败就越困难。通过单元测试测试远程依赖就像是试图使用钓竿从房间另一侧操作电视遥控器的人。
答案 3 :(得分:0)
@David,我看到你的单元测试正在完成它应该做的工作。
您正在思考软件的设计。
从您提出关于封闭环境与开放环境的问题的方式来看,这将使我相信您必须回答基于状态和行为单元测试代码的问题。< / p>
对于何时使用真实对象或模拟进行单元测试,没有严格的快速规则。 只有指导原则!使用真实Logging
对象进行单元测试的问题是,此对象的测试不仅在记录器的单元测试中,而且在单元测试中也是如此。使用记录器的对象。
因此,如果您有20个使用记录器的对象,并且当您更改记录器界面时,将至少有21个失败的测试。这在重构时可能非常痛苦。但另一方面,如果您不在20个对象单元测试中使用记录器类,并且您更改了记录器接口,则只有一个失败的测试和20个其他单元测试变为绿色,即使它们将在生产中失败。 / p>
我能真正给你的唯一见解就是你没有正确的抽象。您可能想要查找SOLID。这些应该是指导原则。
请记住每当出现问题时,就像您要求的那样,代码会给您反馈。注意那个反馈。它会在以后为你节省很多痛苦。