Каким образом можно подделать мой слой базы данных в 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: Это почти тот же ответ, что и Стив Найт, все будет намного короче:)