C# 使用IQueryable的单元测试代码

C# 使用IQueryable的单元测试代码,c#,linq,entity-framework,unit-testing,C#,Linq,Entity Framework,Unit Testing,我被要求为某些功能编写一些单元测试,但坦率地说,我不太确定为这段特定的代码编写单元测试的必要性和有用性。我并没有试图质疑单元测试的必要性或实用性 所讨论的代码非常琐碎,经常被使用。基本上,它是对.Skip()和.Take()扩展方法的包装。在我看来,这些方法作为一个整体的合法性是值得怀疑的 代码基本上是这样的: public IQueryable<T> Page(IQueryable<T> query, int page, int size) { if(query

我被要求为某些功能编写一些单元测试,但坦率地说,我不太确定为这段特定的代码编写单元测试的必要性和有用性。我并没有试图质疑单元测试的必要性或实用性

所讨论的代码非常琐碎,经常被使用。基本上,它是对.Skip()和.Take()扩展方法的包装。在我看来,这些方法作为一个整体的合法性是值得怀疑的

代码基本上是这样的:

public IQueryable<T> Page(IQueryable<T> query, int page, int size)
{
    if(query == null) throw new ArgumentNullException("query");
    if(page < 0) throw new ArgumentOutOfRangeException("page"); 
    if(page < 0) throw new ArgumentOutOfRangeException("size"); 

    return query.Skip(page * size).Take(size);
}
publicIQueryable页面(IQueryable查询,int页面,int大小)
{
如果(query==null)抛出新的ArgumentNullException(“query”);
如果(第<0页)抛出新ArgumentOutOfRangeException(“第页”);
如果(第<0页)抛出新ArgumentOutOfRangeException(“大小”);
返回查询。跳过(页面*大小)。获取(大小);
}

当然,我可以对预期的异常进行单元测试,但还有什么?很可能是我没有抓住要点,那么这是怎么回事呢?

事实上,因为
IQueryable
可能封装了一个底层的数据存储—这是实体框架的可查询文件的情况—我们谈论的是集成测试

在我的例子中,如果我需要测试像您的代码块这样的东西,我会在数据库中存储一些测试数据,以便以后查询

因为我可以预期结果,所以我可以执行断言来检查被测试的方法是否返回了预期结果

更新
由于我看到了一些与否决票和一些评论相混淆的地方,我想指出,我在回答这个问题时考虑到了这样一个事实,答案被标记为实体框架,我猜可查询项是从
DbSet

中获得的,您可以通过调用

List dummyData=new List();
//添加数据
var result=Page(dummyData.AsQueryable(),0,10);
//对结果执行断言。

如果您实际上只是想测试分页是否正常工作。

您可以在这里测试很多东西:

  • 检查是否有适当的防护措施(传递无效参数时引发的异常)
  • 检查是否使用正确的参数调用了
    Skip
  • 检查是否使用正确的参数调用了
    Take
  • 检查在
    Take
    之前是否调用了
    Skip
  • 第2-4点最好用模拟测试。模拟框架在这里派上了用场。
    这种测试称为“基于交互的测试”

    您还可以使用“基于状态的测试”,方法是使用包含数据的列表调用测试中的方法,并检查返回的数据是否是正确的子集

    第2点的测试可能如下所示:

    public void PageSkipsThePagesBeforeTheRequestedPage()
    {
        var sut = new YourClass();
        var queryable = Substitute.For<IQueryable<int>>();
    
        sut.Page(queryable, 10, 50);
    
        queryable.Received().Skip(500);
    }
    
    public void pageskipsthepages在此之前请求页面()
    {
    var sut=newyourclass();
    var queryable=Substitute.For();
    sut.Page(可查询,10,50);
    queryable.Received().Skip(500);
    }
    

    此测试使用模拟框架。

    我确信,非常需要测试此方法。 首先,由于缺陷:

    if(page < 0) throw new ArgumentOutOfRangeException("page"); 
    if(page < 0) throw new ArgumentOutOfRangeException("size"); 
    
    如果(第<0页)抛出新ArgumentOutOfRangeException(“第页”);
    如果(第<0页)抛出新ArgumentOutOfRangeException(“大小”);
    
    实际上,您没有检查大小变量

    无论如何,不需要执行“集成”测试-您可以简单地模拟IQueryable对象并执行单元测试。(见以下答案:)
    在我看来,测试这个方法是绝对正确的。

    您可以编写一个测试,通过传递一个
    IQueryable
    来确保它的执行,如果迭代而不是迭代结果,它将抛出。1。第三个
    if
    语句有问题。2.请看Jon Skeet的精彩EduLINQ项目,以及他在重新实现LINQ扩展方法的过程中编写的测试。@fuaaark您能否澄清一下,您是否被要求测试这个连接到基础数据库的方法,还是真正的单元测试方法?第三个if语句是否不正确?如果(大小<0)抛出新ArgumentOutOfRangeException(“大小”)你说得对,顺便说一下,这不是实际的代码。这和很多好主意没什么不同,在我的回答中,我考虑过创建测试数据。在您的情况下,这是一个实际的单元测试,而不是我的,它是一个集成测试。这里不需要集成测试。您只需使用在内存中工作的
    IQueryable
    实现。我正要写类似的评论。@DanielHilgarth如果你对我的答案投了反对票,你应该再检查一遍。我不是说“做一个集成测试”。我说过OP的代码是一个集成测试,而不是一个单元测试……………@MatíasFidemraizer:我没有投反对票。但是你的评论是不正确的。1) OP没有显示任何测试代码,因此您无法知道它是什么类型的测试。他展示的代码是一个简单的方法。当然不是集成测试。2) 他展示的方法不需要集成测试。@DanielHilgarth检查OP问题中的
    [EntityFramework]
    标记。也许我疯了,但当我阅读OP的代码并检查标记为实体框架的代码时,我怀疑可查询项来自
    DbSet
    !好的,听起来不错。我不清楚的是,当在其他代码中使用这段经过测试的功能时,如何以干净的方式验证对它的调用?当方法被调用时,我可以设置一些公共属性,但这感觉很难看…@fuaaark:不,不要设置公共属性。你到底想测试什么?你想测试另一个名为
    Page
    方法的方法吗?是的,因为Page()方法在很多不同的地方使用,所以我只想对它进行一次单元测试。但我需要验证它是否被调用。@fuaaark:你想验证它在所有不同的地方被使用吗?是的,我想是的,怎么办
    if(page < 0) throw new ArgumentOutOfRangeException("page"); 
    if(page < 0) throw new ArgumentOutOfRangeException("size");