TDD:静态方法,依赖注入,缓存,你!

时间:2009-03-02 20:14:25

标签: c# oop tdd

希望我可以解释一下这一点,因为它今天在我脑中燃烧了一个导火索。我正在用C#学习TDD,所以我仍然试图重新连接我的大脑以适应它。

假设我有一个用户类,之前有一个静态方法来检索User对象(下面简化)。

public static User GetUser(string username)
{
   User user = GetUserFromCache(username);
   if(user == null)
   {
       user = GetUserFromDatabase(username);
       StoreObjectInCache(user);
   }
   return user;
}

所以我试图重写这个以使用依赖注入,所以如果需要去那里我可以伪造出“GetUserFromDatabase”方法。这意味着我必须使该功能不是静态的。数据访问层将从数据库构造用户对象,将返回的列映射到对象属性,从缓存中检索将返回真蓝色用户对象。但是,在非静态方法中,我不能只说

this = GetUserFromCache(username);

因为它不起作用。虽然我不是世界上如何与OO一起跳舞的专家,看起来我几乎必须从缓存中获取User对象并编写另一个映射函数,将返回的User对象属性存储到新的用户实例。

这里有什么解决方案?我失踪了任何OO魔法?是重构所有内容以使用工厂而不是在对象本身中使用实例化逻辑的唯一解决方案吗?或者我是否一直盯着这个太久而且遗漏了一些完全明显的东西?

4 个答案:

答案 0 :(得分:7)

我认为你没有遗漏任何魔法,我认为重构从业务对象和持久层中删除持久性代码是从单元测试和设计角度进行的正确方法。您可能需要考虑将缓存放在业务层和持久层之间,调整业务对象的检索/更新以简化操作。如果你以这种方式分离,你应该可以模拟/伪造你的缓存和持久层。

答案 1 :(得分:4)

Unit testing code that does date processing based on today's date

  • 有些代码使用数据库来获取用户并将其放入缓存中。

  • 有一些代码使用数据库来获取用户
  • 有一些代码将用户置于缓存中。

这两组代码不应相互依赖。

public static Func<string, UserName> Loader {get;set;}

public static Constructor()
{
  Loader = GetFromDataBase;
}

public static User GetUser(string userName)
{
  User user = GetUserFromCache()
  if (user == null)
  {
    user = Loader(userName);
    StoreUserInCache(user);
  }
  return user;
}    

public void Test1()
{
  UserGetter.Loader = Mock.GetUser;
  UserGetter.GetUser("Bob");
}

传统上,将使用接口而不是Func。如果涉及多个方法,则接口是Func的明显选择。如果方法实现本身是静态的,那么Func就是一种对它们进行抽象的方法。

答案 2 :(得分:1)

您在示例中缺少的是您调用“GetUser”的上下文。这可能是因为使用静态方法您无需考虑,因为您可以从任何地方调用它。在DI中,这意味着发件人需要以某种方式引用存储库,这很可能是一个字段。

如果您的缓存是某个对象的字段,可能是一个外观,您可以使用它来使您的缓存成为数据库的代理。

所以你会:

class ApplicationFacade{
  private IUserRepository users = null;

  public doStuff(){
    this.users.GetUser("my-name");
  }
}

其中IUserRepository是缓存,虚假数据库和数据库的通用接口。简单的事情:

interface IUserRepository{
  User GetUser(string username);
}

您的缓存现在可以是实现此接口的简单对象,并且因为缓存被注入,DI容器也可以注入其中。

class Cache : IUserRepository {
  private IUserRepository users = null;
  public User GetUser(string username){
    if (this.NotCached(username)){
      this.ToCache(this.users.GetUser(username));
    }
    return this.FromCache(username);
  }
}

现在根据您的需要,您可以将假冒,缓存或数据库注入到Facade对象中,如果您使用缓存对象,您可以根据需要将虚假数据库注入其中(如果您真的想要,甚至可以注入其他缓存到)。

原因实际注入机制取决于您的DI容器,可能需要一些额外的代码作为公共属性或构造函数字段。

答案 3 :(得分:0)

查看Refactoring static method / static field for Testing

这种方法表明,如果出于某种原因,你无法重构所有内容以解决另一个问题所建议的问题。