Статьи

Устаревший код к тестируемому коду № 3: добавление методов доступа Setter

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

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

Нам нужен метод доступа в двух случаях:

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

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

ИНЪЕКЦИОННЫЕ ЗНАЧЕНИЯ

Если у нас есть другие методы, нам может не понадобиться метод доступа. Например, у нас есть приватное поле, которое инициализируется в конструкторе:

public Account(Bank bank) {
    this.bank = bank;
    ...

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

public Account() {
    this.bank = new Bank();
    ...

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

public Account() {
    this.bank = BankFactory.getBank();
    ...

В обоих этих случаях мы можем высмеивать создание или статический вызов с помощью электроинструментов. Если мы не можем использовать их, мы можем ввести метод «setter». Этот «сеттер» может быть общедоступным или, по крайней мере, доступным для нашего теста.

public setBank(Bank bank) {
    this.bank = bank;
}

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

Отсюда становится все сложнее. Что если наш «банк» статичен? Рассмотрим частный статический синглтон:

private static Bank theBank = new Bank():

После инициализации его нельзя заменить обычными средствами. Если наш тест требует этого, заменить его проще, добавив наш собственный  mockBank . Таким образом, мы можем добавить статический «сеттер»:

public static void setBank(Bank bank) {
    theBank = bank;
}

Как и прежде, добавление внешнего «сеттера» сопряжено с риском: что если кто-то решит его назвать? Мы можем уменьшить доступность, но риск все еще существует.

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

public void getAccount() {
    Bank tempBank = new Bank();
    ...

Здесь мы не можем получить доступ к  tempBank , потому что он находится в стеке. В этом случае мы можем сделать небольшой рефакторинг, чтобы разрешить инъекцию. Сначала мы извлечем создание в закрытый метод getBank :

private Bank getBank() {
    return new Bank();
}
public void getAccount() {
    Bank tempBank = getBank();
    ...

Используя инструменты рефакторинга, мы можем использовать этот метод для каждого создания. Затем мы можем ввести поле и «установщик» и изменить   метод getAccount :

private Bank bank = Null;
public void setBank(Bank bank) {
    this.bank = bank;
}
private Bank getBank() {
    if (this.bank == Null)
        return new Bank();
    else 
        return this.bank;
}

Используя этот метод, мы пропускаем насмешку (и уменьшаем сцепление) за счет конструктивных изменений. Мы не можем избежать компромиссов.

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

Это все на данный момент. В следующий раз мы продолжим обсуждение методов доступа с «получателями».