Асинхронная функция тестирования модулей
В следующем примере кода у меня есть класс Async Calculator. Это вводится ICalc, который будет синхронным калькулятором. Я использую инъекцию зависимостей и издеваюсь над ICalc, потому что это похоже на мой истинный сценарий, хотя я думаю, что издевательство действительно не имеет отношения к вопросу. AsyncCalc имеет функцию, которая будет асинхронно вызывать другую функцию - выбор параметра обратного вызова в качестве параметра. И когда вызов функции асинхронной связи завершит обратный вызов, будет вызван результат.
Теперь я хочу проверить свою асинхронную функцию - проверку того, что обратный вызов запускается с ожидаемым параметром. Этот код, похоже, работает. Тем не менее, я чувствую, что он может взорваться в любое время - и моя озабоченность связана с состоянием гонки в обратном вызове до завершения функции и завершения теста - поскольку это будет выполняться в отдельном потоке.
Теперь мой вопрос, если я нахожусь на правильном пути, проверяя функцию async, или кто-нибудь может помочь мне встать на правильный путь..? Что было бы лучше, если бы я мог убедиться, что обратный вызов запускается сразу - и, желательно, по той же теме, я думаю? Может/Должно ли это быть сделано?
public interface ICalc
{
int AddNumbers(int a, int b);
}
public class AsyncCalc
{
private readonly ICalc _calc;
public delegate void ResultProcessor(int result);
public delegate int AddNumbersAsyncCaller(int a, int b);
public AsyncCalc(ICalc calc)
{
_calc = calc;
}
public void AddNumbers(int a, int b, ResultProcessor resultProcessor)
{
var caller = new AddNumbersAsyncCaller(_calc.AddNumbers);
caller.BeginInvoke(a, b, new AsyncCallback(AddNumbersCallbackMethod), resultProcessor);
}
public void AddNumbersCallbackMethod(IAsyncResult ar)
{
var result = (AsyncResult)ar;
var caller = (AddNumbersAsyncCaller)result.AsyncDelegate;
var resultFromAdd = caller.EndInvoke(ar);
var resultProcessor = ar.AsyncState as ResultProcessor;
if (resultProcessor == null) return;
resultProcessor(resultFromAdd);
}
}
[Test]
public void TestingAsyncCalc()
{
var mocks = new MockRepository();
var fakeCalc = mocks.DynamicMock<ICalc>();
using (mocks.Record())
{
fakeCalc.AddNumbers(1, 2);
LastCall.Return(3);
}
var asyncCalc = new AsyncCalc(fakeCalc);
asyncCalc.AddNumbers(1, 2, TestResultProcessor);
}
public void TestResultProcessor(int result)
{
Assert.AreEqual(3, result);
}
Ответы
Ответ 1
Вы можете использовать ManualResetEvent для синхронизации ваших потоков.
В следующем примере тестовый поток будет блокировать вызов completion.WaitOne()
.
Обратный вызов для вычисления async сохраняет результат, а затем сигнализирует событие, вызывая completion.Set()
.
[Test]
public void TestingAsyncCalc()
{
var mocks = new MockRepository();
var fakeCalc = mocks.DynamicMock<ICalc>();
using (mocks.Record())
{
fakeCalc.AddNumbers(1, 2);
LastCall.Return(3);
}
var asyncCalc = new AsyncCalc(fakeCalc);
var completion = new ManualResetEvent(false);
int result = 0;
asyncCalc.AddNumbers(1, 2, r => { result = r; completion.Set(); });
completion.WaitOne();
Assert.AreEqual(3, calcResult);
}
// ** USING AN ANONYMOUS METHOD INSTEAD
// public void TestResultProcessor(int result)
// {
// Assert.AreEqual(3, result);
// }
Ответ 2
Вы также можете использовать класс "test runner" для запуска утверждений в цикле. Цикл будет запускать утверждения в try/catch. Обработчик исключений попробовал бы снова запускать утверждения до истечения тайм-аута. Я недавно написал сообщение в блоге об этой технике. Пример приведен в groovy, но применим к любому языку. Вместо того, чтобы проходить Закрытие, вы должны передать действие в С#.
http://www.greenmoonsoftware.com/2013/08/asynchronous-functional-testing/