2013-12-20 4 views
5

, что касается моего понимания, части тестового кода записи (unit-), конструктор не должен выполнять реальную работу в конструкторе и только назначать поля. До сих пор это работало очень хорошо. Но я столкнулся с проблемой, и я не уверен, что это лучший способ ее решить. См. Пример кода ниже.Тестируемый код: Прикрепить обработчик событий в конструкторе

class SomeClass 
{ 
    private IClassWithEvent classWithEvent; 

    public SomeClass(IClassWithEvent classWithEvent) 
    { 
     this.classWithEvent = classWithEvent; 

     // (1) attach event handler in ctor. 
     this.classWithEvent.Event += OnEvent; 
    } 

    public void ActivateEventHandling() 
    { 
     // (2) attach event handler in method 
     this.classWithEvent.Event += OnEvent; 
    } 

    private void OnEvent(object sender, EventArgs args) 
    { 
    } 
} 

Для меня вариант (1) звучит нормально, но конструктору следует назначать поля. Вариант (2) чувствует себя немного «слишком много».

Любая помощь приветствуется.

+0

Думаю, я был в спешке, чтобы ответить на этот вопрос :) (если вы не видите мой ответ, потому что он удален) – Abhinav

+0

lol ditto. нужно больше кофе. –

ответ

2

Проводные события : назначение поля (поскольку делегаты - это не что иное, как простые ссылочные переменные, указывающие на методы).

Таким образом, вариант (1) в порядке.

+0

, но это также задание поля в другом объекте, поэтому здесь действительно не применяется – catbert

+0

@catbert Почему бы и нет? Не имеет значения, где находится назначенное поле, пока назначение служит единственной цели для правильной инициализации экземпляра. –

4

Единичный тест проверит SomeClass максимум. Поэтому вы обычно издеваетесь classWithEvent. Использование какой-то инъекции для classWithEvent в ctor в порядке. Как Томас Уэллер сказал, что проводка - это назначение поля.

Вариант 2 на самом деле плохо ИМХО. Как будто вы опускаете вызов ActivateEventHandling, вы получаете неправильный инициализированный класс и должны передавать знания о требовании для вызова ActivateEventHandling в комментариях или как-то еще, что делает класс более сложным в использовании и, вероятно, приводит к использованию класса, которое было даже не проверены вами, так как вы вызвали ActivateEventHandling и протестировали его, но не проинформированный пользователь, не указав активацию, не сделал этого, и вы, конечно же, не тестировали свой класс, когда ActivateEventHandling не был вызван, не так ли? :)

Edit: Там могут быть альтернативные подходы здесь, которые стоит упомянуть его

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

Проводка может рассматриваться как знание контекста. single responsibility principle говорит, что класс должен выполнять только одну задачу. Кроме того, класс можно использовать более универсальным, если он не имеет зависимости от чего-то другого. Очень слабосвязанная система обеспечит много классов, имеющих события и обработчики, и не знает других классов.

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

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

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

Ralf Westphal (один из основателей clean code developer initiative (sorry german only)) написал программное обеспечение, которое автоматически выполняет проводку в концепции под названием «компоненты, основанные на событиях» (не обязательно придумано самим собой). Он использует соглашения об именах и соответствие подписи с отражением для объединения событий и обработчиков вместе.

+0

Как раз для записи, я удалил свой ответ, так как он не будет работать с использованием Moq - он был вне вершины головы и => не был подходящим ответом. Однако * * можно проверить с помощью RhinoMocks, к которому привязан обработчик событий. –

1

Точка конструктора не должна «назначать поля». Это установить инварианты вашего объекта, т.е. е. что никогда не меняется в течение его жизни.

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

С другой стороны, если подписки приходят и уходят (возможно, здесь не так), вы можете переместить этот код на другой метод.

+0

Я никогда не думал о том, что задача конструктора заключается в создании инвариантов. И это имеет смысл! Вы также можете называть это _setup зависимостями объекта_: В моем случае зависимость - это событие 'classWithEvent.Event' (а не сам объект' classWithEvent'). Но так как вы не можете передать событие непосредственно в качестве параметра ctor, вариант (1) - это путь. btw: Я не отмечаю ответ как мой принятый ответ, но я хочу ждать других комментариев и, может быть, я передумаю :) – Johannes

0

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

Тогда какой-то другой класс (загрузчик, конфигуратор и т. Д.) Должен нести ответственность за проводку. Ваш класс должен нести ответственность только за то, что происходит, когда новые данные поступают годов в

Псевдо код:.

public interface IEventProvider //your IClassWithEvent 
{ 
    Event MyEvent... 
} 

public class EventResponder : IEventResponder 
{ 
    public void OnEvent(object sender, EventArgs args){...} 
} 

public class Boostrapper 
{ 
    public void WireEvent(IEventProvider eventProvider, IEventResponder eventResponder) 
    { 
     eventProvider>event += eventResponder.OnEvent; 
    } 
} 

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

Как реализуется ваш загрузчик, зависит от многих факторов. Это может быть ваш «основной» метод, или ваш global.asax, или все, что у вас есть, чтобы фактически настроить и подготовить ваше приложение.

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

+0

Не могли бы вы привести простой пример кода. Тогда более ясно, как это может выглядеть. Сторона примечания: имена более общие, и я полностью согласен с тем, что они не приемлемы в производственном коде. – Johannes

+0

Каковы требования? Существует ли жесткое требование, согласно которому ваш класс должен быть подписан только на события ClassWithEvents? Или он будет работать независимо от того, что поднимает событие?Отвечает ли ваш класс «знать», кто отправит мероприятие? –

+0

В этом конкретном случае существует класс, предоставляющий событие (событие C#, а не нечто абстрактное). И есть еще один класс, который должен реагировать на это событие. ClassWithEvent реализует интерфейс, поэтому это не конкретная реализация. Прямо сейчас, только IClassWithEvent может поднять это специальное событие. Надеюсь, я ответил на ваш вопрос. – Johannes

Смежные вопросы