Доступ к данным, модульное тестирование, внедрение зависимостей - PullRequest
3 голосов
/ 21 сентября 2009

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

Итак, вот в чем проблема: класс доступа к данным сделан статическим, это не позволяет имитировать его и в результате создавать реальные модульные тесты. Чтобы это исправить, мне нужно создать интерфейс и реализовать его в классе доступа к данным. Также мне нужно будет добавить конструктор в класс бизнес-логики, который будет принимать параметр этого типа интерфейса. Таким образом, это означает, что я в конечном итоге создаю класс доступа к данным в методе приложения Main (), и что-то подсказывает мне, что это не лучший подход (действительно ли нормально, что точка входа должна знать о некоторых вещах доступа к данным? Что, если цепочка намного длиннее или должно быть несколько цепочек?). Я знаю, что могу использовать некоторый контейнер IoC, но я думаю, что это слишком простое приложение для использования контейнеров.

Спасибо!

Ответы [ 3 ]

5 голосов
/ 21 сентября 2009

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

Наоборот! Этот является лучшим подходом, по крайней мере, с точки зрения тестируемости.

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

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

Если цепочка намного длиннее или имеется несколько цепочек, это не страшно (хотя вы можете рассмотреть возможность сворачивания некоторых прикладных уровней, если они выйдут из-под контроля). Рассмотрим этот потенциальный код в View приложения Model-View-Presenter, где Presenter зависит от CustomerService, который зависит от Repository и от AccountingService (который также зависит на Repository):

public CustomerView() {
    IRespository       repository        = new ConcreteRepository();
    IAccountingService accountingService = new ConcreteAccountingService(repository);
    ICustomerService   customerService   = new ConcreteCustomerService(accountingService, repository)
    this._Presenter = new CustomerPresenter(customerService);
}

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

1 голос
/ 21 сентября 2009

Предполагая, что вы используете LINQ to SQL, возможно, вы могли бы использовать шаблон репозитория, чтобы обернуть DataContext в интерфейс, который вы позже можете смоделировать, что делает возможным модульное тестирование.

В Интернете есть несколько статей на эту тему, вот одна из них: http://andrewtokeley.net/archive/2008/07/06/mocking-linq-to-sql-datacontext.aspx

0 голосов
/ 21 сентября 2009

Вот простое решение: вместо прямого вызова класса доступа к данным используйте вспомогательные методы:

  public void insert (...) {
      DataAccess.insert (...);
  }

Теперь вы можете переопределить эти вызовы. Я предлагаю разделить тесты так:

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

  2. В тестах макета просто соберите параметры, отправленные на insert(). Не звоните DataAccess.

Тесты в # 1 гарантируют, что запись данных в БД будет работать, в то время как тесты в # 2 убедятся, что вы вызываете DataAccess с правильными значениями. Последние тесты будут выполняться очень быстро, что облегчит тестирование особых случаев и т. Д.

Вам также не нужно постоянно запускать тесты с # 1. Только когда вы что-то измените в DataAccess или перед выпуском. Это сделает тестирование эффективным и приятным.

...