TDD и DI: внедрения зависимости, становящиеся громоздкими

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

SELECT
    X.HHID,
    C1.FIRST_NAME AS I_FIRST_NAME, C1.LAST_NAME AS I_LAST_NAME,
    C2.FIRST_NAME AS II_FIRST_NAME, C2.LAST_NAME AS II_LAST_NAME
FROM
    ((  SELECT
            HHID, Min(CID) AS MinCId, IIf(Max(CID)=Min(CID), Null, Max(CID))  AS MaxCId
        FROM HouseHold_Client
        GROUP BY HHID
    ) X
    INNER JOIN Client AS C1
        ON X.MinCId = C1.CID)
    LEFT JOIN Client AS C2
        ON X.MaxCId = C2.CID;

Цель выражения IIf() - вывести максимальный идентификатор клиента только в том случае, если он отличается от минимального идентификатора клиента. Чтобы также вернуть запись, когда MaxCId равно Null, требуется LEFT JOIN на C2.

Я не присоединился к таблице HouseHold здесь, так как нам нужна только HHID из нее, которая также доступна в HouseHold_Client. Вы также можете присоединиться к нему, если вам нужны другие столбцы из него.


Подзапрос:

(  SELECT
        HHID, Min(CID) AS MinCId, IIf(Max(CID)=Min(CID), Null, Max(CID))  AS MaxCId
    FROM HouseHold_Client
    GROUP BY HHID
) X

Подзапросы должны быть заключены в круглые скобки и им должно быть присвоено имя (здесь X). X действует как обычная таблица, имеющая столбцы HHID, MinCId и MaxCId в основном запросе. Он сгруппирован по HHID. Т.е. он возвращает одну строку за HHID. Min(CID) возвращает наименьшее CID и Max(CID) наибольшее CID за HHID.

В случае, когда у вас есть 2 клиента на HHID, это означает, что Min и Max дадут этих 2 клиентов. Если у вас есть только 1 клиент, то и Min, и Max вернут одного и того же клиента. Если это так, то IIf вернет Null вместо Max(CID), чтобы избежать двойного возврата одного и того же клиента.

9
задан Chris 3 March 2009 в 15:22
поделиться

8 ответов

Используйте контейнер AutoMocking. Существует один записанный для RhinoMocks.

Предположите, что у Вас есть класс с большим количеством зависимостей, введенных через инжекцию конструктора. Вот то, на что это похоже для установки его с RhinoMocks, никаким контейнером AutoMocking:

private MockRepository _mocks;
private BroadcastListViewPresenter _presenter;
private IBroadcastListView _view;
private IAddNewBroadcastEventBroker _addNewBroadcastEventBroker;
private IBroadcastService _broadcastService;
private IChannelService _channelService;
private IDeviceService _deviceService;
private IDialogFactory _dialogFactory;
private IMessageBoxService _messageBoxService;
private ITouchScreenService _touchScreenService;
private IDeviceBroadcastFactory _deviceBroadcastFactory;
private IFileBroadcastFactory _fileBroadcastFactory;
private IBroadcastServiceCallback _broadcastServiceCallback;
private IChannelServiceCallback _channelServiceCallback;

[SetUp]
public void SetUp()
{
    _mocks = new MockRepository();
    _view = _mocks.DynamicMock<IBroadcastListView>();

    _addNewBroadcastEventBroker = _mocks.DynamicMock<IAddNewBroadcastEventBroker>();

    _broadcastService = _mocks.DynamicMock<IBroadcastService>();
    _channelService = _mocks.DynamicMock<IChannelService>();
    _deviceService = _mocks.DynamicMock<IDeviceService>();
    _dialogFactory = _mocks.DynamicMock<IDialogFactory>();
    _messageBoxService = _mocks.DynamicMock<IMessageBoxService>();
    _touchScreenService = _mocks.DynamicMock<ITouchScreenService>();
    _deviceBroadcastFactory = _mocks.DynamicMock<IDeviceBroadcastFactory>();
    _fileBroadcastFactory = _mocks.DynamicMock<IFileBroadcastFactory>();
    _broadcastServiceCallback = _mocks.DynamicMock<IBroadcastServiceCallback>();
    _channelServiceCallback = _mocks.DynamicMock<IChannelServiceCallback>();


    _presenter = new BroadcastListViewPresenter(
        _addNewBroadcastEventBroker,
        _broadcastService,
        _channelService,
        _deviceService,
        _dialogFactory,
        _messageBoxService,
        _touchScreenService,
        _deviceBroadcastFactory,
        _fileBroadcastFactory,
        _broadcastServiceCallback,
        _channelServiceCallback);

    _presenter.View = _view;
}

