接口仓库单元测试的目的是什么?

7

我正在对用于检索类型为Customer的对象的ICustomerRepository接口进行单元测试。

  • 作为单元测试,通过这种方式测试ICustomerRepository能获得什么价值?
  • 在什么条件下会导致下面的测试失败?
  • 对于这种测试,是否建议进行我知道应该失败的测试?例如:当我知道我只在存储库中放置了5时,寻找id4

我可能遗漏了一些显而易见的东西,但似乎实现ICustomerRepository的类的集成测试将更有价值。

[TestClass]
public class CustomerTests : TestClassBase
{
    private Customer SetUpCustomerForRepository()
    {
        return new Customer()
        {
            CustId = 5,
            DifId = "55",
            CustLookupName = "The Dude",
            LoginList = new[]
            {
                new Login { LoginCustId = 5, LoginName = "tdude" },
                new Login { LoginCustId = 5, LoginName = "tdude2" }
            }
        };
    }

    [TestMethod]
    public void CanGetCustomerById()
    {
        // arrange
        var customer = SetUpCustomerForRepository();
        var repository = Stub<ICustomerRepository>();

        // act
        repository.Stub(rep => rep.GetById(5)).Return(customer);

        // assert
        Assert.AreEqual(customer, repository.GetById(5));
    }
}

测试基类

public class TestClassBase
{
    protected T Stub<T>() where T : class
    {
        return MockRepository.GenerateStub<T>();
    }
}

ICustomerRepository and IRepository

public interface ICustomerRepository : IRepository<Customer>
{
    IList<Customer> FindCustomers(string q);
    Customer GetCustomerByDifID(string difId);
    Customer GetCustomerByLogin(string loginName);
}

public interface IRepository<T>
{
    void Save(T entity);
    void Save(List<T> entity);
    bool Save(T entity, out string message);
    void Delete(T entity);
    T GetById(int id);
    ICollection<T> FindAll();
}
4个回答

6
我可能没有注意到什么,但似乎你测试的每一个方面都是模拟出来的?
一般来说,你只会模拟那些不是测试核心的对象。在这种情况下,您可以使用此存储库作为函数的源,该函数期望使用该存储库检索客户#5并对其执行操作。
例如,您可以模拟一个客户存储库,以便可以调用验证用户登录的方法。您将使用模拟存储库来防止单元测试依赖于真实数据源,而不是测试您的模拟存储库本身。

5

测试的第一条规则:

在编写测试之前,要知道测试的目的以及它试图证明什么。如果不知道它能证明什么,那么它就是无用的 :)

正如其他帖子中正确指出的那样,您正在存根化一个接口,然后调用该存根--这并不能证明您的生产代码是否有效。

什么是存根?

存根用于提供预先设定的值以驱动测试类的某些方面。例如,假设您有一个CustomerService,其中包含类型为ICustomerRepository的实例。如果您想查看当存储库为空时,CustomerService是否可以优雅地处理错误情况,则会将ICustomerRepositoryGetCustomerById方法存根为返回值为空/ null /引发异常,然后确保CustomerService方法执行了正确的操作(例如返回未找到客户结果)。

即存根只是帮助您达到特定条件/行为的协作者。我们正在测试CustomerService,而存根的ICustomerRepository仅帮助我们实现我们的目标。

你不是第一个问这个问题的人 :). 我通常建议开发者首先手动编写他们的测试替身。这有助于理解所有的交互以及框架实际为您做了什么。


4

根据定义,接口只是一种契约,因此没有代码可供测试。你需要针对接口的具体实现编写单元测试,因为那里才是实际执行代码所在的地方。

如果没有接口的具体实现,只是模拟具体实现,那么你的单元测试就只是基于一个模拟。这并没有什么实际价值。


2

我不理解你的测试意义。你只是在测试预设的存根是否会返回你输入的值,而没有测试任何真正的代码。

按照dcp的回复,接口是方法的声明。你需要测试这些方法的实现。


如何进行单元测试?无论我在哪里阅读,我们都被教导为单元测试模拟接口。那么如何对具体实现进行单元测试呢?据我理解,这应该是要避免的。您能举个例子吗?谢谢! - Stack0verflow
1
接口是一种规范,一种声明,而不是可以运行(因此进行测试)的代码。您可以在具体类中实现该规范,这是您将要运行和测试的代码。您可以使用模拟来实现接口,以帮助您测试依赖于实现该接口的对象的其他代码。您不会“测试接口”,而是通过在这些实现上调用接口方法(“针对接口进行测试”)来测试接口实现是否正确。 - Carles Barrobés
1
一个愚蠢的例子:一个“吃”的接口,由“狗”实现,并在“Master”中通过“feed(Eater pet)”方法调用。当您测试“Dog”时,将通过调用狗实例上的“eat”来测试其对“Eater”接口的实现,例如,看到狗增重。当您测试“Master”的“feed()”方法时,创建一个模拟的“Eater”,将其传递给该方法,并检查是否在其上调用了“eat”。 - Carles Barrobés
经过一些单元测试,我理解了这个想法。谢谢。 - Stack0verflow

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接