Издевательство над блокирующим вызовом с помощью Rhino Mocks

В настоящее время я создаю класс с использованием 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 Меня меньше интересует изменение дизайна (например, «Не использовать блокирующий вызов»), так как это может быть возможно здесь, но я уверен, что есть случаи, когда намного сложнее изменить дизайн , и меня интересует более общее решение. Но если имитировать блокирующие вызовы просто невозможно, подобные предложения приветствуются!)


person dvdvorle    schedule 18.11.2010    source источник
comment
Что вы на самом деле хотите протестировать? Зачем нужно блокировать макет? Почему в тесте нужна многопоточность?   -  person Stefan Steinegger    schedule 18.11.2010


Ответы (2)


Не уверен, что понимаю проблему.

Ваш код просто вызывает метод на макете (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));
person Stefan Steinegger    schedule 18.11.2010
comment
Причина, по которой я хочу, чтобы вызов макета блокировался, заключается в том, что он находится внутри цикла. Если я не заблокирую его, он будет продолжать повторять цикл между каждым оператором в методе Run снова и снова. Это предполагаемое поведение для реального приложения, но оно мешает в модульных тестах. - person dvdvorle; 18.11.2010
comment
Теперь я понимаю проблему. Я добавил раздел к своему ответу. - person Stefan Steinegger; 19.11.2010
comment
Отличный ответ, мне все равно не нравилась многопоточность. Это было бы невозможно, если бы в методе Run () происходил перехват исключения. Но поскольку здесь это не так, это именно то, что мне нужно. Спасибо! - person dvdvorle; 19.11.2010

Ваш тест содержит только вызов имитируемого метода. Поэтому он проверяет только ваш макет, а не реальный код, что довольно странно. Нам может потребоваться немного больше контекста, чтобы понять проблему.

Используйте Thread.Sleep() вместо AutoResetEvents: поскольку вы имитируете COM-объект, который выполняет проверку активности блокирующего окна, вы можете просто подождать некоторое время, чтобы имитировать поведение, а затем убедиться, что окно действительно активно, сделав его активным программно. То, как вы блокируете, не должно иметь значения в тесте, только , что вы блокируете на какое-то значительное время.

Хотя из вашего кода неясно, какой вклад вносят winWaitActiveIsCancelled и continueWinWaitActive, я подозреваю, что их следует исключить из макета WinWaitActive. Замените их на Thread.Sleep(500).

person Joseph Tanenbaum    schedule 18.11.2010
comment
Я использую AutoResetEvents для синхронизации потоков. Предполагаемое поведение состоит в том, что вызов winWaitActiveCalled.WaitOne () в SetUp гарантирует, что выполнение в потоке выполнения происходит при вызове winWaitActive. - person dvdvorle; 18.11.2010