Этот пост является частью серии « Устаревший код для тестируемого кода ». В этой серии мы поговорим о том, как выполнить шаги по рефакторингу перед написанием тестов для унаследованного кода, и о том, как они облегчают нашу жизнь.
Добавление средств доступа к частным данным о состоянии — это признание того, что либо наш дизайн неверен, либо мы добавляем средства доступа исключительно для тестирования. Если это государство раньше было частным, то почему мы это разоблачаем? И если это так, возможно, тесты — не единственные клиенты этих данных.
Нам нужен метод доступа в двух случаях:
- Когда нам нужно ввести значения, которые в противном случае трудно внедрить
- Когда нам нужно исследовать внутреннее состояние, чтобы понять влияние нашей работы
В обоих случаях мы добавляем функции, которые раньше не были нужны. Это означает, что абоненту не нужно было вводить или проверять данные. Теперь наши тесты, другой клиент, требуют дополнительной точки входа. В этой статье я сконцентрируюсь на средствах доступа 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; }
Используя этот метод, мы пропускаем насмешку (и уменьшаем сцепление) за счет конструктивных изменений. Мы не можем избежать компромиссов.
Есть последний вариант для ввода значений, если язык поддерживает это: Отражение. Если мы используем отражение в тесте, нет необходимости вносить изменения в тестируемый код. Тем не менее, тесты с использованием рефлексии столь же хрупки, как и тесты с насмешками, а иногда даже больше. Доступ к отражению хрупок и не рефактивен. Поэтому этот метод следует считать последним в предлагаемых решениях.
Это все на данный момент. В следующий раз мы продолжим обсуждение методов доступа с «получателями».