Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/unit-testing/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Unit testing 如何在没有这么多模拟的情况下编写测试?_Unit Testing_Tdd_Mocking - Fatal编程技术网

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

Unit testing 如何在没有这么多模拟的情况下编写测试?,unit-testing,tdd,mocking,Unit Testing,Tdd,Mocking,我大力提倡正确的测试驱动设计或行为驱动设计,我喜欢编写测试。然而,我一直把自己编码到一个角落,我需要在一个特定的测试用例中为单个类使用3-5个模拟。无论我从哪种方式开始,自上而下还是自下而上,我最终的设计都需要来自最高抽象级别的至少三个协作者 有人能就如何避免这个陷阱给出好的建议吗 这是一个典型的场景。我设计了一个小部件,它根据给定的文本值生成一个侏儒。在我进入细节之前,它总是非常简单。我的Widget必须与一些难以测试的东西交互,比如文件系统、数据库和网络 因此,我没有将所有这些都设计到我的小

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

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

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

因此,我没有将所有这些都设计到我的小部件中,而是制作了一个Bridget协作器。Bridget处理了复杂性的一半,数据库和网络,这让我可以专注于另一半,即多媒体演示。然后,我做了一个Gidget来表演多媒体片段。整个事情都需要在后台发生,所以现在我加入了一个Thridget来实现这一点。当所有的事情都说了又做了,我最终得到了一个小部件,它将工作交给Thridget,Thridget通过Bridget将结果传递给Gidget

因为我在CocoaTouch中工作,并且试图避免模拟对象,所以我使用了自分流模式,在这种模式中,协作者上的抽象成为我的测试采用的协议。有了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按钮单击事件。因此,outter most测试反映了一个Sprint任务,即给定一个“播放电影”按钮,当它被单击时,电影将播放。 编辑
让我们讨论一下。

我做了一些相当完整的测试,但它是自动集成测试,而不是单元测试,所以我没有模拟(除了用户:我模拟最终用户,模拟用户输入事件,测试/断言用户的输出):


我想要的是使用TDD的最佳实践。

作为

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

然后,它继续规定:

  • 添加一个测试
  • 运行所有测试,查看新测试是否失败
  • 写一些代码
  • 运行自动化测试并确保它们成功
  • 重构代码
  • 我做了其中的第一个,即“非常短的开发周期”,不同之处在于我在编写之后进行了测试

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

    我的周期是这样的:

  • 重新运行所有自动化集成测试(从头开始)
  • 实现一个新特性(如有必要,重构现有代码以支持新特性)
  • 重新运行所有自动化集成测试(回归测试以确保新开发没有破坏现有功能)
  • 测试新功能:

    a。最终用户(me)通过用户界面进行用户输入,以实现新功能

    b。最终用户(me)检查相应的程序输出,以验证输出对于给定输入是否正确

  • 当我在步骤4中进行测试时,测试环境将用户输入和程序输出捕获到数据文件中;测试环境可以在将来重播这样的测试(重新创建用户输入,并断言相应的输出是否与之前捕获的预期输出相同)。因此,在步骤4中运行/创建的测试用例被添加到所有自动化测试套件中

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

    • 测试与开发是结合在一起的:我在编码后立即进行测试,而不是在编码之前,但无论如何,新代码在签入之前就进行了测试;从来没有未经测试的代码

    • 我有自动测试套件,用于回归测试

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

    • 编写测试(相反,我使用UI创建新的测试,它更快、更容易,更接近原始需求)

    • 创建模拟(单元测试所需)

    • 在重构内部实现时编辑测试(因为测试只依赖于公共API,而不依赖于内部实现细节)


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

        有许多模拟对象表明:

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

        因为我知道
        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');
        }
        
        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'));