Теперь, вот то же самое с контейнером AutoMocking:

private MockRepository _mocks;
private AutoMockingContainer _container;
private BroadcastListViewPresenter _presenter;
private IBroadcastListView _view;

[SetUp]
public void SetUp()
{

    _mocks = new MockRepository();
    _container = new AutoMockingContainer(_mocks);
    _container.Initialize();

    _view = _mocks.DynamicMock<IBroadcastListView>();
    _presenter = _container.Create<BroadcastListViewPresenter>();
    _presenter.View = _view;

}

Легче, да?

Контейнер AutoMocking автоматически создает насмешки для каждой зависимости в конструкторе, и можно получить доступ к ним для тестирования как так:

using (_mocks.Record())
    {
      _container.Get<IChannelService>().Expect(cs => cs.ChannelIsBroadcasting(channel)).Return(false);
      _container.Get<IBroadcastService>().Expect(bs => bs.Start(8));
    }

Надежда, которая помогает. Я знаю, что моя жизнь тестирования была сделана намного легче с появлением контейнера AutoMocking.

7
ответ дан 4 December 2019 в 06:17
поделиться

Вы правы, что это может быть громоздким.

Сторонник насмешки методологии указал бы, что код написан неправильно к тому, чтобы быть с. Таким образом, Вы не должны создавать зависимые объекты в этом методе. Скорее инжекционный API должен иметь функции, которые создают соответствующие объекты.

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

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

5
ответ дан 4 December 2019 в 06:17
поделиться

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

Когда Вы следуете этим трем законам и прилежны о рефакторинге, затем Вы никогда не волнуете со "сложной функцией". Скорее Вы волнуете со многими, протестированными, простыми функциями.

Теперь, на Вашей точке. Если у Вас уже есть "сложная функция", и Вы хотите перенести тесты вокруг этого затем, Вы должны:

  1. Добавьте свои насмешки явно, вместо через DI. (например, что-то ужасное как 'тестовый' флаг и, 'если' оператор, который выбирает насмешки вместо реальных объектов).
  2. Запишите несколько тестов для покрытия основной операции компонента.
  3. Осуществите рефакторинг беспощадно, разбив сложную функцию во многие небольшие простые функции, при запущении починенных тестов максимально часто.
  4. Продвиньте 'тестовый' флаг максимально высоко. Как Вы осуществляете рефакторинг, передаете свои источники данных к небольшим простым функциям. Не позволяйте 'тесту' отметить, заражают любого, но самую верхнюю функцию.
  5. Перепишите тесты. Как Вы осуществляете рефакторинг, переписываете как можно больше тестов для вызывания простых небольших функций вместо большой функции верхнего уровня. Можно передать насмешки в простые функции от тестов.
  6. Избавьтесь от 'теста', отмечают и определяют, в каком количестве DI Вы действительно нуждаетесь. Так как у Вас есть тесты, записанные на более низких уровнях, которые могут вставить насмешки через areguments, Вы, вероятно, не должны дразнить много источников данных на верхнем уровне больше.

Если, в конце концов, это, DI является все еще громоздким, то думайте о введении отдельного объекта, который содержит ссылки на все Ваши источники данных. Всегда легче ввести одну вещь, а не многих.

16
ответ дан 4 December 2019 в 06:17
поделиться

У меня нет Вашего кода, но моя первая реакция состоит в том, что Ваш тест пытается сказать Вам, что Ваш объект имеет слишком много сотрудников. В случаях как это я всегда нахожу, что существует недостающая конструкция там, которая должна быть упакована в высокоуровневую структуру. Используя автонасмешку контейнер просто затыкает рот обратной связи, которую Вы получаете от своих тестов. См. http://www.mockobjects.com/2007/04/test-smell-bloated-constructor.html для более длительного обсуждения.

