TDD с шаблоном хранилища

10

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

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

Я думаю об этом с двух дней и до сих пор не могу найти какое-либо разумное решение. Что я должен делать?

Thaven
источник

Ответы:

11

Что делает репозиторий - это переводит из вашего домена в вашу инфраструктуру DAL, такую ​​как NHibernate или Doctrine, или ваши классы, выполняющие SQL. Это означает, что ваш репозиторий будет вызывать методы в указанной платформе для выполнения своих обязанностей: ваш репозиторий создает запросы, необходимые для извлечения данных. Если вы не используете ORM-каркас (я надеюсь, что вы ...), репозиторий будет местом, где создаются необработанные SQL-операторы.

Основным из этих методов является сохранение: в большинстве случаев он просто передает объект из хранилища на единицу работы (или сеанс).

public void Save(Car car)
{
    session.Save(car);
}

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

public function GetCarWithId(String id)
{
    return Session.QueryOver<Car>()
                    .Where(x => x.Id == id)
                    .SingleOrDefault();
}

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

Вариант 1: имитация вызовов, сделанных в рамках ORM

Конечно, вы можете смоделировать Session-объект и просто утверждать, что сделаны правильные вызовы. Хотя это тестирует хранилище, на самом деле это не тестирование , потому что вы просто проверяете, что хранилище выглядит так, как вы этого хотите. Тест в основном говорит «код должен выглядеть так». Тем не менее, это правильный подход, но кажется, что этот вид теста имеет очень мало значения.

Вариант 2: (Пере) построить базу данных из тестов

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

Вариант 3: тестирование на реальной базе данных

Другой подход заключается в тестировании реальной базы данных и изоляции тестового модуля. Вы можете сделать это несколькими способами: окружить свои тесты транзакцией, очистить вручную (не рекомендуется, так как это очень сложно поддерживать), полностью перестроить базу данных после каждого шага ... В зависимости от приложения, которое вы создаете, это может или может потребоваться не быть осуществимым В моих приложениях я могу полностью создать локальную базу данных разработки из системы контроля версий, и мои тесты юнитов в репозиториях используют транзакции для полной изоляции тестов друг от друга (открытая транзакция, вставка данных, тестовый репозиторий, транзакция отката). Каждая сборка сначала настраивает локальную базу данных разработки, а затем проводит изолированные тесты транзакций для репозиториев в этой локальной базе данных разработки. Это'

Не проверяйте DAL

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

При тестировании возврата ваших репозиториев вы можете просто проверить, соответствует ли какое-либо идентифицирующее свойство вашего объекта домена. Это может быть идентификатор, но в тестах часто выгоднее проверять читабельное свойство. В разделе «Принеси мне все машины, произведенные после 2010 года…» можно просто проверить, что пять машин возвращены, а номерные знаки - «вставить список здесь». Дополнительным преимуществом является то, что это заставляет вас думать о сортировке, а ваш тест автоматически форсирует сортировку. Вы будете удивлены тем, сколько приложений либо сортируют несколько раз (возвращают отсортированные из базы данных, сортируют перед созданием объекта представления, а затем сортируют объект представления, на всякий случай , все по одному и тому же свойству ), либо неявно принимают сортировку хранилища и случайно удаляют что-то по пути, ломая пользовательский интерфейс.

«Юнит тест» - это просто название

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

JDT
источник
3
Модульные и интеграционные тесты имеют разные цели. Названия этих тестов не просто декоративны; они также описательны.
Роберт Харви
9
  1. Не тестируйте тривиальные или очевидные методы репозитория.

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

    Это тот же принцип, который применяется к тривиальным свойствам, как этот:

    public property SomeProperty
    {
        get { return _someProperty; }
        set { _someProperty = value; }
    }
    

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

  2. Если вы все еще хотите проверить эти методы ...

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

Дополнительная информация
Полный стек, часть 3: Построение репозитория с использованием TDD (начните просмотр примерно через 16 минут).

Роберт Харви
источник
3
Конечно, я понимаю это. Тем не менее, если это подход TDD, я не должен писать какой-либо код, если у меня нет тестов для этого кода в первую очередь, верно?
Thaven
1
@Thaven - на youtube есть серия видеороликов, озаглавленная "tdd dead?". Смотреть их Они затрагивают множество интересных моментов, одним из которых является то, что применение TDD на каждом уровне вашего приложения не обязательно является лучшей идеей. «Ни один код без неудачного теста» - это слишком крайняя позиция, это один из выводов.
Жюль
2
@Jules: что такое tl; dw?
Роберт Харви
1
@RobertHarvey Сложно подвести итог, но самым важным моментом было то, что трактовка TDD как религии, которую всегда следует соблюдать, является ошибкой. Выбор его использования является частью компромисса, и вы должны учитывать, что (1) вы можете работать быстрее без каких-либо проблем и (2) это может подтолкнуть вас к более сложному решению, чем вам нужно, особенно если вы используете много издевательств.
Жюль
1
+1 за точку # 1. Тесты могут быть ошибочными, просто они обычно тривиальны. Бессмысленно проверять функцию, правильность которой более очевидна, чем проверка. Это не значит, что получение 100% покрытия кода позволяет вам приблизиться к тестированию при каждом возможном выполнении программы, поэтому вы также можете быть осторожны с тем, на что вы тратите усилия по тестированию.
Довал