unit-testing - 如何在没有这么多模拟的情况下编写测试?

标签 unit-testing tdd mocking

我是正确的测试驱动设计或行为驱动设计的大力倡导者,我喜欢编写测试。然而,我一直把自己编码到一个角落,我需要在一个特定的测试用例中为一个类使用 3-5 个模拟。无论我以哪种方式开始,自上而下或自下而上,我最终的设计都需要来自最高抽象级别的至少三个协作者。

有人可以就如何避免这个陷阱给出好的建议吗?

这是一个典型的场景。我设计了一个从给定文本值生成 Midget 的 Widget。在我进入细节之前,它总是开始非常简单。我的 Widget 必须与一些难以测试的东西交互,比如文件系统、数据库和网络。

因此,我没有将所有这些都设计到我的 Widget 中,而是创建了一个 Bridget 合作者。 Bridget 处理了一半的复杂性,即数据库和网络,让我可以专注于另一半,即多媒体演示。所以,然后我制作了一个执行多媒体片段的小工具。整个事情需要在后台发生,所以现在我包括一个 Thridget 来实现它。当一切都说完了,我最终得到了一个小部件,它将工作交给一个 Thridget,它通过一个 Bridget 将其结果提供给一个 Gidget。

因为我在 CocoaTouch 中工作并试图避免模拟对象,所以我使用自分流模式,其中对协作者的抽象成为我的测试采用的协议(protocol)。与 3 个以上的合作者一起,我的测试气球变得太复杂了。即使使用 OCMock 模拟对象之类的东西,也会给我留下一个我宁愿避免的复杂性顺序。我尝试将我的大脑包裹在一个菊花链的协作者(A 代表 B,B 代表 C 等等),但我无法想象它。

编辑
以下面的示例为例,假设我们有一个必须从套接字读取/写入并显示返回的电影数据的对象。

//Assume myRequest is a String param...
InputStream   aIn  = aSocket.getInputStram();
OutputStream  aOut = aSocket.getOutputStram();
DataProcessor aProcessor = ...;

// This gets broken into a "Network" collaborator.
for(stuff in myRequest.charArray()) aOut.write(stuff);
Object Data = aIn.read(); // Simplified read

//This is our second collaborator
aProcessor.process(Data);

现在上面显然处理网络延迟,所以它必须是线程的。这引入了一个线程抽象来让我们摆脱线程单元测试的实践。我们现在有
AsynchronousWorker myworker = getWorker(); //here's our third collaborator
worker.doThisWork( new WorkRequest() {
//Assume myRequest is a String param...
DataProcessor aProcessor = ...;

// Use our "Network" collaborator.
NetworkHandler networkHandler = getNetworkHandler();
Object Data = networkHandler.retrieveData(); // Simplified read

//This is our multimedia collaborator
aProcessor.process(Data);
})

请原谅我在没有测试的情况下向后工作,但我正要带我的女儿出去,我正在匆匆完成这个例子。这里的想法是,我正在从一个简单的界面后面协调几个协作者的协作,该界面将与 UI 按钮单击事件相关联。所以最外面的测试反射(reflect)了一个 Sprint 任务,它说给定一个“播放电影”按钮,当它被点击时,电影就会播放。
编辑
让我们讨论。

最佳答案

拥有许多模拟对象表明:

1)你有太多的依赖。
重新查看您的代码并尝试进一步分解它。特别是,尽量将数据转换和处理分开。

由于我在您正在开发的环境中没有经验。所以让我以我自己的经验为例。

在 Java 套接字中,您将获得一组简单的 InputStream 和 OutputStream,以便您可以从对等点读取数据并将数据发送到对等点。所以你的程序看起来像这样:

InputStream  aIn  = aSocket.getInputStram();
OutputStream aOut = aSocket.getOutputStram();

// Read data
Object Data = aIn.read(); // Simplified read
// Process
if (Data.equals('1')) {
   // Do something
   // Write data
   aOut.write('A');
} else {
   // Do something else 
   // Write another data
   aOut.write('B');
}

