使用 Rhino Mocks 模拟阻塞调用

发布于 2024-10-03 08:08:36 字数 2303 浏览 1 评论 0原文

我目前正在使用 TDD 构建一个类。该类负责等待特定窗口变为活动状态,然后触发某些方法。

我正在使用 AutoIt COM 库(有关 AutoIt 的更多信息,请查看此处)我想要的行为实际上是 AutoIt 中的一个方法。

代码几乎如下所示:

public class WindowMonitor
{
    private readonly IAutoItX3 _autoItLib;

    public WindowMonitor(IAutoItX3 autoItLib)
    {
        _autoItLib = autoItLib;
    }


    public void Run() // indefinitely
    {
        while(true)
        {
            _autoItLib.WinWaitActive("Open File", "", 0);
            // Do stuff now that the window named "Open File" is finally active.
        }
    }
}

正如您所看到的,AutoIt COM 库实现了一个我可以模拟的接口(使用 NUnit 和 Rhino Mocks):

[TestFixture]
 public class When_running_the_monitor
 {
  WindowMonitor subject;
  IAutoItX3 mockAutoItLibrary;
  AutoResetEvent continueWinWaitActive;
  AutoResetEvent winWaitActiveIsCalled;


 [SetUp]
 public void Setup()
  {
   // Arrange
   mockAutoItLibrary = MockRepository.GenerateStub<IAutoItX3>();
   mockAutoItLib.Stub(m => m.WinWaitActive("", "", 0))
                .IgnoreArguments()
                .Do((Func<string, string, int, int>) ((a, b, c) =>
                {
                    winWaitActiveIsCalled.Set();
                    continueWinWaitActive.WaitOne();
                    return 1;
                }));

   subject = new Subject(mockAutoItLibrary)

   // Act
   new Thread(new ThreadStart(subject.Run)).Start();
   winWaitActiveIsCalled.WaitOne();
  }

  // Assert

    [Test]
    [Timeout(1000)]
    public void should_call_winWaitActive()
    {
        mockAutoItLib.AssertWasCalled(m => m.WinWaitActive("Bestand selecteren", "", 0));
    }

    [Test]
    [Timeout(1000)]
    public void ensure_that_nothing_is_done_while_window_is_not_active_yet()
    {
        // When you do an "AssertWasCalled" for the actions when the window becomes active, put an equivalent "AssertWasNotCalled" here.

    }

}

问题是,第一个测试总是超时。我已经发现,当调用存根“WinWaitActive”时,它会阻塞(按预期,在单独的线程上),并且当此后调用“AssertWasCalled”时,执行永远不会返回。

我不知道如何继续,并且找不到任何模拟阻塞调用的示例。

总之:

有没有办法在不使测试超时的情况下模拟阻塞调用?

(PS,我对改变设计不太感兴趣(即“不要使用阻塞调用”),因为在这里可能可以做到这一点,但我确信在某些情况下改变设计要困难得多,并且我对更通用的解决方案感兴趣,但是如果根本不可能模拟阻塞调用,那么更欢迎这样的建议!)

I'm currently building a class using TDD. The class is responsible for waiting for a specific window to become active, and then firing some method.

I'm using the AutoIt COM library (for more information about AutoIt look here) since the behavior I want is actually a single method in AutoIt.

The code is pretty much as the following:

public class WindowMonitor
{
    private readonly IAutoItX3 _autoItLib;

    public WindowMonitor(IAutoItX3 autoItLib)
    {
        _autoItLib = autoItLib;
    }


    public void Run() // indefinitely
    {
        while(true)
        {
            _autoItLib.WinWaitActive("Open File", "", 0);
            // Do stuff now that the window named "Open File" is finally active.
        }
    }
}

As you can see the AutoIt COM library implements an interface wich I can mock (Using NUnit and Rhino Mocks):

[TestFixture]
 public class When_running_the_monitor
 {
  WindowMonitor subject;
  IAutoItX3 mockAutoItLibrary;
  AutoResetEvent continueWinWaitActive;
  AutoResetEvent winWaitActiveIsCalled;


 [SetUp]
 public void Setup()
  {
   // Arrange
   mockAutoItLibrary = MockRepository.GenerateStub<IAutoItX3>();
   mockAutoItLib.Stub(m => m.WinWaitActive("", "", 0))
                .IgnoreArguments()
                .Do((Func<string, string, int, int>) ((a, b, c) =>
                {
                    winWaitActiveIsCalled.Set();
                    continueWinWaitActive.WaitOne();
                    return 1;
                }));

   subject = new Subject(mockAutoItLibrary)

   // Act
   new Thread(new ThreadStart(subject.Run)).Start();
   winWaitActiveIsCalled.WaitOne();
  }

  // Assert

    [Test]
    [Timeout(1000)]
    public void should_call_winWaitActive()
    {
        mockAutoItLib.AssertWasCalled(m => m.WinWaitActive("Bestand selecteren", "", 0));
    }

    [Test]
    [Timeout(1000)]
    public void ensure_that_nothing_is_done_while_window_is_not_active_yet()
    {
        // When you do an "AssertWasCalled" for the actions when the window becomes active, put an equivalent "AssertWasNotCalled" here.

    }

}

The problem is, the first test keeps timing out. I have already found out that when the stub "WinWaitActive" is called, it blocks (as intended, on the seperate thread), and when the "AssertWasCalled" is called after that, execution never returns.

I'm at a loss how to proceed, and I couldn't find any examples of mocking out a blocking call.

So in conclusion:

