У меня есть вопрос о поблочном тестировании.
Скажите, что у меня есть контроллер с, каждый создает метод, который помещает нового клиента в базу данных:
//code a bit shortened
public actionresult Create(Formcollection formcollection){
client c = nwe client();
c.Name = formcollection["name"];
ClientService.Save(c);
{
Clientservice назвал бы объект datalayer и сохранил бы его в базе данных.
Что я делаю теперь, создают базу данных testscript и устанавливают мою базу данных в знать условии перед тестированием. Таким образом, когда я тестирую этот метод в модульном тесте, я знаю, что должен быть еще один клиент в базе данных, и каково ее имя. Короче говоря:
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);
Таким образом, я читал, то поблочное тестирование не должно поражать базу данных, я создал МОК для классов базы данных, но затем что? Я могу создать поддельный класс базы данных и заставить его ничего не сделать.
Но затем конечно, мои утверждения не будут работать потому что, если я скажу GetNumberOfClients()
это будет всегда возвращаться X, потому что это не имеет никакого взаимодействия с поддельным классом базы данных, используемым в Создать Методе.
Я могу также создать Список Клиентов в поддельном классе базы данных, но поскольку будет два различных созданные экземпляра (один в действии контроллера и один в модульном тесте), у них не будет взаимодействия.
Что путь состоит в том, чтобы заставить этот модульный тест работать без базы данных?
Править: clientservice не соединяется непосредственно с DB. Это называет ClientDataClass, который соединится с базой данных. Таким образом, ClientDatabaseClass будет заменен фальшивкой
В этом конкретном случае вы тестируете контроллер изолированно от базы данных. 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);
}
Возможно, вы могли бы сделать свой поддельный класс БД Serialiseable и загружать его каждый раз из одного места. Это позволит вам сохранить данные в нем, так что он будет вести себя так, как если бы это была база данных, но на самом деле она не была бы таковой.
Используйте внедрение зависимостей и вместо обращения к базе данных создайте репозиторий и используйте его (по крайней мере, так Я делаю это, когда дело доходит до модульного тестирования)
edit: Это почти тот же ответ, что и ответ Стива Найта, только намного короче :)
Вот где, на мой взгляд, юнит-тестирование становится трудным.
Раньше я делал это эффективно, абстрагируя всю базу данных. Как вы это сделаете, будет зависеть от того, что вы пытаетесь сделать, потому что базы данных, очевидно, довольно универсальны. В вашем конкретном примере что-то вроде следующего:
public interface IDatabase<T>
{
void Create(T value);
int Count { get; }
T[] All { get; }
}
Затем вы реализуете этот интерфейс с помощью некоторого простого контейнера в памяти, а затем снова реализуете его с использованием реальных средств доступа к базе данных. Контейнер в памяти часто называют «тестовым двойником».
Это дает вам разделение, которое позволяет вам продолжать модульное тестирование кода вашего контроллера без необходимости доступа к базе данных.
Конечно, у вас все еще есть проблема с тем, как вы проводите модульное тестирование уровня доступа к базе данных. Для этого у меня может возникнуть соблазн использовать настоящую базу данных или протестировать ее с помощью набора интеграционных тестов.