【问题标题】:Need ideas for a TDD Approach需要 TDD 方法的想法
【发布时间】:2009-12-04 16:53:50
【问题描述】:

我们刚刚为我们的专有系统发布了一个重新编写的(第三次)模块。这个模块,我们称之为负载管理器,是迄今为止我们系统中所有模块中最复杂的。我们正在尝试获得一个全面的测试套件,因为每次我们对该模块进行任何重大更改时,都会花费数周时间来整理错误和怪癖。然而,事实证明,开发测试套件相当困难,因此我们正在寻找想法。

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

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

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

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:我本质上是在为本质上相当复杂的面向数据的代码寻找测试策略。

【问题讨论】:

    标签: c# unit-testing tdd mocking moq


    【解决方案1】:

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

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

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

    【讨论】:

      【解决方案2】:

      要通过 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);
          }
      }
      

      输出是:

      一 三 二 三 真的

      【讨论】:

      • 我赞成你的回答,因为我不知道这是可能的,但我不相信这会完全达到我正在寻找的结果。我需要根据使用的参数返回特定的数据,并且不一定会以预定的顺序调用此方法。非常感谢,这将在其他地方帮助我。
      • 好的....关于为什么这对我不起作用有什么想法吗?我使用的是最新版本的起订量,无论我传入什么参数,我都会收到最后一个模拟返回结果。
      • @josh - 不知道。我使用 v3.1.416.3 对其进行了测试,这是该站点的最新非 beta 版本。这是标准行为,这就是引入 It.IsAny 语法以在必要时覆盖它的原因。我建议您使用控制器编写最简单的测试,看看是否可以消除变量。您也可以在 Moq Discussions 上发帖 - 它们非常有帮助。 groups.google.com/group/moqdisc
      • P.S.如果您在其他地方找到答案,请在此处发布。你总是可以回答你自己的问题! :)
      • 我假设既然你让它工作了,那一定是我的代码有问题。我会再试一次,如果我发现我做错了,我会让大家知道。非常感谢您的帮助!
      【解决方案3】:

      听起来 LoaderManagerHandler 确实...相当多的工作。类名中的“Manager”总是让我有些担心……从 TDD 的角度来看,如果可能的话,可能值得考虑适当地分解类。

      这节课多长时间?

      【讨论】:

      • LoadManagerHandler 确实做了很多工作,它现在包含了实际负载管理器模块的所有逻辑(模块的名称是类名中包含“manager”的唯一原因)。创建此类是一种尝试将这个模块的整个逻辑删除为一个位置。它大约有 1400 行代码,所以不是庞然大物,其中很多是参数验证等。所有数据访问代码都由数据层处理。根据我的阅读,这应该是执行 TDD 的最佳方式,但我可能又弄错了。
      • 就我个人而言,我认为 1400 行是一个非常大的类,可能需要将其重构为更小的尺寸,尽管不是病态的巨大。更重要的不仅仅是大小,类的描述听起来像是有许多职责,违反了“单一职责原则”,成为了神类。无论如何,当我听到“这个类太复杂,需要测试的依赖项太多”时,我的第一反应总是“把它分解成更小、更简单的类”。控制反转模式可能会有所帮助。
      【解决方案4】:

      我从未使用过 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); 
      

      我认为您应该可以使用上面的第二个示例。

      【讨论】:

      • 感谢您的回复。我尝试了多种不同的方法,但最终这就是我发现的(在备注部分):clariusconsulting.net/labs/moq/html/3BFDD309.htm
      • @josh - 哇,该链接具有误导性。我将就此向作者发送反馈。
      • @josh - 我认为您正在查看的链接实际上是说“如果有两个期望匹配最后一个获胜”。在你的情况下,如果你使用 It.Is 匹配器,那么就不会有冲突。
      【解决方案5】:

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

      【讨论】:

        【解决方案6】:

        无论我向 jobDetailController.GetById(x) 发送什么,我都会返回 jobDetail3

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-09-03
          • 1970-01-01
          相关资源
          最近更新 更多