首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在没有这么多模拟的情况下编写测试?

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

Stack Overflow用户
提问于 2009-10-20 16:52:34
回答 3查看 4.3K关注 0票数 22

我强烈主张正确的测试驱动设计或行为驱动的设计,我喜欢编写测试。但是,我一直将自己编码到一个角落,在这个角落里,我需要在单个类的特定测试用例中使用3-5模拟。无论从哪种方式开始,自上而下还是自下而上,我最终都会得到一个至少需要三个来自最高抽象级别的协作者的设计。

有人能就如何避免这一陷阱给出好的建议吗?

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

所以,与其把所有这些设计成我的Widget,我还做了一个Bridget协作者。Bridget负责一半的复杂性,数据库和网络,允许我关注另一半,也就是多媒体演示。然后,我制作了一个Gidget来执行多媒体片段。整个事情需要发生在后台,所以现在我包括一个Thridget来实现这一点。当所有的话和做完,我结束了一个Widget,它的手工作的Thridget,谈判的布里奇特,把它的结果给一个吉吉特。

因为我在CocoaTouch中工作,并且试图避免模拟对象,所以我使用了自分流模式,在这种模式中,对协作者的抽象变成了我的测试所采用的协议。与3+合作,我的测试气球和变得太复杂。即使使用类似于OCMock的模拟对象,也给我留下了一个我宁愿避免的复杂顺序。我试着把我的大脑围绕在雏菊链上(A代表B,代表C等等),但我无法想象。

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

代码语言:javascript
复制
//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);

显然,上面提到的是网络延迟,所以它必须是线程化的。这引入了线程抽象,以使我们摆脱线程单元测试的实践。我们现在有

代码语言:javascript
复制
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按钮单击事件。因此,最外面的测试反映了一个Sprint任务,上面有一个“播放电影”按钮,当它被点击时,电影就会播放。编辑让我们讨论一下。

EN

回答 3

Stack Overflow用户

发布于 2009-10-20 16:57:36

我做了一些相当完整的测试,但是它是自动化的集成测试,而不是单元测试,所以我没有模拟(除了用户:我嘲笑最终用户,模拟用户输入事件,测试/断言任何输出给用户的东西):应该测试内部实现,还是只测试公共行为?

我正在寻找的是使用TDD.的最佳实践。

维基百科描述 as

一种软件开发技术,它依赖于非常短的开发周期的重复:首先,开发人员编写一个失败的自动测试用例,该测试用例定义了所需的改进或新功能,然后生成通过测试的代码,最后将新代码重新组合到可接受的标准。

然后,它继续规定:

  1. 添加测试
  2. 运行所有测试,看看新测试是否失败。
  3. 写一些代码
  4. 运行自动化测试,并确保它们成功。
  5. 重构码

我做的第一个,即“非常短的开发周期”,在我的情况下的区别是,我测试后,它是写的。

我之所以在编写之后进行测试,是因为我根本不需要“编写”任何测试,甚至是集成测试。

我的周期大概是:

  1. 重新运行所有自动集成测试(从干净的开始)
  2. 实现一个新特性(如果有必要对现有代码进行重构以支持新特性)
  3. 重新运行所有自动集成测试(回归测试,以确保新开发不会破坏现有功能)
  4. 测试新功能: a.终端用户(me)通过用户界面进行用户输入,目的是执行新功能。 终端用户(me)检查相应的程序输出,以验证输出对于给定输入是否正确。
  5. 当我在步骤4中进行测试时,测试环境将用户输入和程序输出捕获到一个数据文件中;测试环境可以在将来重放这样的测试(重新创建用户输入,并断言相应的输出是否与先前捕获的预期输出相同)。因此,在步骤4中运行/创建的测试用例被添加到所有自动化测试的套件中。

我认为这给了我TDD的好处:

  • 测试与开发相结合:我在编码后立即进行测试,而不是在编码之前进行测试,但无论如何,新代码在签入之前都会进行测试;从来没有未经测试的代码。
  • 我有自动测试套件,用于回归测试。

我避免了一些成本/缺点:

  • 编写测试(相反,我使用UI创建新的测试,这样更快、更容易,并且更接近于最初的需求)
  • 创建模拟(单元测试所需)
  • 当内部实现被重构时编辑测试(因为测试只依赖于公共API,而不是内部实现细节)。
票数 0
EN

Stack Overflow用户

发布于 2009-10-20 17:22:36

我的解决方案(不是CocoaTouch)是继续模拟对象,而是将模拟重新设置为一个通用的测试方法。这降低了测试本身的复杂性,同时保留了模拟基础结构,以便隔离地测试我的类。

票数 0
EN

Stack Overflow用户

发布于 2016-05-07 22:13:12

为了摆脱过度的嘲弄,您可以遵循试验金字塔,这意味着有大量的单元+组件测试和较少数量的缓慢和脆弱的系统测试。它归结为几个简单的规则:

  • 在不需要嘲弄的最低级别编写测试。如果您可以编写单元测试(例如解析字符串),那么编写它。但是,如果要检查解析是否由上层调用,则需要初始化更多的内容。
  • 模拟外部系统。你的系统需要是一个独立的独立部分。依赖外部应用程序(这些应用程序会有自己的bug)将使测试变得非常复杂。编写模拟/存根要容易得多。
  • 在那之后,有几个测试检查你的应用程序与真正的集成。

有了这种心态,你几乎可以消除所有的嘲弄。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/1595952

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档