2009-12-04 36 views
2

我们刚刚发布了一个为我们专有系统重写的模块(第三次)。这个模块,我们称之为负载管理器,迄今为止是迄今为止我们系统中所有模块中最复杂的。我们试图获得一个全面的测试套件,因为每当我们对这个模块做出任何重大改变时,都会花费数周时间来解决错误和怪癖。然而,开发一个测试套件已经证明是相当困难的,所以我们正在寻找想法。需要TDD方法的想法

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

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

简要解释负载管理器包含一些“卸载”细节,有时数百个,然后放入用户创建的负载和reship池中。在创建和填充这些负载的过程中,会有大量的删除,更改和添加,最终导致问题出现。然而,因为当你嘲笑对象的方法,最后模拟胜,即:

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:我本质上是在寻找面向数据的代码的测试策略,这种代码本质上是非常复杂的。

回答

1

要解决“最后模拟胜”与起订量,你可以从这个博客使用的技术:

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); 
    } 
} 

输出是:

 
    one 
    three 
    two 
    three 
    True 
+0

我提高了你的答案,因为我不知道这是可能的,但我不相信这将完全达到我期待的结果。我需要根据所使用的参数返回特定的数据,并且此方法不一定会按预定顺序调用。不过非常感谢,这会帮助我在其他地方。 – joshlrogers 2009-12-04 17:23:39

+0

好的....有什么想法,为什么这不适合我呢?我正在使用Moq的最新版本,无论我传入什么参数,我都会收到最后的模拟返回结果。 – joshlrogers 2009-12-04 17:30:16

+0

@josh - 不知道。我使用v3.1.416.3进行了测试,该版本是该网站的最新非beta版本。这是标准行为,这就是为什么必要时引入It.IsAny语法来覆盖它的原因。我建议你用控制器编写最简单的测试,看看你能否消除变量。您也可以发表Moq讨论 - 他们非常有帮助。 http://groups.google.com/group/moqdisc – TrueWill 2009-12-04 17:36:43

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); 

我想你应该可以使用上面的第二个例子。

+0

感谢您的回复。我尝试了多种不同的方式,但最终这是我发现的(在备注部分): http://www.clariusconsulting.net/labs/moq/html/3BFDD309.htm – joshlrogers 2009-12-04 17:15:27

+0

@josh - 哇,那链接的误导。我打算就此向作者发送反馈意见。 – TrueWill 2009-12-04 17:39:54

+0

@josh - 我认为你所看到的链接确实是说“如果有两个期望与最后一个赢得的结果相匹配”。 就你而言,如果你使用It.Is匹配器,那么就不会有冲突。 – 2009-12-04 19:46:52

0

一个简单的测试技术是确保每次一个错误记录对一个系统中,确保一个单元测试写入覆盖这种情况下。你可以用这种技术建立一套非常坚实的测试。甚至更好,你不会两次碰到同一件事。

0

不管我送jobDetailController.GetById(X)我将永远找回jobDetail3

你应该花更多的时间调试测试,因为正在发生的事情并不怎么起订量行为方式。您的代码或测试中存在导致某些操作不当的错误。

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

2

正如勒布指出的,你的确可以使用一系列配套:

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

该代码使用传递给方法来计算要返回的值的论点。

1

这听起来像LoaderManagerHandler做...很多工作。在课堂名称中的“经理”总是有点担心我......从TDD的角度来看,如果可能的话,可能需要考虑如何适当地打破课堂。

这个班多久了?

+0

LoadManagerHandler确实做了很多工作,它现在包含了实际Load Manager模块的所有逻辑(模块的名称是“manager”包含在类名称中的唯一原因)。创建这个类是为了将这个模块的整个逻辑包含在一个位置中而尝试删除。它大约是1400行代码,所以不是一个庞然大物,很多是参数验证等。所有的数据访问代码都是由数据层处理的。从我读过的内容来看,这应该是TDD的最佳方式,但是我可能会误会。 – joshlrogers 2009-12-14 14:56:44

+0

就我个人而言,我认为1400行是一个非常大的类,可能需要重构为较小的大小,尽管不是病态的巨大。除了规模之外,对班级的描述听起来像是有许多责任,违反了“单一责任原则”并成为上帝阶级。无论如何,当我听到“课堂太复杂,有太多依赖要测试”时,我的直接反应总是“把它分解成更小,更简单的课程”。控制模式的反转可能会有所帮助。 – kyoryu 2009-12-15 21:32:38