我刚开始接触单元测试,现在才开始理解仓储模式和IoC。但是我认为自己还没有完全理解,因为其中有些部分似乎有点愚蠢。让我来解释一下。
我的控制器:
public class UserProfileController : ApiController
{
private IUserProfileRepository repository;
// Optional constructor, passes repository, allows dependency injection
public UserProfileController(IUserProfileRepository userProfileRepository)
{
this.repository = userProfileRepository;
}
// GET api/UserProfile
// Returns a list of all users
public IEnumerable<UserProfile> Get()
{
// Only Admins can see a list of users
if (Roles.IsUserInRole("Admin"))
{
return repository.Get();
}
else
{
throw new HttpResponseException(
new HttpResponseMessage(HttpStatusCode.Forbidden)
{
ReasonPhrase = "Administrator access required"
});
}
}
// Other methods, etc.
请注意,我有一个依赖项Roles.IsUserInRole("Admin"),我无法抽象出来,这会导致一些问题。
我的典型的repo接口:
public interface IUserProfileRepository : IDisposable
{
IEnumerable<UserProfile> Get();
// Other methods, etc.
}
代码库:
public class UserProfileRepository : IUserProfileRepository, IDisposable
{
private OfootContext context;
public UserProfileRepository(OfootContext context)
{
this.context = context;
}
public IEnumerable<UserProfile> Get()
{
return context.UserProfiles.AsEnumerable();
}
// ... More code
现在一切看起来都很好,我已经将业务访问层从业务逻辑中抽象出来,现在我可以创建一个虚假的存储库来运行单元测试。
虚假存储库:
public class FakeUserProfileRepository : IUserProfileRepository, IDisposable
{
private List<UserProfile> context;
public FakeUserProfileRepository(List<UserProfile> context)
{
this.context = context;
}
public IEnumerable<UserProfile> Get()
{
return context.AsEnumerable();
}
测试:
[TestMethod]
public void GetUsers()
{
// Arrange
var items = new List<UserProfile>()
{
new UserProfile
{
UserId = 1,
Username = "Bob",
},
new UserProfile
{
UserId = 2,
Username = "Bob2",
}
};
FakeUserProfileRepository repo = new FakeUserProfileRepository(
items);
UserProfileController controller = new UserProfileController(
repo);
// Act
IEnumerable<UserProfile> result = controller.Get();
// Assert
Assert.IsNotNull(result);
}
现在我们已经在同一页面上了(随时指出任何“代码气味”),以下是我的想法:
- 假存储库要求我重新实现所有的Entity Framework逻辑,并将其更改为处理List对象。这需要更多的工作和更多的调试链接。
- 如果单元测试通过,它仍然无法说明我访问EF的代码的任何内容,因此我的应用程序仍有可能失败。这意味着我需要单独测试我的EF代码并使其连接到数据库。
- 从第1点可以看出,如果单元测试没有测试EF代码,则它只处理我的控制器中的身份验证、授权和用户创建代码。但它无法这样做,因为WebSecurity和Roles类会访问我的数据库。
- 如果我将使用数据库来测试EF代码(参见点#2),并且需要一个数据库来测试控制器代码(用于身份验证和授权,参见点#3),那么使用存储库进行抽象化还有什么意义呢?为什么不直接使用IContext之类的方式抽象化上下文,并连接一个使用DropCreateDatabaseAlways类填充的测试数据库呢?
如果我确实找到了一种将用户帐户垃圾邮件抽象化的方法,那么我仍然只是在移动代码和创建更多的代码(甚至可能是两倍?因为我需要创建假文件),而我可以直接替换Context。
我的问题是:我错过了什么?它是一个整体概念还是某个特定的东西?