Is there a way to mock a blocking call without making the tests timeout?

(P.S. I'm less interested in changing the design (i.e. "Don't use a blocking call") since it may be possible to do that here, but I'm sure there are cases where it's a lot harder to change the design, and I'm interested in the more general solution. But if it's simply impossible to mock blocking calls, suggestions like that are more that welcome!)

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

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

发布评论

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

评论(2

×纯※雪 2024-10-10 08:08:37

不确定我是否理解这个问题。

您的代码只是调用模拟上的方法 (WinWaitActive)。当然,在呼叫返回之前它无法继续。这是编程语言的本质,您无需测试任何内容。

因此,如果您测试 WinWaitActive 被调用,则测试已完成。您可以测试 WinWaitActive 是否在其他任何事情之前被调用,但这需要有序的期望,这需要旧式的 rhino 模拟语法,并且通常不值得这样做。

   mockAutoItLibrary = MockRepository.GenerateStub<IAutoItX3>();

   subject = new Subject(mockAutoItLibrary)
   subject.Run()

   mockAutoItLib.AssertWasCalled(m => m.WinWaitActive("Open File", "", 0));

除了调用方法之外,您无需执行任何其他操作...因此无需测试其他任何内容。

编辑:退出无限循环

您可以通过从模拟中抛出异常来使其退出无限循环。这不是很好,但它避免了在单元测试中使用所有这些多线程的东西。

   mockAutoItLibrary = MockRepository.GenerateStub<IAutoItX3>();

   // make loop throw an exception on second call
   // to exit the infinite loop
   mockAutoItLib
     .Stub(m => m.WinWaitActive(
       Arg<string>.Is.Anything, 
       Arg<string>.Is.Anything, 
       Arg<int>.Is.Anything));
     .Repeat.Once();

   mockAutoItLib
     .Stub(m => m.WinWaitActive(
       Arg<string>.Is.Anything, 
       Arg<string>.Is.Anything, 
       Arg<int>.Is.Anything));
     .Throw(new StopInfiniteLoopException());

   subject = new Subject(mockAutoItLibrary)
   try
   {
     subject.Run()
   }
   catch(StopInfiniteLoopException)
   {} // expected exception thrown by mock

   mockAutoItLib.AssertWasCalled(m => m.WinWaitActive("Open File", "", 0));

Not sure if I understand the problem.

Your code is just calling a method on the mock (WinWaitActive). Of course, it can't proceed before the call returns. This is in the nature of the programming language and nothing you need to test.

So if you test that WinWaitActive gets called, your test is done. You could test if WinWaitActive gets called before anything else, but this requires ordered expectations, which requires the old style rhino mocks syntax and is usually not worth to do.

   mockAutoItLibrary = MockRepository.GenerateStub<IAutoItX3>();

   subject = new Subject(mockAutoItLibrary)
   subject.Run()

   mockAutoItLib.AssertWasCalled(m => m.WinWaitActive("Open File", "", 0));

You don't do anything else then calling a method ... so there isn't anything else to test.

Edit: exit the infinite loop

You could make it exit the infinite loop by throwing an exception from the mocks. This is not very nice, but it avoids having all this multi-threading stuff in the unit test.

   mockAutoItLibrary = MockRepository.GenerateStub<IAutoItX3>();

   // make loop throw an exception on second call
   // to exit the infinite loop
   mockAutoItLib
     .Stub(m => m.WinWaitActive(
       Arg<string>.Is.Anything, 
       Arg<string>.Is.Anything, 
       Arg<int>.Is.Anything));
     .Repeat.Once();

   mockAutoItLib
     .Stub(m => m.WinWaitActive(
       Arg<string>.Is.Anything, 
       Arg<string>.Is.Anything, 
       Arg<int>.Is.Anything));
     .Throw(new StopInfiniteLoopException());

   subject = new Subject(mockAutoItLibrary)
   try
   {
     subject.Run()
   }
   catch(StopInfiniteLoopException)
   {} // expected exception thrown by mock

   mockAutoItLib.AssertWasCalled(m => m.WinWaitActive("Open File", "", 0));
风为裳 2024-10-10 08:08:37

您的测试仅包含对模拟方法的调用。因此它只测试你的模拟而不是任何真实的代码,这是一件奇怪的事情。我们可能需要更多背景信息来理解这个问题。

使用 Thread.Sleep() 代替 AutoResetEvent:由于您正在模拟执行阻塞窗口活动检查的 COM 对象,因此您可以等待一段时间来模仿行为,然后通过以编程方式使其处于活动状态来确保窗口确实处于活动状态。在测试中,如何阻止并不重要,重要的是你阻止了一段重要的时间。

尽管从您的代码中不清楚 winWaitActiveIsCancelled 和 continueWinWaitActive 如何发挥作用,但我怀疑它们应该被排除在 WinWaitActive 模拟之外。将它们替换为 Thread.Sleep(500)。

Your Test only contains a call to the mocked method. Therefore it tests only your mock instead of any real code, which is an odd thing to do. We might need a bit more context to understand the problem.

Use Thread.Sleep() instead of AutoResetEvents: Since you are mocking the COM object that does the blocking window-active check, you can just wait for some time to mimick the behavior, and then make sure that the window is indeed active by making it active programmatically. How you block should not be important in the test, only that you block for some significant time.

Although from your code it is not clear how winWaitActiveIsCancelled and continueWinWaitActive contribute, I suspect they should be left out of the WinWaitActive mock. Replace them with a Thread.Sleep(500).

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