改造依赖注入和自动测试:工厂和加载/列表方法混淆

时间:2013-02-13 17:27:54

标签: c# asp.net-mvc-4 dependency-injection inversion-of-control

我正在研究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. 在构造函数中将Repository注入为依赖项
  2. 将GetByUserName更改为不再是User
  3. 中的静态方法
  4. 做某种工厂,以便能够在GetByUserName方法中创建User实例
  5. 我无法弄清楚如何以合理的方式完成所有这些工作。 #1是微不足道的,但#2和#3不那么重要。以下是我正在考虑如何做#2:

    1. 保持结构大致不变,只需删除static关键字。这意味着,为了通过用户名获取用户,您必须创建User对象的实例,然后调用GetByUserName方法。这并没有通过我高度复杂的气味测试。
    2. 将加载和保存用户的责任移至某种UserCollection对象。这对我来说似乎很可疑,因为它意味着几乎每个业务对象都需要一个底层的集合对象,因为它会让我的域模型变得更加贫乏。
    3. 直接与存储库接口。现在,控制器可能会调用User.GetByUserName()来获取用户域对象以执行某些操作。相反,控制器将知道存储库并调用Repository.list()。这对我来说似乎是漏洞 - 控制器突然觉得它正在做生意。
    4. 然后是#3。看起来我的选择是要么使用我的IOC容器来注入工厂方法(例如autofac工厂委托),要么为每个对象手动创建工厂。使用autofac方法似乎很漏洞因为我必须在单元测试中使用autofac或者为每个对象存根工厂委托。为所有对象制作工厂似乎很麻烦,因为在某些时候我只是为了能够在不了解我的IOC容器的情况下使用DI而给开发人员施加了大量工作量。

      我已经尽我所能搜索这些主题,但我真的没有看到很多不涉及上述缺点的具体例子。

      在不失去“优秀设计”或“太多工作”的情况下解决#2和#3的一些技巧是什么?

2 个答案:

答案 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类上,并让它执行上面的一行。

免责声明:我没有测试过此代码。