Каким образом можно подделать мой слой базы данных в unit test?

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

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

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

Клиентский сервис вызовет объект datalayer и сохранит его в базе данных.

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

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.

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

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

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

Ответ 1

В этом конкретном случае вы тестируете контроллер отдельно от базы данных. ClientService - это абстракция над базой данных и должна быть заменена тестовой двойной. Вы вводили фальшивку в контроллер, но все же утверждаете реальную реализацию. Это не имеет смысла.

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

 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

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

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

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

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

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

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

Ответ 3

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

Ответ 4

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

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