我正在研究MVC项目的新阶段。第一阶段相当小,不是很复杂。下一阶段将非常复杂和更大。在第一阶段,我们讨论过,然后放弃了出于各种原因进行彻底自动化测试的想法。
在新阶段,我希望我们的团队投资自动化测试。我不认为没有依赖注入我们可以做任何有用的事情,因为我们想要删除数据库工作以使我们的测试可控。
我所挣扎的是将依赖注入改造到我的业务层。我们现有的业务对象具有状态,行为,并负责加载自己的实例(通过静态方法)。例如,我们可能有一个过于简化的例子:
public class User
{
public User(string userName)
{}
public bool Authenticate()
{}
public static User GetByUserName(string userName)
{
//Do some DB querying, and then map the data object to a new instance:
UserRepository repository = new UserRepository();
var databaseObject = repository.list(u => u.userName = userName);
User user = new User();
user.userName = databaseObject.userName;
// populate other fields
return user;
}
}
从快速浏览一下,我似乎需要:
我无法弄清楚如何以合理的方式完成所有这些工作。 #1是微不足道的,但#2和#3不那么重要。以下是我正在考虑如何做#2:
然后是#3。看起来我的选择是要么使用我的IOC容器来注入工厂方法(例如autofac工厂委托),要么为每个对象手动创建工厂。使用autofac方法似乎很漏洞因为我必须在单元测试中使用autofac或者为每个对象存根工厂委托。为所有对象制作工厂似乎很麻烦,因为在某些时候我只是为了能够在不了解我的IOC容器的情况下使用DI而给开发人员施加了大量工作量。
我已经尽我所能搜索这些主题,但我真的没有看到很多不涉及上述缺点的具体例子。
在不失去“优秀设计”或“太多工作”的情况下解决#2和#3的一些技巧是什么?
答案 0 :(得分:1)
User
类不应该知道存储库恕我直言。如果您在存储库中连接,则很难对User
类进行单元测试,如果您决定将存储库放在不同的程序集中,则会创建循环引用。
典型的设计是使用IUserRepository
接口定义您的CRUD方法(例如GetUserByUserName
)和一个或多个指向实际数据源的UserRepository
类。
如果您有任何需要使用UserRepository
(如控制器)的内容,请将其注入IUserRepository
。这样,您就可以使用mock / stub存储库对控制器进行单元测试。
您的存储库将了解您的业务类,并且可以(松散地)耦合到其他存储库(例如,Role
存储库可以注入IUserRepository
以使用户处于角色中。< / p>
如果你想从所有类的一个“主”存储库开始,那么你可能会在以后拆分存储库(这可能不是一件大事)。
所以我建议选项#2和子选项#2(称之为UserRepository
而不是UserCollection
)。
答案 1 :(得分:0)
如何实现这一点主要取决于您希望从UserRepository
中放置检索数据库对象的逻辑并从中填充User
实例。有些人喜欢拥有可以检索自己数据的“智能”模型,但其他人却不喜欢。
听起来你想拥有一个有点聪明的User
课程。我认为你可以通过将UserRepository
注入User
类的构造函数来实现这一点,而不是创建另一个 User
实例,只需使用存储库即可填充此User
实例。
以下是一个示例实现。我的期望是您正在使用IoC容器,并且您将在其中注册IUserRepository接口。我假设您需要一个用户存储库的接口,因为您可能想要将其模拟出来进行单元测试等。
public class User
{
public User(IUserRepository repository, string userName)
{
var databaseObject = repository.list(u => u.userName = userName);
this.userName = databaseObject.userName;
// populate other fields
}
public bool Authenticate()
{}
}
现在,您应该能够通过IoC容器实例化User对象。例如,如果您正在使用Ninject,那么我认为语法如下:
var user = kernel.Get<User>(new ConstructorArgument("userName", "<actual username value>"));
使用Unity我相信它会是这样的:
container.Resolve<User>(new ParameterOverride("userName", "<actual username value>"))
根据您的IoC容器,您可能还需要注册User类,即使它没有实现接口,并且您并没有真正尝试将其与任何内容交换。可能有必要将已注册的IUserRepository与User类的构造函数参数连接起来。
如果你真的想保持你的实例化代码干净,那么你可以将静态方法保留在User类上,并让它执行上面的一行。
免责声明:我没有测试过此代码。