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

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

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;
    }

}

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

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

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

Спасибо заранее.

Ответ 1

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

  • Подкласс вашего класса в unit test и замените вашу приватную переменную с помощью mock-объекта в ней (добавив публичный сеттер или в конструктор). Вероятно, вам нужно защитить переменную.
  • Создайте защищенный getter для этой частной переменной и переопределите его в тестировании подкласса, чтобы вернуть объект-макет вместо фактической частной переменной.
  • Создайте защищенный метод factory для создания объекта ISnapshot и переопределите его в тестировании подкласса, чтобы вернуть экземпляр макетного объекта вместо реального. Таким образом, конструктор получит правильное значение с самого начала.
  • Параметризовать конструктор, чтобы взять экземпляр ISnapshot.

Ответ 2

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

Ответ 3

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

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

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

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

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

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

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

Мне нужно установить значение XYZ в моем тестовом примере. Я решил проблему, используя этот syntex.

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

В приведенном выше случае мы можем сделать это следующим образом:

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

Ответ 7

Вероятно, вам придется реорганизовать свой класс таким образом, чтобы он был введен с другой зависимостью для 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;
 }

}

Ответ 8

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

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

Ответ 9

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

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;