Как издеваться над частным полем?

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

public class Cache {
    private ISnapshot _lastest_snapshot;

    public ISnapshot LatestSnapshot {
        get { return this._lastest_snapshot; }
        private set { this._latest_snapshot = value; }
    }

    public Cache() {
        this.LatestSnapshot = new Snapshot();
    }

    public void Freeze(IUpdates Updates) {
        ISnapshot _next = this.LastestSnapshot.CreateNext();
        _next.FreezeFrom(Updates);
        this.LastestSnapshot = _next;
    }

}

Я пытаюсь создать модульный тест, который утверждает, что ISnapshot.FreezeFrom(IUpdates) вызывается изнутри Cache.Freeze(IUpdates) . Я предполагаю, что мне следует заменить частное поле _latest_snapshot фиктивным объектом (может быть, неправильное предположение?). Как я могу это сделать, сохранив конструктор без параметров и не прибегая к LatestSnapshot публичному открытию набора?

Если я совершенно неправильно собираюсь написать тест, пожалуйста, тоже укажите на это.

Фактическая реализация ISnapshot.FreezeFrom сама по себе вызывает иерархию других методов с глубоким графом объектов, поэтому я не слишком заинтересован в утверждении графа объектов.

Заранее спасибо.

Ответов (9)

Решение

Я почти цитирую методы из «Эффективной работы с устаревшим кодом» :

  1. Подклассифицируйте свой класс в модульном тесте и замените вашу частную переменную фиктивным объектом в ней (путем добавления общедоступного установщика или в конструкторе). Вероятно, вам придется сделать переменную защищенной.
  2. Создайте защищенный получатель для этой частной переменной и переопределите его в подклассе тестирования, чтобы он возвращал фиктивный объект вместо фактической частной переменной.
  3. Создайте защищенный фабричный метод для создания ISnapshotобъекта и переопределите его в подклассе тестирования, чтобы он возвращал экземпляр фиктивного объекта вместо реального. Таким образом, конструктор с самого начала получит правильное значение.
  4. Конструктор параметризации, экземпляр которого требуется ISnapshot.

Я не думаю, что вам нужно издеваться над частными переменными-членами. Разве не идея насмехаться над тем, чтобы публичный интерфейс объекта работал так, как ожидалось? Частные переменные - это детали реализации, которые не касаются имитации.

Возникает вопрос: каковы будут внешние видимые эффекты, если это сработает?

Что происходит со всеми этими снимками? Один из вариантов - инициализировать кэш с его первым снимком извне, скажем, в конструкторе. Другой вариант - издеваться над тем, что вызывает Snapshot вне кеша. Это зависит от того, что вам небезразлично.

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

В качестве примера того, что я могу попробовать использовать фреймворк Moq:

Mock<ISnapshot> snapshotMock = new Mock<ISnapshot>();
snapshotMock.Expect(p => p.FreezeFrom(expectedUpdate)).AtMostOnce();
Cache c = new Cache(snapshotMock.Object);
c.Freeze(expectedUpdate);

Примечание: я не пытался скомпилировать приведенный выше код. Это просто, чтобы дать пример того, как я подхожу к решению этой проблемы.

Этот ответ может быть простым, но если посмотреть на код, есть ли способ, которым ISnapshot.FreezeFrom(IUpdates) он не будет вызван? Похоже, вы хотите утверждать то, что всегда будет правдой.

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

Может быть, уже слишком поздно отвечать. В любом случае. У меня тоже была похожая проблема.

public class Model
{
  public ISomeClass XYZ{
      get;
      private set;
      }
}

Мне потребовалось установить значение XYZ в моем тестовом примере. Решил проблему с помощью этого синтекса.

Expect.Call(_model.XYZ).Return(new SomeClass());
_repository.ReplayAll();

В приведенном выше случае мы можем сделать это так

Expect.Call(_cache.LatestSnapshot).Return(new Snapshot());
_repository.ReplayAll();

Превратите кеш в шаблон, как показано ниже.

template <typename T=ISnapshot>
public class Cache {
    private T _lastest_snapshot;

    public T LatestSnapshot {
        get { return this._lastest_snapshot; }
        private set { this._latest_snapshot = value; }
    }

    public Cache() {
        this.LatestSnapshot = new Snapshot();
    }

    public void Freeze(IUpdates Updates) {
        T _next = this.LastestSnapshot.CreateNext();
        _next.FreezeFrom(Updates);
        this.LastestSnapshot = _next;
    }

}

В производственном коде выполните:

Cache<> foo;//OR
Cache<ISnapshot> bar;

В тестовом коде выполните:

Cache<MockSnapshot> mockFoo;

Вероятно, вам придется реорганизовать свой класс таким образом, чтобы он был внедрен с другой зависимостью для ISnapshot. Ваш класс останется работать без изменений.

public class Cache {
 private ISnapshot _lastest_snapshot;

 public ISnapshot LatestSnapshot {
  get { return this._lastest_snapshot; }
  private set { this._latest_snapshot = value; }
 }

 public Cache() : this (new Snapshot()) {
 }

 public Cache(ISnapshot latestSnapshot) {
  this.LatestSnapshot = latestSnapshot;
 }

 public void Freeze(IUpdates Updates) {
  ISnapshot _next = this.LastestSnapshot.CreateNext();
  _next.FreezeFrom(Updates);
  this.LastestSnapshot = _next;
 }

}

Вы можете просто добавить метод «setSnapshot (ISnapshot)» в Cache с помощью своего имитационного экземпляра класса.

Вы также можете добавить конструктор, который принимает ISnapshot.