C# 单元测试函数

C# 单元测试函数,c#,entity-framework,unit-testing,mocking,C#,Entity Framework,Unit Testing,Mocking,我有一个公开IQueryable的存储库和一个处理特定查询的服务,这里有一些使用DbFunctions的方法。为了便于测试,我创建了一个包含静态元素列表的假存储库,并将其注入到服务中。问题是,由于我的服务查询一个列表而不使用数据库,所以我得到一个错误“这个函数只能从LINQ调用到实体” 有没有比创建假DbFunctions和QueryProvider更简单的方法来测试它 提前感谢您无法可靠地伪造SQL,因为在许多情况下,LINQ to对象的行为与LINQ to SQL不同。例如,如果(new[]

我有一个公开IQueryable的存储库和一个处理特定查询的服务,这里有一些使用DbFunctions的方法。为了便于测试,我创建了一个包含静态元素列表的假存储库,并将其注入到服务中。问题是,由于我的服务查询一个列表而不使用数据库,所以我得到一个错误“这个函数只能从LINQ调用到实体”

有没有比创建假DbFunctions和QueryProvider更简单的方法来测试它


提前感谢

您无法可靠地伪造SQL,因为在许多情况下,LINQ to对象的行为与LINQ to SQL不同。例如,如果
(new[]{“asdf”}).Contains(“asdf”)
在LINQ to对象中返回false,那么LINQ to SQL中相同类型的查询将返回true。我发现最好的办法是将数据的检索与对该数据的操作分开。也许可以创建某种PersonManager,将IPersonRepository作为依赖项。您可以伪造/模拟IPersonRepository,并使用它来测试PersonManager在各种情况下是否执行了它应该执行的操作

因为我最近遇到了同样的问题,并且选择了一个更简单的解决方案,所以我想把它发布在这里。。此解决方案不需要填隙片、模拟,也不需要膨胀等

  • 将“useDbFunctions”布尔标志传递给方法,默认值为true
  • 当您的实时代码执行时,您的查询将使用DbFunctions,一切都将正常工作。由于默认值,调用者不必担心它
  • 当单元测试调用要测试的方法时,它们可以传递useDbFunctions:false
  • 在您的方法中,您可以使用该标志组成IQueryable。。 如果useDbFunctions为true,则使用DbFunctions将谓词添加到查询表中。 如果useDbFunctions为false,则跳过DbFunctions方法调用,并执行显式C#等价解决方案 这样,您的单元测试将检查几乎95%的方法与活动代码的一致性。您仍然拥有“DbFunctions”与等效代码之间的增量,但是要努力做到这一点,95%的增量看起来会带来很多好处

    public SomeMethodWithDbFunctions(bool useDbFunctions = true)
    {
        var queryable = db.Employees.Where(e=>e.Id==1); // without the DbFunctions
    
        if (useDbFunctions) // use the DbFunctions
        {
         queryable = queryable.Where(e=> 
         DbFunctions.AddSeconds(e.LoginTime, 3600) <= DateTime.Now);
        }  
        else
        {
          // do db-functions equivalent here using C# logic
          // this is what the unit test path will invoke
          queryable = queryable.Where(e=>e.LoginTime.AddSeconds(3600) < DateTime.Now);
        }                    
    
        var query = queryable.Select(); // do projections, sorting etc.
    }
    

    因为单元测试将设置本地DbContext实体,所以C#logic/DateTime函数将起作用。

    我尝试实现dateDiff函数,它对我来说很有效 但我们应该认为,在这种情况下,我们测试不同的功能 我们测试的不是真实的行为

     private class MySqlFunctions
        {
            [DbFunction("SqlServer", "DATEDIFF")]//EF will use this function
            public int? DateDiff(string datePartArg, DateTime startDate, DateTime endDate)
            {
                var subtract = startDate.Subtract(endDate);
                switch (datePartArg)
                {
                    case "d":
                        return (int?)subtract.TotalDays;
                    case "s":
                        return (int?)subtract.TotalSeconds; // unit test will use this one
                }
                throw new NotSupportedException("Method supports only s or d param");
            }
        }
    
    然后在linq代码中

    var sqlFunctions = new MySqlFunctions();
    
    var result = matches.Average(s => sqlFunctions.DateDiff("s", s.MatchCreated, s.WaitingStarted);
    

    嗨,亚历克斯,谢谢你的回复。我刚刚发现有人在这里遇到了同样的问题:如果您在EntityFunctionsFake类上创建了一个具有相同签名的Contains方法,并在比较时指定IgnoreCase,那么您给出的示例可以使用给定的解决方案来解决。它可能不适合所有情况,但至少我现在可以测试我的需求。你怎么认为?谢谢,但是你不能在LINQ查询中使用IgnoreCase。那只能是。做集成测试。mock永远不会可靠,或者需要太多的代码(+维护),以致于它们自己成为一个应用程序。您需要为您的单元测试框架进行单元测试!谢谢你,格特!模拟DbFunctions确实很困难,我已经能够制作出一些我需要的函数,但正如您所说的,这只是维护。
    var sqlFunctions = new MySqlFunctions();
    
    var result = matches.Average(s => sqlFunctions.DateDiff("s", s.MatchCreated, s.WaitingStarted);