Как можно подделать слой моей базы данных в модульном тесте?
У меня есть вопрос о модульном тестировании.
Скажем, у меня есть контроллер с одним методом create, который помещает нового клиента в базу данных:
//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.
Я также могу создать список клиентов в поддельном классе базы данных, но, поскольку будут созданы два разных экземпляра (один в действии контроллера и один в модульном тесте), они не будут взаимодействовать.
Как заставить этот модульный тест работать без базы данных?
РЕДАКТИРОВАТЬ: клиентская служба не подключается напрямую к БД. Он вызывает ClientDataClass, который будет подключаться к базе данных. Таким образом, ClientDatabaseClass будет заменен на подделку
4 ответа
В этом конкретном случае вы тестируете контроллер изолированно от базы данных. 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);
}
Здесь, на мой взгляд, юнит-тестирование становится сложным.
То, как я делал это в прошлом, - это эффективное удаление всей базы данных. То, как вы это сделаете, будет зависеть от того, что вы пытаетесь сделать, потому что базы данных, очевидно, довольно универсальны. В вашем конкретном примере что-то вроде следующего:
public interface IDatabase<T>
{
void Create(T value);
int Count { get; }
T[] All { get; }
}
Затем вы реализуете этот интерфейс с помощью некоторого простого контейнера в памяти, а затем внедряете его снова, используя реальные средства доступа к базе данных. Контейнер в памяти часто называют "test-double".
Это дает вам ваше разделение, которое позволяет вам продолжить модульное тестирование кода вашего контроллера без необходимости доступа к базе данных.
Конечно, у вас все еще есть проблема с тем, как вы тестируете уровень доступа к базе данных. Для этого у меня может возникнуть соблазн использовать реальную базу данных или проверить ее с помощью набора интеграционных тестов.
Возможно, вы могли бы сделать ваш поддельный класс DB Serialiseable и загружать его из одного места каждый раз. Это позволило бы вам сохранить данные в нем, чтобы они вели себя так, как если бы это была база данных, но на самом деле она не была.
Используйте внедрение зависимостей, и вместо того, чтобы поразить вашу базу данных, создайте репозиторий и используйте его (по крайней мере, так я делаю, когда дело доходит до модульного тестирования)
редактировать: это в значительной степени тот же ответ, что и Стив Найт, все это будет намного короче:)