gpt4 book ai didi

c# - 使用扩展方法中定义的查询进行单元测试

转载 作者:可可西里 更新时间:2023-11-01 03:06:04 25 4
gpt4 key购买 nike

在我的项目中,我使用以下方法从数据库中查询数据:

  1. 使用可以返回任何类型且不绑定(bind)到一种类型的通用存储库,即 IRepository.Get<T>而不是 IRepository<T>.Get . NHibernates ISession是此类存储库的示例。
  2. IQueryable<T> 上使用扩展方法具有特定的 T封装重复查询,例如

    public static IQueryable<Invoice> ByInvoiceType(this IQueryable<Invoice> q,
    InvoiceType invoiceType)
    {
    return q.Where(x => x.InvoiceType == invoiceType);
    }

用法是这样的:

var result = session.Query<Invoice>().ByInvoiceType(InvoiceType.NormalInvoice);

现在假设我有一个要测试的公共(public)方法,它使用了这个查询。我想测试三种可能的情况:

  1. 查询返回 0 个发票
  2. 查询返回 1 张发票
  3. 查询返回多张发票

我现在的问题是:模拟什么?

  • 我不能 mock ByInvoiceType因为它是一种扩展方法,或者我可以吗?
  • 我什至不能 mock Query出于同样的原因。

最佳答案

经过更多研究并基于此处和 these 上的答案links ,我决定完全重新设计我的 API。

基本概念是在业务代码中完全禁止自定义查询。这解决了两个问题:

  1. 提高了可测试性
  2. Mark's blog post 中列出的问题不能再发生了。业务层不再需要关于正在使用的数据存储的隐式知识来了解 IQueryable<T> 上允许哪些操作。哪些不是。

在业务代码中,查询现在如下所示:

IEnumerable<Invoice> inv = repository.Query
.Invoices.ThatAre
.Started()
.Unfinished()
.And.WithoutError();

// or

IEnumerable<Invoice> inv = repository.Query.Invoices.ThatAre.Started();

// or

Invoice inv = repository.Query.Invoices.ByInvoiceNumber(invoiceNumber);

在实践中,这是这样实现的:

正如 Vytautas Mackonis 在 his answer 中建议的那样, 我不再直接依赖于 NHibernate 的 ISession ,相反,我现在依赖于 IRepository .

此接口(interface)有一个名为 Query 的属性类型 IQueries .对于业务层需要查询的每个实体,IQueries 中都有一个属性。 .每个属性都有自己的接口(interface),用于定义实体的查询。每个查询接口(interface)都实现了通用的 IQuery<T>接口(interface)依次实现 IEnumerable<T> ,导致上面看到的非常干净的 DSL 语法。

部分代码:

public interface IRepository
{
IQueries Queries { get; }
}

public interface IQueries
{
IInvoiceQuery Invoices { get; }
IUserQuery Users { get; }
}

public interface IQuery<T> : IEnumerable<T>
{
T Single();
T SingleOrDefault();
T First();
T FirstOrDefault();
}

public interface IInvoiceQuery : IQuery<Invoice>
{
IInvoiceQuery Started();
IInvoiceQuery Unfinished();
IInvoiceQuery WithoutError();
Invoice ByInvoiceNumber(string invoiceNumber);
}

这种流畅的查询语法允许业务层组合提供的查询,以充分利用底层 ORM 的功能,让数据库尽可能多地进行过滤。

NHibernate 的实现看起来像这样:

public class NHibernateInvoiceQuery : IInvoiceQuery
{
IQueryable<Invoice> _query;

public NHibernateInvoiceQuery(ISession session)
{
_query = session.Query<Invoice>();
}

public IInvoiceQuery Started()
{
_query = _query.Where(x => x.IsStarted);
return this;
}

public IInvoiceQuery WithoutError()
{
_query = _query.Where(x => !x.HasError);
return this;
}

public Invoice ByInvoiceNumber(string invoiceNumber)
{
return _query.SingleOrDefault(x => x.InvoiceNumber == invoiceNumber);
}

public IEnumerator<Invoice> GetEnumerator()
{
return _query.GetEnumerator();
}

// ...
}

在我的实际实现中,我将大部分基础结构代码提取到一个基类中,因此为新实体创建新查询对象变得非常容易。向现有实体添加新查询也非常简单。

这样做的好处是业务层完全没有查询逻辑,因此可以轻松切换数据存储。或者可以使用条件 API 实现其中一个查询或从另一个数据源获取数据。业务层不会注意到这些细节。

关于c# - 使用扩展方法中定义的查询进行单元测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9921647/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com