Тестирование событий от объектов - PullRequest
2 голосов
/ 02 декабря 2008

Я пытался получить больше в TDD. В настоящее время все довольно просто: Debug.Asserts в консольном приложении.

Часть тестов, которые я хотел завершить, заключалась в том, чтобы убедиться, что события были вызваны объектом, правильное количество раз, поскольку клиентский код будет зависеть от этих событий.

Итак, я начал думать о том, как проверить происходящие события и как их отследить. Итак, я придумал Monitor «шаблон» (если можно так его назвать). В основном это класс, который принимает объект тестируемого типа в своем конструкторе.

Затем события подключаются к монитору с созданными делегатами, которые учитываются и регистрируются при возникновении события.

Затем я возвращаюсь к своему тестовому коду и делаю что-то вроде:

    bool Test()
    {
        MyObject mo = new MyObject();
        MyMonitor mon = new MyMonitor(mo);

        // Do some tests that should cause the object to raise events..

        return mon.EventCount == expectedCount;
    }

Это работает нормально, и когда я намеренно перевернул свой код, тесты не прошли должным образом, но Интересно, это слишком много тестового кода "свободной формы" (т. Е. Кода без поддержки тестов)?


Дополнительные мысли

  • Тестируете ли вы на события?
  • Как вы тестируете на события?
  • Как вы думаете, в вышеприведенном есть какие-то дыры / возможности для улучшения?

Весь вклад с благодарностью получен! ^_^

Ответы [ 7 ]

4 голосов
/ 02 декабря 2008

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

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

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

MyObject subjectUnderTest = new MyObject();
EventMonitor monitor = new Monitor();
subjectUnderTest.Event += monitor.EventCatcher;

// testcode;

Assert.Equal( 1, monitor.EventsFired );

Проблема в том, что это не совсем универсально. Вы можете тестировать только те события, на которые monitor.EventCatcher () можно подписаться. Обычно я не делаю события с аргументами, так что это не проблема, у меня просто стандартный void EventCatcher (отправитель объекта, аргументы EventArgs). Вы можете сделать это более универсальным, подписав лямбду правильного типа на событие и вызвав EventCatcher в лямбде. Это делает ваши тесты немного сложнее для чтения. Вы также можете использовать универсальные шаблоны, чтобы метод EventCatcher работал с универсальным EventHandler.

Возможно, вы захотите посмотреть, в конце концов вы захотите хранить точно, какие события были вызваны, в каком порядке и с какими параметрами. Ваш монитор событий может легко выйти из-под контроля.


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

Вместо того, чтобы создавать свой собственный монитор, вы позволяете создаваемой имитационной структуре создавать его для себя, вы просто создаете интерфейс для класса, который обрабатывает событие. Примерно так:

public interface IEventHandlerStub
{
    event EventHandler<T> Event(object sender, T arguments);
}

Тогда вы можете смоделировать этот интерфейс в своем тесте. Rhino Mocks делает это так:

var eventHandlerStub = MockRepository.GenerateStub<IEventHandlerStub>();
myObject.Event += eventHandlerStub.Event;

// Run your code

eventHandlerStub.AssertWasCalled(x => x.Event(null, null));

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

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

2 голосов
/ 22 апреля 2010

Недавно я написал серию постов в блоге о юнит-тестировании последовательностей событий для объектов, которые публикуют синхронные и асинхронные события. Посты описывают подход и структуру модульного тестирования и предоставляют полный исходный код с тестами.

Я описываю реализацию «монитора событий», схожего по концепции с тем, что вы описываете, и некоторые из предыдущих ответчиков на этот вопрос упоминали. Определенно в правильном направлении, так как написание большого количества тестов без какого-либо шаблона монитора приводит к большому количеству грязного стандартного кода.

Используя монитор событий, описанный в моей статье, тесты можно записать так:

AsyncEventPublisher publisher = new AsyncEventPublisher();

Action test = () =>
{
    publisher.RaiseA();
    publisher.RaiseB();
    publisher.RaiseC();
};

var expectedSequence = new[] { "EventA", "EventB", "EventC" };

EventMonitor.Assert(test, publisher, expectedSequence, TimeoutMS);

EventMonitor выполняет всю тяжелую работу и запускает тест (test) и утверждает, что события генерируются в ожидаемой последовательности (Ожидаемая последовательность). Он также выводит на экран хорошие диагностические сообщения о сбое теста. Что отличается от некоторых обсуждаемых подходов, так это то, что он не просто учитывается, он будет утверждать, что была соблюдена точная указанная последовательность. Также вам не нужно перехватывать какие-либо события. Это все обрабатывается монитором событий. Поэтому тесты довольно чистые.

В постах много подробностей, описывающих проблемы и подходы, а также исходный код:

http://gojisoft.com/blog/2010/04/22/event-sequence-unit-testing-part-1/

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

Я обычно проверяю события с помощью объекта Mock - поскольку я работаю в Java, я использую EasyMock (что-то подобное должно существовать для вашего языка):

FooListener listener = createMock(FooListener.class);
expect(listener.someEvent());
replay(listener);
myObject.addListener(listener);
myObject.doSomethingThatFiresEvent();
verify(listener);

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

Взгляните на эту статью на тему .

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

Одна дыра / комната для улучшения состоит в том, что ваш монитор учитывает только количество событий. В идеале можно было бы указать ожидания того, какие события следует вызывать, сколько раз, в каком порядке и, возможно, даже как должна выглядеть пара (отправитель объекта, EventArgs e) при возникновении каждого события.

0 голосов
/ 02 декабря 2008

В моих тестовых классах я просто подключаю обработчики событий к событиям объявленных объектов ... или я что-то здесь упускаю ??

0 голосов
/ 02 декабря 2008

Я делаю тестовое событие. То, как я это делаю, довольно просто.

private int counterEvent;

[Test]
public void abcTest()
{
    counterEvent = 0;
    //1- Initialize object to test
    //2- Set the event to test (abcTest_event for example)
    //Assert the object incremented (counterEvent for example)
}

private void abcTest_event(object sender)
{
    counterEvent++;
}
0 голосов
/ 02 декабря 2008

выглядит хорошо для меня - так как это работает; -)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...