C# 如何测试创建DBContext的方法?

C# 如何测试创建DBContext的方法?,c#,.net,asp.net-mvc,entity-framework,unit-testing,C#,.net,Asp.net Mvc,Entity Framework,Unit Testing,我没有很多单元测试的经验。例如,我在应用程序中有一个简单的方法: public void GetName() { UserRights rights = new UserRights(new DatabaseContext()); string test = rights.LookupNameByID("12345"); Console.WriteLine(test); } 在本例中,我可以通过传递模拟的DatabaseConte

我没有很多单元测试的经验。例如,我在应用程序中有一个简单的方法:

public void GetName()
    {
        UserRights rights = new UserRights(new DatabaseContext());
        string test = rights.LookupNameByID("12345");
        Console.WriteLine(test);
    }

在本例中,我可以通过传递模拟的DatabaseContext来测试UserRights类的所有方法,但如何测试GetName()方法呢?最佳做法是什么?应在何处创建DatabaseContext?

将代码与DB Context的依赖性分开是您需要研究的问题,这可以通过使用来实现。由于要将DB上下文传递到对象构造函数(在本例中为UserRights),因此可以非常轻松地更改代码以接受接口(或者简单地向接受接口的类添加重载构造函数,然后在单元测试中调用该构造函数,保留任何现有代码)。有很多方法可以做到这一点,一个快速的谷歌搜索产生了以下文章:

一旦您的类可以接受接口而不是强类型DB上下文对象(或作为强类型DB上下文对象的替代对象),您就可以使用模拟框架来协助测试。看看Moq,看看如何在单元测试中使用它们的例子


请注意:当您的测试需要通过外键关联的数据时,Moq可能会变得很难使用,语法可能会很复杂,难以理解。需要注意的一件事是,为了使单元测试更容易设置,可能会对代码进行更改,虽然进行此更改可能有价值,但它也可能表明,如果您希望以适当隔离的方式(即单元测试)测试
GetName
方法,您需要重新考虑单元测试,而不是违反应用程序体系结构和/或设计模式然后不能使用new在方法本身中创建
UserRights
实例;因为测试实际上只是代码的另一个客户机,所以它不能(也不应该)知道
GetName
如何在内部工作

因此,这意味着对于一个适当的单元测试,您必须能够用客户机完全控制的方法替换方法的所有依赖项——在这种情况下,单元测试中的代码就是客户机

在您发布的代码中,客户机代码根本无法控制
用户权限
数据库上下文
,因此这是必须更改的第一件事

您需要重新编写代码,以便客户机能够提供
UserRights
实现。事实上,一旦完成,关于
DatabaseContext
从何而来的问题实际上与单元测试无关,因为它不关心
UserRights
本身如何执行其任务

有很多方法可以做到这一点;您可以使用mock或stub,您可以使用构造函数或方法注入,您可以使用UserRights工厂。下面是一个使用非常简单的存根的例子,IMHO是最好的开始方式,并且避免学习模拟框架——我个人会使用模拟来实现这一点,但这是因为我很懒:)

(下面的代码假设包含GetName的类称为“UserService”,并使用xUnit框架;但MSTest也可以正常工作)

假设您可以控制
UserService
的代码,这样就可以使
LookupNameByID
方法成为虚拟的(如果不能,那么您可能必须选择接口和模拟的路径)

现在在单元测试代码中,假设您创建了一个子类
UserRights
,如下所示:

public class ExplodingUserRights: UserRights
{
     public override string LookupNameByID(string id)
     {
         throw new Exception("BOOM!");
     }
}
现在,您可以编写一个测试来查看
GetName
在发生不好的事情时如何反应:

[Fact]
public void LookupNameByID_WhenUserRightsThrowsException_DoesNotReThrow()
{
   //this test will fail if an exception is thrown, thus proving that GetName doesn't handle exceptions correctly.
   var sut = new UserService(new ExplodingUserRights()); <-Look, no DatabaseContext!
   sut.GetName("12345");
}
等等。请注意,我们从未接近过
DatabaseContext
,因为只有在对
UserRights
类本身进行单元测试时,才需要解决这个问题。(在这一点上,我可能会建议使用Jeroen在其链接文章中的建议,并进行集成测试,除非为每个测试设置数据库是您不能或不愿做的事情,在这种情况下,您需要使用接口和模拟)


希望有帮助。

什么是
用户权限
?为什么它需要上下文?理想情况下,您可以使用依赖项注入:使用Unity或Ninject之类的框架在构造函数中注入上下文和存储库。在应用程序中,您可以使用DI绑定,而在测试中,您只需传递模拟依赖项。如果你想知道如何在记忆中测试上下文,看看。我觉得自己很愚蠢。。。我完全忘记了依赖注入。谢谢!哇!真令人印象深刻!非常感谢你。
[Fact]
public void LookupNameByID_WhenUserRightsThrowsException_DoesNotReThrow()
{
   //this test will fail if an exception is thrown, thus proving that GetName doesn't handle exceptions correctly.
   var sut = new UserService(new ExplodingUserRights()); <-Look, no DatabaseContext!
   sut.GetName("12345");
}
public class HappyUserRights: UserRights
{
     public override string LookupNameByID(string id)
     {
         return "yay!";
     }
}

[Fact]
public void LookupNameByID_ReturnsResultOfUserRightsCall()
{
   //this test will fail if an exception is thrown, thus proving that GetName doesn't handle exceptions correctly.
   var sut = new UserService(new HappyUserRights()); 
   var actual = sut.GetName("12345");
   Assert.Equal("yay!",actual);
}