我是單元測驗和模擬的新手,但我正在嘗試為現有的 CRUD 功能添加測驗。我知道我需要使用 MOQ 庫,但我不確定是否可以使用下面的函式來做到這一點。所有 CRUD 函式using內部都有陳述句。
我正在使用 .NET 5.0 和 EF6
現有功能示例:
public static async Task CreateRoleAsync(string roleName)
{
using(DbEntities db = new DbEntities())
{
Log newLog = CreateLog();
Role newRole = new Role()
{
Name = roleName,
Log = newLog
};
db.Roles.Add(newRole);
await db.SaveChangesAsync();
}
}
是否可以維護當前的函式結構并實作模擬?還是應該重構這些函式以使用依賴注入?
如果您能指出正確的方向,我將不勝感激。讓我知道是否需要其他資訊。
提前致謝。
uj5u.com熱心網友回復:
在我看來,在這里您不需要撰寫單元測驗。你究竟會斷言什么?這里沒有業務規則或功能要求。在這種情況下,集成測驗就足夠了。
uj5u.com熱心網友回復:
我知道我需要使用 MOQ 庫
您不必被迫使用 MOQ 或任何其他模擬框架,您可以在沒有模擬框架的情況下實作很多目標,方法是創建自己的假貨而不模擬不訪問外部資源的代碼。
我正在嘗試為現有的 CRUD 功能添加測驗
CRUD 函式主要是更新資料庫狀態的函式,對于有用的反饋,我建議使用實際資料庫撰寫測驗。
uj5u.com熱心網友回復:
依賴注入促進了單元測驗,因此像您所擁有的代碼結構除了運行集成式測驗之外并不是非常可測驗的,這會將代碼配置為針對已知狀態的 DbContext 運行,該 DbContext 可能是具有合適測驗資料的資料庫或記憶體資料源。
靜態方法也帶來了依賴注入的問題。
該方法的更可測驗版本將類似于:
public class RoleService
{
private readonly DbEntities _context = null;
public RoleService(DbEntities context)
{
_context = context ?? throw new ArgumentNullException("context");
}
public async Task CreateRoleAsync(string roleName)
{
Log newLog = CreateLog();
Role newRole = new Role()
{
Name = roleName,
Log = newLog
};
_context.Roles.Add(newRole);
await context.SaveChangesAsync();
}
}
現在,當您進行單元測驗 RoleService.CreateRole 時,您可以模擬 DbContext,盡管模擬一個可以說是相當笨拙。單元測驗是考慮使用存盤庫模式和 UnitOfWork 模式來管理 DbContext 范圍的一個很好的理由。DbContext這些類可以公開更簡單的介面,這些介面比和更容易模擬DbSet<TEntity>。
public class RoleService
{
private readonly IDbContextScopeFactory _contextScopeFactory = null;
private readonly IRoleRepository _roleRepository = null;
public RoleService(IDbContextScopeFactory contextScopeFactory, IRoleRepository roleRepository)
{
_contextScopeFactory = contextScopeFactory ?? throw new ArgumentNullException("contextScopeFactory");
_roleRepository = roleRepository ?? throw new ArgumentNullException("roleRepository");
}
public async Task CreateRoleAsync(string roleName)
{
using(var contextScope = _contextScopeFactory.Create())
{
Role newRole = _roleRepository.CreateRole(roleName);
await contextScope.SaveChangesAsync();
}
}
}
The ContextScopeFactory comes from a unit of work pattern called Medhime DbContextScope to manage the scope of a DbContext and provide an ambient context locator for the Repository to find the scope it is under to resolve an instance of a DbContext.
The RoleRepository is used to wrap DbContext operations functions including creation of entities (Role and Log) to ensure that these are given enough information to create a valid entity and associated to the DbContext.
public class RoleRepository : IRoleRepository
{
private readonly IAmbientDbContextLocator _contextLocator = null;
public RoleRepository(IAmbientDbContextLocator contextLocator)
{
_contextLocator = contextLocator ?? throw new ArgumentNullException("contextLocator");
}
private DbEntities Context => _contextLocator.Get<DbEntities>();
Role IRoleRepository.CreateRole(string roleName)
{
// Can do validation for duplicate roles, etc...
var role = new Role
{
RoleName = roleName,
Log = new Log();
}
Context.Roles.Add(role);
}
}
This repository method probably looks quite similar to your Service code, but it is just serving as a factory method for our entity. Note that the Context is fetched from our ambient context scope locator, and while we are adding our new entity to the Context within that context scope, we aren't calling a SaveChanges(). That is the responsibility of the code managing the Context Scope. (Unit of Work). At the end of the day though, this Repository implementation is done so that all of this "work" against data can be mocked out when we want to test our Service. Where we might want to test this repository behaviour out, that would be best served by an integration test running against a database. Business logic is what unit tests would be concerned with and the idea would be that this business logic would be in our service, it just counts on the repository to handle data.
Now a unit test can mock out the IDbContextScopeFactory and the IRoleRepository dependencies for this service:
[Test]
public void EnsureCreateRoleCreatesANewRole()
{
const string TestRoleName = "Boss";
var mockContextScope = new Mock<IDbContextScope>();
var mockContextScopeFactory = new Mock<IDbContextScopeFactory>();
var mockRepository = new Mock<IRoleRepository>();
var stubRole = new Role
{
RoleId = 1,
RoleName = TestRoleName,
Log = new Log();
};
mockContextScopeFactory.Setup(x => x.Create()).Returns(mockContextScope.Object);
mockRepository.Setup(x => x.CreateRole(TestRoleName)).Returns(stubRole);
var testService = new RoleService(mockContextScopeFactory.Object, mockRepository.Object);
await testService.CreateRole(TestRoleName);
mockRepository.Verify(x=> x.CreateRole(TestRoleName), Times.Once);
mockContextScope.Verify(x => x.SaveChanges(), Times.Once);
}
Now this is a very bare-bones example as the service isn't really doing any business logic. Typically the code under test would do something more than simply attempt to create an entity without first performing some validation or such. This test does assert that the repository was given a request to create the role with the expected value, and that the Context Scope was told to save the changes. Whatever situational conditions that the code under test might encounter (duplicate names, handling a null/empty name, etc.) can be handled by test cases where you can assert values returned or verify that specific methods should, or should not be called. You can also assert whether exceptions are handled by having your mocks throw an exception when called rather than returning a value.
Testing with dependency injection (and inversion of control) opens the way for those dependencies to be substituted with mocks by the tests to take over responsibility for inner logic and state. Your original code structure scopes a DbContext inside the method and that DbContext is expecting a database so it cannot be "mocked out" since the code under test has no way to allow it to be substituted.
Hopefully this gives you some ideas about how to restructure code and write new classes and methods to be accommodating for unit testing.
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/457267.html
下一篇:@Cacheable測驗方法