5
ответ дан 4 December 2019 в 06:17
поделиться

Конструктор DI не является единственным способом сделать DI. Так как Вы используете C#, если Ваш конструктор не делает никакой значительной работы, Вы могли бы использовать Свойство DI. Это упрощает вещи значительно с точки зрения конструкторов Вашего объекта за счет сложности в Вашей функции. Ваша функция должна проверить на ничтожность любых зависимых свойств и бросить InvalidOperation, если они являются пустыми, прежде чем это начнет работу.

1
ответ дан 4 December 2019 в 06:17
поделиться

Когда трудно протестировать что-то, это обычно - признак качества кода, что код не является тестируемым (упомянутый в этом подкасте, IIRC). Рекомендация состоит в том, чтобы осуществить рефакторинг код так, чтобы код было легко протестировать. Некоторой эвристикой для решения, как разделить код на классы, является SRP и OCP. Для более конкретных инструкций было бы необходимо видеть рассматриваемый код.

0
ответ дан 4 December 2019 в 06:17
поделиться

В этом контексте я обычно нахожу утверждения в духе «это указывает на то, что у вашего объекта слишком много зависимостей» или «у вашего объекта слишком много соавторов», чтобы быть довольно ложным заявлением. Конечно, контроллер MVC или форма будут вызывать множество различных сервисов и объектов для выполнения своих обязанностей; в конце концов, он находится на верхнем уровне приложения. Вы можете объединить некоторые из этих зависимостей в объекты более высокого уровня (скажем, ShippingMethodRepository и TransitTimeCalculator объединяются в ShippingRateFinder), но это только так, особенно для этих объектов верхнего уровня, ориентированных на представление. Это еще один объект для насмешки, но вы просто запутали фактические зависимости одним слоем косвенного обращения, а не удалили их.

Один кощунственный совет - сказать, что если вы зависимы, внедряете объект и создаете интерфейс для него, который вряд ли когда-либо изменится (вы действительно собираетесь добавить новый MessageBoxService при изменении кода? Действительно? ), то не беспокойтесь. Эта зависимость является частью ожидаемого поведения объекта, и вы должны просто протестировать их вместе, так как в этом тесте интеграции лежит реальная ценность для бизнеса.

Другой кощунственный совет - я обычно вижу небольшую полезность в модульном тестировании MVC. контроллеры или Windows Forms. Каждый раз, когда я вижу, как кто-то насмехается над HttpContext и проверяет, не был ли установлен cookie, я хочу кричать. Кому какое дело, если AccountController установит куки? Я не. Cookie не имеет ничего общего с обработкой контроллера как черного ящика; Интеграционный тест - это то, что необходимо для тестирования его функциональности (хм, вызов функции PrivilegedArea () завершился неудачно после Login () в интеграционном тесте). Таким образом, вы избежите аннулирования миллиона бесполезных модульных тестов, если формат cookie для входа в систему когда-либо изменится.

Сохраните модульные тесты для объектной модели, сохраните интеграционные тесты для уровня представления и по возможности избегайте фиктивных объектов. Если издеваться над конкретной зависимостью сложно, пришло время проявить прагматичность: просто не делайте модульный тест, вместо этого напишите интеграционный тест и перестаньте тратить свое время.

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

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

4
ответ дан 4 December 2019 в 06:17
поделиться
$('div#someDiv').removeAttr("height");

Метод кнопки «Сохранить» должен содержать только вызовы верхнего уровня для делегирования объектов другим объектам . Эти объекты затем могут быть абстрагированы через интерфейсы. Затем, когда вы тестируете метод кнопки «Сохранить», вы тестируете только взаимодействие с макетированными объектами .

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

Рекомендуемое прочтение:

  1. Чистый код: Руководство по мастерству гибкого программного обеспечения
  2. Руководство Google по написание тестируемого кода
3
ответ дан 4 December 2019 в 06:17
поделиться
Другие вопросы по тегам:

Похожие вопросы: