Thread.Sleep () - определенно плохая идея. Я несколько раз читал на SO: «Настоящие приложения не спят». Примите это как хотите, но я согласен с этим утверждением. Особенно во время юнит-тестов. Если ваш тестовый код создает ложные сбои, ваши тесты хрупкие.
Недавно я написал несколько тестов, которые должным образом ожидают завершения параллельных задач, и подумал, что поделюсь своим решением. Я понимаю, что это старый пост, но я подумал, что он будет полезен тем, кто ищет решение.
Моя реализация включает в себя изменение тестируемого класса и тестируемого метода.
class Bar : IBar
{
private IEnumerable<IFoo> Foos { get; set; }
internal CountdownEvent FooCountdown;
public Bar(IEnumerable<IFoo> foos)
{
Foos = foos;
}
public void Start()
{
FooCountdown = new CountdownEvent(foo.Count);
foreach(var foo in Foos)
{
Task.Factory.StartNew(() =>
{
foo.Start();
// once a worker method completes, we signal the countdown
FooCountdown.Signal();
});
}
}
}
Объекты CountdownEvent удобны, когда у вас есть несколько параллельных задач, выполняющихся, и вам нужно ждать завершения (например, когда мы ждем попытки подтверждения в модульных тестах). Конструктор инициализируется числом раз, которое должно быть передано, прежде чем он сообщит ожидающему коду, что обработка завершена.
Причина, по которой внутренний модификатор доступа используется для CountdownEvent, заключается в том, что я обычно устанавливаю свойства и методы как внутренние, когда модульным тестам необходим доступ к ним. Затем я добавляю новый атрибут сборки в проверяемую сборку Properties\AssemblyInfo.cs
, чтобы внутренние компоненты были доступны для тестового проекта.
[assembly: InternalsVisibleTo("FooNamespace.UnitTests")]
В этом примере FooCountdown будет ждать 3 сигнала, если в Foos есть 3 объекта foo.
Теперь вы ждете, пока FooCountdown завершит обработку сигнала, чтобы вы могли продолжить свою жизнь и прекратить тратить впустую циклы процессора на Thread.Sleep ().
[Test]
public void StartBar_ShouldCallStartOnAllFoo_WhenFoosExist()
{
//ARRANGE
var mockFoo0 = new Mock<IFoo>();
mockFoo0.Setup(foo => foo.Start());
var mockFoo1 = new Mock<IFoo>();
mockFoo1.Setup(foo => foo.Start());
//Add mockobjects to a collection
var foos = new List<IFoo> { mockFoo0.Object, mockFoo1.Object };
IBar sutBar = new Bar(foos);
//ACT
sutBar.Start(); //Should call mockFoo.Start()
sutBar.FooCountdown.Wait(); // this blocks until all parallel tasks in sutBar complete
//ASSERT
mockFoo0.VerifyAll();
mockFoo1.VerifyAll();
}