TDD: Статические методы, внедрение зависимостей, кэширование и вы! - PullRequest
5 голосов
/ 02 марта 2009

Надеюсь, я смогу объяснить это несколько прилично, потому что сегодня в моем мозгу перегорел предохранитель. Я изучаю TDD на C #, поэтому я все еще пытаюсь перенастроить свой мозг, чтобы соответствовать ему.

Допустим, у меня есть класс Пользователь , у которого ранее был статический метод для извлечения объекта Пользователь (упрощено ниже).

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

Так что я пытаюсь переписать это, чтобы использовать внедрение зависимостей, чтобы я мог подделать метод "GetUserFromDatabase", если он должен идти туда. Это означает, что я должен сделать функцию не статичной. Принимая во внимание, что уровень доступа к данным будет конструировать объект пользователя из базы данных, сопоставляя возвращенные столбцы со свойствами объекта, при извлечении из кэша будет возвращаться объект истинного синего цвета Пользователь . Однако в нестатическом методе я не могу просто сказать

this = GetUserFromCache(username);

Потому что так просто не работает. Хотя я ни в коем случае не являюсь мировым экспертом в том, как танцевать вокруг этого с помощью ОО, похоже, мне почти нужно было бы извлечь объект User из кэша и написать другую функцию отображения, которая сохранит возвращенные свойства объекта User в новом Пользовательский экземпляр.

Какое решение здесь? Какое-то волшебство OO я пропускаю? Является ли единственное решение для реорганизации всего, чтобы использовать фабрики вместо того, чтобы иметь логику реализации в самом объекте? Или я слишком долго смотрел на это и упускал что-то совершенно очевидное?

Ответы [ 4 ]

7 голосов
/ 02 марта 2009

Я не думаю, что вы упускаете какое-то волшебство, и я думаю, что рефакторинг для удаления персистентного кода из ваших бизнес-объектов и в ваш уровень персистентности - это правильный путь как с точки зрения модульного тестирования, так и с точки зрения проектирования. Возможно, вы захотите подумать о том, чтобы кеш располагался между вашим бизнес-уровнем и уровнем персистентности, опосредуя поиск / обновление ваших бизнес-объектов для упрощения работы. Вы должны быть в состоянии смоделировать / подделать ваш кеш и уровень персистентности, если вы разделяете вещи таким образом.

4 голосов
/ 02 марта 2009

Код модульного тестирования, который выполняет обработку даты на основе сегодняшней даты

До

  • Существует некоторый код, который использует базу данных для извлечения пользователя и помещения его в кеш.

* После +1011 *

  • Существует некоторый код, который использует базу данных для извлечения пользователя
  • Есть некоторый код, который помещает пользователя в кеш.

Эти два набора кода не должны зависеть друг от друга.

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 - это способ абстрагироваться над ними.

1 голос
/ 02 марта 2009

В вашем примере мне не хватает контекста вашего вызова 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);
  }
}

Теперь, в зависимости от того, что вы хотите, вы можете вставить свой фальшивый, кеш или базу данных в ваш фасадный объект, и если вы используете ваш кеш-объект, вы можете добавить свою фальшивую базу данных в него по желанию (или даже другой кеш, если вы действительно хотели к).

Конечно, фактический механизм внедрения зависит от вашего DI-контейнера и может потребовать некоторого дополнительного кода в качестве открытых свойств или полей конструктора.

0 голосов
/ 02 апреля 2009

Взгляните на Рефакторинг статического метода / статического поля для тестирования

Предлагаемый подход может сработать для вас, если по какой-то причине вы не можете все реорганизовать, чтобы разделить проблемы, как предложено в другом ответе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...