我没有很多单元测试经验。例如,我在应用程序中有简单的方法:
public void GetName()
{
UserRights rights = new UserRights(new DatabaseContext());
string test = rights.LookupNameByID("12345");
Console.WriteLine(test);
}
在这种情况下,我可以通过传递模拟的DatabaseContext来测试UserRights类的所有方法,但是如何测试GetName()方法?什么是最佳做法?应该在哪里创建DatabaseContext?
答案 0 :(得分:1)
如果要以适当隔离的方式测试GetName
方法(即单元测试),则不能使用new在方法本身中创建UserRights
实例;因为测试实际上只是代码的另一个客户端,因此它不能(也不应该)知道GetName
如何在内部工作
这意味着,对于正确的单元测试,必须能够将客户端完全控制的方法的所有依赖项替换为 - 在这种情况下,单元测试中的代码是客户。
在您发布的代码中,客户端代码完全无法控制
UserRights
或DatabaseContext
,因此这是首先需要更改的内容。
您需要重新编写代码,以便客户端可以提供UserRights
实现。事实上,一旦完成,DatabaseContext
来自哪里的问题实际上与单元测试无关,因为它不关心UserRights
本身如何执行其任务!
有很多方法可以做到这一点;你可以使用Mocks或Stubs,你可以使用Constructor或Method注入,你可以使用UserRights工厂。这是一个使用非常简单的存根的例子,其中恕我直言是最好的开始方式,并避免必须学习模拟框架 - 我个人会使用模拟器,但这是因为我很懒:)
(下面的代码假设包含GetName的类称为“UserService”,并使用xUnit框架;但MSTest也可以正常工作)
让我们假设您可以控制UserService
的代码,这样您就可以使LookupNameByID
方法成为虚拟(如果不能,那么如果接口和模拟,则可能需要进入路径)< / p>
public class UserRights
{
public virtual LookupNameByID(string id)
{
//does whatever a UserRights does.
}
}
public class UserService
{
readonly UserRights _rights;
public UserService(UserRights rights)
{
_rights=rights; //null guard omitted for brevity
}
public string GetName(string id)
{
return _rights.LookupNameByID(id);
}
}
现在在您的单元测试代码中假设您创建了一个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");
}
和好事发生的时候:
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);
}
等等。请注意,我们从未到过DatabaseContext
附近的任何地方,因为这是一个问题,当您对UserRights
类本身进行单元测试时,您只需解决。(此时我可能会建议使用他的链接文章中的Jeroen建议,并进行集成测试,除非为每个测试设置数据库是你不能或不会做的事情,在这种情况下你需要使用接口和模拟)
希望有所帮助。
答案 1 :(得分:0)
将您的代码分离依赖于数据库上下文是您需要调查的内容,这可以使用Repository pattern来完成。由于您将DB Context传递给对象构造函数(在本例中为UserRights),您可以非常轻松地更改代码以接受接口(或者只是将重载的构造函数添加到接受接口的类中,然后在您的单元测试,保留任何现有代码)。有很多方法可以完成这项工作,快速谷歌搜索产生了以下文章:
一旦你的类可以接受一个接口而不是(或作为替代)强类型的DB Context对象,你可以使用Mock框架来协助测试。请查看Moq,了解如何在单元测试https://github.com/Moq/moq4/wiki/Quickstart
中使用这些示例要注意:当您的测试需要通过外键相关的数据时,Moq很难处理,语法可能很复杂,无法理解。需要注意的一件事是,为了使单元测试更容易设置而对代码进行更改的诱惑,虽然进行此更改可能有价值,但也可能表明您需要重新考虑单元测试而不是而不是违反您的应用程序架构和/或设计模式