Как выполнить модульное тестирование кода с использованием SynchronizationContext? - PullRequest
4 голосов
/ 02 декабря 2011

У меня есть следующий код, который я пытаюсь проверить, используя макеты NUnit и Rhino.

void _tracker_IndexChanged(object sender, IndexTrackerChangedEventArgs e)
{
    //  _context is initialised as 
    //  SynchronizationContext _context = SynchronizationContext.Current;
    // _tracker.Index is stubbed to return the value 100
    _context.Post(o => _view.SetTrackbarValue(_tracker.Index), null);
}

В тестовом примере я установил ожидание как

_view.Expect(v => v.SetTrackbarValue(100));

и когда я проверяю ожидание, модульный тест завершается неудачно с сообщением

Test(s) failed. Rhino.Mocks.Exceptions.ExpectationViolationException :
IIndexTrackerView.SetTrackbarValue(100); Expected #1, Actual #0.

Я не смог определить проблему здесь, Как я могу это исправить?

Ответы [ 2 ]

6 голосов
/ 02 декабря 2011

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

Это позволяет мне смоделировать глобальное поведение и делает так, чтобы мои тесты независеть или проявлять это несвязанное поведение.

Вот один из способов, которым вы могли бы сделать это.

public interface IContext
{
    void Post(SendOrPostCallback d, Object state);
}

public class SynchronizationContextAdapter : IContext
{
    private SynchronizationContext _context;

    public SynchronizationContextAdapter(SynchronizationContext context)
    {
        _context = context;
    }

    public virtual void Post(SendOrPostCallback d, Object state)
    {
        _context.Post(d, state);
    }
}

public class SomeClass
{
    public SomeClass(IContext context)
    {
        _context = context;
    }

    void _tracker_IndexChanged(object sender, IndexTrackerChangedEventArgs e)
    {
        _context.Post(o => _view.SetTrackbarValue(_tracker.Index), null);
    }
    // ...
}

Тогда вы можете высмеять или заглушить IContext, чтобы вам не пришлось беспокоиться омногопоточность, и может использовать простую реализацию, которая просто выполняет делегат.

Если бы я написал модульные тесты, чтобы смоделировать это, я бы также написал «интеграционные» тесты более высокого уровня, которые не делали это, нобыло меньше гранулярных проверок.

1 голос
/ 02 декабря 2011

Я некоторое время не использовал Rhino Mocks, поэтому не могу вспомнить точный синтаксис, но если рефакторинг вещей, как предлагает Мерлин, не вариант, то другим решением было бы использовать ManualResetEvent, чтобы дождаться, пока ваш макет не будет активирован. на.

Примерно так:

[Test]
public void ATest(){
  ManualResetEvent completed = new ManualResetEvent(false);

  _view.Expect(v => v.SetTrackbarValue(100)).Do(() => completed.Set());
  //Stuff done here...   
  Assert.IsTrue(completed.WaitOne(1000), "Waited a second for a call that never arrived!");

}

Таким образом, вы можете подождать, пока другой поток не вызовет событие, после чего вы можете продолжить. Убедитесь, что есть разумное время ожидания, поэтому вы не должны ждать вечно!

...