Как можно подделать слой моей базы данных в модульном тесте? - PullRequest
5 голосов
/ 22 апреля 2010

У меня вопрос по модульному тестированию.

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

//code a bit shortened
public actionresult Create(Formcollection formcollection){
    client c = nwe client();
    c.Name = formcollection["name"];
    ClientService.Save(c);
{

Clientservice будет вызывать объект слоя данных и сохранять его в базе данных.

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

ClientController cc = new ClientController();
cc.Create(new FormCollection (){name="John"});
//i know i had 10 clients before
assert.areEqual(11, ClientService.GetNumberOfClients());
//the last inserted one is John
assert.areEqual("John", ClientService.GetAllClients()[10].Name);

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

Но тогда, конечно, мои утверждения не будут работать, потому что, если я скажу GetNumberOfClients(), он всегда вернет X, потому что не взаимодействует с поддельным классом базы данных, используемым в методе Create.

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

Как заставить этот модульный тест работать без базы данных?

EDIT: Клиентская служба не подключается напрямую к БД. Он вызывает ClientDataClass, который будет подключаться к базе данных. Таким образом, ClientDatabaseClass будет заменен на фальшивку

Ответы [ 4 ]

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

В данном конкретном случае вы тестируете контроллер изолированно от базы данных.ClientService является абстракцией над базой данных и должен быть заменен на test double.Вы вставили подделку в контроллер, но все еще утверждаете реальную реализацию.Это не имеет смысла.

Утверждать тот же объект, который был введен в контроллер.

 interface IClientService 
    {
      public void GetNumberOfClients();
      public IList<Client> GetAllClients();
      public void Insert(Client client);
    }

Поддельная реализация сервиса:

   class FakeClientService : IClientService
   {
     private IList<CLient> rows = new List<CLient>();

     public void GetNumberOfClients()
     { 
       return list.Count;
     }

     public IList<Client> GetAllClients()
     {
       return list;
     }

     public void Insert(Client client)
     {
       client.Add(client);
     }
   }

Тест:

[Test]
public void ClientIsInserted()
{
  ClientController cc = new ClientController();
  FakeClientService fakeService = new FakeClientService();

  cc.ClientService = fakeService;

  cc.Create(new FormCollection (){name="John"});

  assert.areEqual(1, fakeService.GetNumberOfClients());
  assert.areEqual("John", fakeService.GetAllClients()[0].Name);
}

Если вы хотите проверить, как контроллер и сервис работают вместе - создайте подделку для ClientDatabaseClass.Это было бы как:

[Test]
public void ClientIsInserted()
{
  ClientController cc = new ClientController();
  IClientDatabaseClass databaseFake = new ClientDatabaseClassFake();

  ClientService service= new ClientService();

  service.Database = databaseFake;
  cc.ClientService = service;

  cc.Create(new FormCollection (){name="John"});

  assert.areEqual(1, service.GetNumberOfClients());
  assert.areEqual("John", service.GetAllClients()[0].Name);
}
2 голосов
/ 22 апреля 2010

Именно здесь юнит-тестирование, на мой взгляд, становится сложным.

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

public interface IDatabase<T>
{
    void Create(T value);
    int Count { get; }
    T[] All { get; }
}

Затем вы реализуете этот интерфейс, используя некоторый простой контейнер в памяти, а затем внедряете его снова, используя реальные средства доступа к базе данных. Контейнер в памяти часто называют «test-double».

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

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

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

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

edit: Это в значительной степени тот же ответ, что и у Стива Найта, все это будет намного короче:)

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

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

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