在单元测试中伪造我的数据库层的方法是什么?

时间:2010-04-22 07:08:16

标签: .net asp.net-mvc database unit-testing testing

我对单元测试有疑问。

假设我有一个带有一个create方法的控制器,它将新客户放入数据库:

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

Clientservice将调用datalayer对象并将其保存在数据库中。

我现在所做的是创建数据库测试脚本并在测试之前将我的数据库设置为已知状态。 所以当我在单元测试中测试这个方法时,我知道数据库中必须有一个客户端,它的名字是什么。简而言之:

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,因为它与创建方法中使用的假数据库类没有交互。

我还可以在假数据库类中创建一个客户端列表,但由于将创建两个不同的实例(一个在控制器操作中,一个在单元测试中),它们将没有交互。

在没有数据库的情况下,使单元测试工作的方法是什么?

编辑: 客户端服务不直接连接到DB。它调用ClientDataClass,它将连接到数据库。因此ClientDatabaseClass将替换为假的

4 个答案:

答案 0 :(得分:5)

在这种特殊情况下,您将独立于数据库测试控制器。 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);
}

答案 1 :(得分:2)

在我看来,这是单元测试变得困难的地方。

我过去的做法是有效地抽象整个数据库。你如何做到这将取决于你想要做什么,因为数据库显然是相当多才多艺。在您的具体示例中,如下所示:

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

然后使用一些简单的内存容器实现此接口,然后使用真正的数据库访问器再次实现它。内存容器通常被称为“测试双重”。

这为您提供了分离,使您可以继续对控制器代码进行单元测试,而无需访问数据库。

当然,您仍然遇到了如何对数据库访问层进行单元测试的问题。为此,我可能很想使用真正的数据库,或者通过一系列集成测试对其进行测试。

答案 2 :(得分:1)

也许你可以让你的假数据库类Serialiseable并每次从一个位置加载它。这将允许您将数据保留在其中,因此它的行为就好像它是一个数据库,而不是真正的数据库。

答案 3 :(得分:1)

使用依赖注入,而不是点击你的数据库,创建一个存储库并使用它(至少我在单元测试时这样做)

编辑:这与史蒂夫·奈特的答案几乎相同,都要短得多:)