C# 在通过依赖注入提供单元可测试性的同时,在何处放置与数据访问相关的业务逻辑层?
我正在寻找一种方法,在带有实体框架的MVC4应用程序中拥有一个独立的业务逻辑层,而不必在其中注入我的(真实或虚假)数据库上下文。现在,我们有一个从C# 在通过依赖注入提供单元可测试性的同时,在何处放置与数据访问相关的业务逻辑层?,c#,entity-framework,asp.net-mvc-4,unit-testing,dependency-injection,C#,Entity Framework,Asp.net Mvc 4,Unit Testing,Dependency Injection,我正在寻找一种方法,在带有实体框架的MVC4应用程序中拥有一个独立的业务逻辑层,而不必在其中注入我的(真实或虚假)数据库上下文。现在,我们有一个从IContext派生的假数据库上下文,一个存储库repository和一个控制器MyController。IContext通过以下模式注入控制器和存储库: public class MyController : Controller { readonly Repository _repo; /// <summary>
IContext
派生的假数据库上下文,一个存储库repository
和一个控制器MyController
。IContext
通过以下模式注入控制器和存储库:
public class MyController : Controller
{
readonly Repository _repo;
/// <summary>
/// Initializes a new instance of the <see cref="Controllers.MyController"/> class which
/// makes use of the real Entity Framework context that connects with the database.
/// </summary>
public MyController()
{
_repo = new Repository();
}
/// <summary>
/// Initializes a new instance of the <see cref="Controllers.MyController"/> class which
/// can make use of a "Fake" Entity Framework context for unit testing purposes.
/// </summary>
/// <param name="db">Database context.</param>
public MyController(IContext db)
{
_repo = new Repository(db);
}
(...)
}
我认为有两种方法可以避免这个问题:
希望有人能给我一些见解。谢谢。您有EF上下文,它继承自IContext,如果您想使用EF6,则继承自DbContext
public class UserContext : DbContext
{
DbSet<User> users { get; set; }
}
然后通过接口将服务注入控制器
public class UserController : Controller
{
private readonly IUserService _service;
//Consider using a Dependency injection framework e.g. Unity
public UserController(IUserService service)
{
this.service = service;
}
//The method that is tightly coupled to the view and uses the service
[HttpGet]
public ActionResult GetUserByID(int id)
{
return View(_service.GetUserById(id));
}
}
也可以考虑使用嘲讽框架来模拟你的上下文,例如MOQ
[TestMethod]
public void UserServiceTest()
{
//Initialize your Mock Data
var testDataUser = new List<Users>
{
new User{
ID = 1,
Name = "MockUser"
}
}.AsQueryable();
//Initialize the Mock DbSet
var mockSetUser = new Mock<DbSet<User>>();
mockSetUser.As<IQueryable<User>>().Setup(m => m.Provider).Returns(testDataUser. .Provider);
mockSetUser.As<IQueryable<User>>().Setup(m => m.Expression).Returns(testDataUser .Expression);
mockSetUser.As<IQueryable<User>>().Setup(m => m.ElementType).Returns(testDataUser .ElementType);
mockSetUser.As<IQueryable<User>>().Setup(m => m.GetEnumerator()).Returns(testDataUser .GetEnumerator());
//Initialize the mock Context
var mockContext = new Mock<UserContext>();
//Return the mock DbSet via the mock context
mockContext.Setup(c => c.Users).Returns(mockSetUser.Object);
//Create the service and inject the mock context
var userService = new UserService(mockContext.Object)
//Test your context via the service
var user = userService.GetUserByID(1);
Assert.AreEqual("MockUser", user.Name);
}
[TestMethod]
public void UserServiceTest()
{
//初始化模拟数据
var testDataUser=新列表
{
新用户{
ID=1,
Name=“MockUser”
}
}.AsQueryable();
//初始化模拟数据库集
var mockSetUser=new Mock();
mockSetUser.As().Setup(m=>m.Provider).Returns(testDataUser..Provider);
mockSetUser.As().Setup(m=>m.Expression).Returns(testDataUser.Expression);
mockSetUser.As().Setup(m=>m.ElementType).Returns(testDataUser.ElementType);
mockSetUser.As().Setup(m=>m.GetEnumerator()).Returns(testDataUser.GetEnumerator());
//初始化模拟上下文
var mockContext=new Mock();
//通过模拟上下文返回模拟数据库集
Setup(c=>c.Users).Returns(mockSetUser.Object);
//创建服务并注入模拟上下文
var userService=newuserservice(mockContext.Object)
//通过服务测试您的上下文
var user=userService.GetUserByID(1);
Assert.AreEqual(“MockUser”,user.Name);
}
我同意。也
层之间应该有硬边界。它可能看起来像这样:
IUserController@LiverpoolsNumber9啊,很好。因此,在测试时,我首先实例化一个“假”存储库,将该假回购注入业务逻辑,然后用该业务逻辑实例化一个控制器?您需要连接存储库。我不知道你为什么不这么做。然后将IRepository注入到您的业务逻辑中。接口,以防你需要模仿它。也只将IBusinessLogic注入控制器。是的,对不起,第一条评论被删除了。我希望第二条评论有意义:)为什么需要存储库的接口?唯一需要伪造的是DbContext。啊,我找到了一个分离业务逻辑和存储库的好例子。谢谢!这正是我们现在正在做的。只有我决定打电话给
服务
经理。我们将查看是否使用Moq
。谢谢。这确实与我现在使用的方法非常相似。使用容器的优点是什么?每个人使用存储库和服务/管理器接口的原因是什么?我现在不知道如何提高单元可测试性。您应该始终针对接口而不是具体实现进行编码。1) 保持代码的可伸缩性,2)保持代码的可测试性。当进行单元测试时,您应该测试一种方法。如果该方法使用依赖项,则应模拟该依赖项。否则,您实际上是在测试该方法及其依赖项。尤其是当您的方法依赖于数据库之类的东西时。您不希望对实时数据库运行单元测试。此外,对于存储库容器,您可以决定让所有存储库共享同一个IContext,这使得将多个存储库操作封装到一个数据库事务中变得更加容易。我有一个IContext
,它被注入到存储库中,因此可以中断数据库连接。现在我认为没有理由为管理器或存储库提供接口。我确实看到了使用存储库容器将多个事务合并到一个数据库事务中的优势(我觉得这是可能的,但我对如何实现它没有明确的看法)。谢谢。我发布的RepositoryContainer代码显示了实现。如果您正在针对您的经理编写单元测试,尽管IContext被模拟,但您仍然在使用存储库代码污染您的经理测试。因此,在测试管理器时模拟存储库。在测试存储库时模拟IContext。
public class UserController : Controller
{
private readonly IUserService _service;
//Consider using a Dependency injection framework e.g. Unity
public UserController(IUserService service)
{
this.service = service;
}
//The method that is tightly coupled to the view and uses the service
[HttpGet]
public ActionResult GetUserByID(int id)
{
return View(_service.GetUserById(id));
}
}
[TestMethod]
public void UserServiceTest()
{
//Initialize your Mock Data
var testDataUser = new List<Users>
{
new User{
ID = 1,
Name = "MockUser"
}
}.AsQueryable();
//Initialize the Mock DbSet
var mockSetUser = new Mock<DbSet<User>>();
mockSetUser.As<IQueryable<User>>().Setup(m => m.Provider).Returns(testDataUser. .Provider);
mockSetUser.As<IQueryable<User>>().Setup(m => m.Expression).Returns(testDataUser .Expression);
mockSetUser.As<IQueryable<User>>().Setup(m => m.ElementType).Returns(testDataUser .ElementType);
mockSetUser.As<IQueryable<User>>().Setup(m => m.GetEnumerator()).Returns(testDataUser .GetEnumerator());
//Initialize the mock Context
var mockContext = new Mock<UserContext>();
//Return the mock DbSet via the mock context
mockContext.Setup(c => c.Users).Returns(mockSetUser.Object);
//Create the service and inject the mock context
var userService = new UserService(mockContext.Object)
//Test your context via the service
var user = userService.GetUserByID(1);
Assert.AreEqual("MockUser", user.Name);
}