如果你想测试这个方法,你最终必须为 In 和 Out 创建 mock,这可能需要它们背后的相当复杂的类来支持。

但是如果你仔细看,从 aIn 中读取和向 aOut 中写入是可以分开处理的。因此,您可以创建另一个类,该类将接受读取输入并返回输出对象。
public class ProcessSocket {
    public Object process(Object readObject) {
        if (readObject.equals(...)) {
       // Do something
       // Write data
       return 'A';
    } else {
       // Do something else 
       // Write another data
       return 'B';
   }
}

你以前的方法是:
InputStream   aIn  = aSocket.getInputStram();
OutputStream  aOut = aSocket.getOutputStram();
ProcessSocket aProcessor = ...;

// Read data
Object Data = aIn.read(); // Simplified read
aProcessor.process(Data);

这样您就可以在几乎不需要模拟的情况下测试处理。你测试可以去:

ProcessSocket aProcessor = ...;
assert(aProcessor.process('1').equals('A'));

因为处理现在独立于输入、输出甚至套接字。

2)您通过单元测试完成了单元测试,应该进行集成测试。

有些测试不适用于单元测试(从某种意义上说,它需要不必要的更多努力,并且可能无法有效地获得一个好的指标)。这类测试的示例是那些涉及并发和用户界面的测试。它们需要与单元测试不同的测试方式。

我的建议是你进一步分解它们(类似于上面的技术),直到其中一些适合单元测试。所以你有一些难以测试的部分。

编辑

如果你相信你已经把它分解成非常细的部分,也许这就是你的问题。

软件组件或子组件以某种方式相互关联,例如字符组合成词,词组合成句子,句子组合成段落,段落组合成小节,章节,章节等等。

我的例子说,你应该将小节分成段落,而你已经将事情归结为单词。

这样看,大多数时候,段落与其他段落的关联程度比与其他句子相关(或依赖于)其他句子的句子松散程度要低。小节、小节更加松散,而单词和字符则更加依赖(随着语法规则的出现)。

因此,也许您将其破坏得如此之好,以至于语言语法强制这些依赖项,进而迫使您拥有如此多的模拟对象。

如果是这种情况,您的解决方案是平衡测试。如果一个部分被许多人依赖并且它需要一组复杂的模拟对象(或简单的更多努力来测试它)。可能你不需要测试它。例如,如果 A 使用 B,C 使用 B,而 B 太难测试了。那么,为什么不将 A+B 视为一个,将 C+B 视为花药。在我的示例中,如果 SocketProcessor 太难测试,太难以至于您将花费更多时间测试和维护测试而不是开发它,那么这是不值得的,我将一次测试所有内容。

如果没有看到您的代码(并且事实上我从未开发过 CocooTouch),这将很难说。我也许可以在这里提供很好的评论。对不起:D。

编辑 2
查看您的示例,很明显您正在处理集成问题。假设您已经分别测试播放电影和 UI。为什么需要这么多模拟对象是可以理解的。如果这是您第一次使用这种集成结构(这种并发模式),那么实际上可能需要那些模拟对象,而您对此无能为力。这就是我能说的:-p

希望这可以帮助。

关于unit-testing - 如何在没有这么多模拟的情况下编写测试?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1595952/

相关文章:

java - Mockito - 如何对参数是否在另一个函数调用的函数中设置进行单元测试

java - 有没有更好的方法来测试以下方法而无需模拟返回模拟?

c++ - 谷歌模拟 : Mocked overloaded functions create warning C4373

unit-testing - 为什么我们使用接口(interface)来模拟 Golang 方法

java - 使用 powermockito 模拟 URL 类时出现问题

java - 如何使用构建器模拟内部变量

java - 为什么模拟私有(private)方法进入方法?

c++ - 使用 QTestLib 时抑制 qDebug

java - JUnit 测试上的 "Singleton"

javascript - TDD 基础知识 - 我是否添加或替换测试?