Подтвердить что ты не робот

Асинхронная функция тестирования модулей

В следующем примере кода у меня есть класс 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);
}
4b9b3361

Ответ 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/