需要 TDD 方法的想法

发布于 2024-08-13 16:56:08 字数 1018 浏览 3 评论 0原文

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

负载管理器的核心位于一个名为 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:我本质上是在寻找本质上非常复杂的面向数据的代码的测试策略。

We have just released a re-written(for the 3rd time) module for our proprietary system. This module, which we call the Load Manager, is by far the most complicated of all the modules in our system to date. We are trying to get a comprehensive test suite because every time we make any kind of significant change to this module there is hell to pay for weeks in sorting out bugs and quirks. However, developing a test suite has proven to be quite difficult so we are looking for ideas.

The Load Manager's guts reside in a class called LoadManagerHandler, this is essentially all of the logic behind the module. This handler calls upon multiple controllers to do the CRUD methods in the database. These controllers are essentially the top layer of the DAL that sits on top and abstracts away our LLBLGen generated code.

So it is easy enough to mock these controllers, which we are doing using the Moq framework. However the problem comes in the complexity of the Load Manager and the issues that we receive aren't in dealing with the simple cases but the cases where there is a substantial amount of data contained within the handler.

To briefly explain the load manager contains a number of "unloaded" details, sometimes in the hundreds, that are then dropped into user created loads and reship pools. During the process of creating and populating these loads there is a multitude of deletes, changes, and additions that eventually cause issues to appear. However, because when you mock a method of an object the last mock wins, ie:

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

No matter what I send to jobDetailController.GetById(x) I will always get back jobDetail3. This makes testing almost impossible because we have to make sure that when changes are made all points are affected that should be affected.

So, I resolved to using the test database and just allowing the reads and writes to occur as normal. However, because you can't(read: should not) dictate the order of your tests, tests that are run earlier could cause tests that run later to fail.

TL/DR: I am essentially looking for testing strategies for data oriented code that is quite complex in nature.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

無處可尋 2024-08-20 16:56:08

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

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

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

As noted by Seb, you can indeed use a range matching:

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

This code uses the argument passed to the method to calculate which value to return.

独夜无伴 2024-08-20 16:56:08

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

Moq Triqs - 连续期望

编辑:

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

<代码>
公共接口IController
{
字符串 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

To get around the "last mock wins" with Moq, you could use the technique from this blog:

Moq Triqs - Successive Expectations

EDIT:

Actually you don't even need that. Based on your example, Moq will return different values based on the method argument.


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

Output is:

   one
   three
   two
   three
   True
情定在深秋 2024-08-20 16:56:08

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

这节课要多长时间?

It sounds like LoaderManagerHandler does... quite a bit of work. "Manager" in a class name always somewhat worries me... from a TDD standpoint, it might be worth thinking about breaking the class up appropriately if possible.

How long is this class?

病毒体 2024-08-20 16:56:08

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

快速浏览一下快速入门文档,其中摘录如下:

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

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

I've never used Moq, but it seems that it should be able to match a mock invocation by argument(s) supplied.

A quick look at the Quick Start documentation has the following excerpt:

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

I think you should be able to use the second example above.

雨后彩虹 2024-08-20 16:56:08

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

A simple testing technique is to make sure everytime a bug is logged against a system, make sure a unit test is written covering that case. You can build up a pretty solid set of tests just from that technique. And even better you won't run into the same thing twice.

怪异←思 2024-08-20 16:56:08

<块引用>

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

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

如果您想使用相同的输入但不同的输出进行重复调用,您也可以使用不同的模拟框架。 RhinoMocks 支持记录/回放习惯用法。你是对的,这并不总是你想要的关于执行呼叫命令的结果。我自己更喜欢 Moq,因为它简单。

No matter what I send to jobDetailController.GetById(x) I will always get back jobDetail3

You should spend more time debugging your tests because what is happening is not how Moq behaves. There is a bug in your code or tests causing something to misbehave.

If you want to make repeated calls with the same inputs but different outputs you could also use a different mocking framework. RhinoMocks supports the record/playback idiom. You're right this is not always what you want with regards to enforcing call order. I do prefer Moq myself for its simplicity.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文