是否值得自己测试数据访问查询?

时间:2014-02-25 12:12:02

标签: sql database unit-testing testing data-access-layer

我知道适当的SOLID原则与IOC相结合意味着您可以通过模拟类依赖性来对所有代码进行单元测试,而无需实际访问数据库。在工作讨论之后,我的问题是,是否值得实际测试您的数据访问层本身。

如果您已正确分离代码,那么您的数据访问方法通常会非常小。这是一个人为的例子:

public class InvoiceQueries
{
    public IEnumerable<Customer> GetAllCustomersWithOverdueInvoices(TimeSpan timeOverdue)
    {
        var results = 
            from invoice in Invoices
            where invoice.DateDue - timeOverdue > DateTime.Now
            select invoice.Customer;

        return results;
    }
}

是否值得编写自动化测试(它不是单元测试)来检查您的查询是否正确编写?也许连接到真实的数据库,甚至是内存数据库,插入一些测试数据然后检查你的方法是否返回那些客户?

一位同事说你绝对应该 - 一切都应该进行测试。对我来说,在构建服务器上启动并运行某种形式的T-SQL数据库似乎需要付出大量的工作,更不用说这些测试几乎肯定会非常慢,而且用处可疑。

谁有实际测试(以孤立方式)他们的数据访问层的经验?

2 个答案:

答案 0 :(得分:1)

是的,它应该进行测试,并且值得花时间让它工作(否则你会浪费时间手动测试它)。是的,它比单元测试慢得多,但你可以让它足够快(内存中的数据库,所有测试的一次数据库启动,测试组等)。当然,您需要将db层与业务逻辑隔离

答案 1 :(得分:0)

经过一番商议,我和我的同事提出了一种将查询与持久层分开的方法。我在下面拼凑了一个简单的例子:

public class Invoice
{
    public int Id {get; set;}
    public DateTime DueOn {get; set;}
    public DateTime MadeOn {get; set;}
    public decimal Total {get; set;}
    public Customer Customer {get;set;}
}

public class Customer
{
    public int Id {get; set;}
    public string Name {get; set;}
}

public class OverdueInvoice
{
    public int CustomerId {get; set;}
    public int InvoiceId {get; set;}
    public string CustomerName {get; set;}
    public decimal Total {get; set;}
}

public class InvoiceQueries
{
    private readonly IReadRepository<Invoice> _invoices;

    public InvoiceQueries(IReadRepository<Invoice> invoices)
    {
        _invoices = invoices;
    }

    public IEnumerable<OverdueInvoice> GetAllOverdueInvoices(TimeSpan timeOverdue)
    {
        return _invoices
            .FindAll(invoice => invoice.DueOn - timeOverdue > DateTime.Now)
            .Select(Map);
    }

    private OverdueInvoice Map(Invoice invoice)
    {
        return new OverdueInvoice
        {
            CustomerId = invoice.Customer.Id,
            InvoiceId = invoice.Id,
            CustomerName = invoice.Customer.Name,
            Total = invoice.Total,
        };
    }
}

public class InMemoryInvoicesRepository : IReadRepository<Invoice>, IWriteRepository<Invoice>
{
    private List<Invoice> _backingStore;

    public InMemoryInvoicesRepository() : this(new List<Invoice>())
    {}

    public InMemoryInvoicesRepository(List<Invoice> backingStore)
    {
        _backingStore = backingStore;
    }

    public IEnumerable<Invoice> FindAll(Predicate<Invoice> predicate)
    {
        return _backingStore.FindAll(predicate);
    }

    public void Add(Invoice item)
    {
        _backingStore.Add(item);
    }
}

public interface IReadRepository<T>
{
    IEnumerable<T> FindAll(Predicate<T> predicate);
}

public interface IWriteRepository<T>
{
    void Add(T item);
}

将数据访问分隔为“查询”对象和“存储库”对象允许您在测试时将存储库替换为内存中集合。这样,您可以使用您想要的任何对象填充List,然后通过针对该列表运行查询来测试您的查询层。存储库是一个非常愚蠢的对象,它会接收您的查询并将其传递给您的ORM(实际上,这个Func可能必须被Expression&gt;替换)。