需要TDD方法的想法

时间:2009-12-04 16:53:50

标签: c# unit-testing tdd mocking moq

我们刚刚为我们的专有系统发布了一个重写(第三次)模块。这个模块,我们称之为负载管理器,是迄今为止我们系统中所有模块中最复杂的模块。我们正在努力获得一个全面的测试套件,因为每次我们对这个模块进行任何重大改变时,都需要花费数周的时间来解决错误和怪癖。但是,开发测试套件已经证明非常困难,因此我们正在寻找创意。

Load Manager的内容驻留在一个名为LoadManagerHandler的类中,这基本上是模块背后的所有逻辑。此处理程序调用多个控制器来执行数据库中的CRUD方法。这些控制器本质上是位于顶部的DAL的顶层,并抽象出我们的LLBLGen生成的代码。

因此,使用Moq框架模拟这些控制器是很容易的。然而,问题出在Load Manager的复杂性上,我们收到的问题不是处理简单的情况,而是处理程序中包含大量数据的情况。

简要说明加载管理器包含许多“已卸载”的详细信息,有时会有数百个,然后放入用户创建的加载和重装池中。在创建和填充这些加载的过程中,会出现大量删除,更改和添加,最终会导致出现问题。但是,因为当你模拟一个对象的方法时,最后一个模拟获胜,即:

jobDetailControllerMock.Setup(mock => mock.GetById(1)).Returns(jobDetail1);
jobDetailControllerMock.Setup(mock => mock.GetById(2)).Returns(jobDetail2);
jobDetailControllerMock.Setup(mock => mock.GetById(3)).Returns(jobDetail3);

无论我发送到jobDetailController.GetById(x)的什么,我总会回来jobDetail3。这使得测试几乎不可能,因为我们必须确保在进行更改时所有受影响的点都会受到影响。

所以,我决定使用测试数据库,只是允许读取和写入正常进行。但是,因为您不能(读取:不应该)指示测试的顺序,所以先前运行的测试可能导致稍后运行的测试失败。

TL / DR:我基本上在寻找面向数据的代码的测试策略,这些代码本质上非常复杂。

6 个答案:

答案 0 :(得分:2)

如Seb所述,您确实可以使用范围匹配:

controller.Setup(x => x.GetById(It.IsInRange<int>(1, 3, Range.Inclusive))))).Returns<int>(i => jobs[i]);

此代码使用传递给方法的参数来计算要返回的值。

答案 1 :(得分:1)

要了解Moq的“最后模拟胜利”,您可以使用此博客中的技术:

Moq Triqs - Successive Expectations

修改

实际上你根本不需要那个。根据您的示例,Moq将根据方法参数返回不同的值。

public interface IController { string GetById(int id); }

class Program
{
    static void Main(string[] args)
    {
        var mockController = new Mock<IController>();

        mockController.Setup(x => x.GetById(1)).Returns("one");
        mockController.Setup(x => x.GetById(2)).Returns("two");
        mockController.Setup(x => x.GetById(3)).Returns("three");

        IController controller = mockController.Object;

        Console.WriteLine(controller.GetById(1));
        Console.WriteLine(controller.GetById(3));
        Console.WriteLine(controller.GetById(2));
        Console.WriteLine(controller.GetById(3));
        Console.WriteLine(controller.GetById(99) == null);
    }
}

class Program { static void Main(string[] args) { var mockController = new Mock<IController>(); mockController.Setup(x => x.GetById(1)).Returns("one"); mockController.Setup(x => x.GetById(2)).Returns("two"); mockController.Setup(x => x.GetById(3)).Returns("three"); IController controller = mockController.Object; Console.WriteLine(controller.GetById(1)); Console.WriteLine(controller.GetById(3)); Console.WriteLine(controller.GetById(2)); Console.WriteLine(controller.GetById(3)); Console.WriteLine(controller.GetById(99) == null); } }

输出是:

   one
   three
   two
   three
   True

答案 2 :(得分:1)

听起来像LoaderManagerHandler确实......相当多的工作。班级名称中的“经理”总是让我担心......从TDD的角度来看,如果可能的话,可能值得考虑适当地打破课程。

这堂课多长时间了?

答案 3 :(得分:0)

我从未使用过Moq,但它似乎应该能够通过提供的参数匹配模拟调用。

快速浏览一下Quick Start documentation,摘录如下:

//Matching Arguments

// any value
mock.Setup(foo => foo.Execute(It.IsAny<string>())).Returns(true);


// matching Func<int>, lazy evaluated
mock.Setup(foo => foo.Add(It.Is<int>(i => i % 2 == 0))).Returns(true); 


// matching ranges
mock.Setup(foo => foo.Add(It.IsInRange<int>(0, 10, Range.Inclusive))).Returns(true); 

我认为你应该能够使用上面的第二个例子。

答案 4 :(得分:0)

一种简单的测试技术是确保每次针对系统记录错误时,请确保编写覆盖该案例的单元测试。您可以使用该技术构建一组非常可靠的测试。更好的是,你不会两次遇到同样的事情。

答案 5 :(得分:0)

  
    

无论我发送到jobDetailController.GetById(x),我总会找回jobDetail3

  

您应该花更多时间调试测试,因为发生的事情并不是Moq的行为方式。您的代码或测试中存在一个错误,导致行为异常。

如果您想使用相同的输入但不同的输出重复调用,您还可以使用不同的模拟框架。 RhinoMocks支持录制/播放习惯用法。你是对的,这并不总是你想要的强制执行电话订单。我更喜欢Moq,因为它简单。