Статьи

Устаревший код для тестируемого кода № 4: больше средств доступа!

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

Мы говорили о методах доступа «сеттер» как о средстве ввода значений. Другая сторона медали — это когда мы хотим знать, что внутри нашего объекта что-то произошло. Например, если внутреннее состояние изменилось или был вызван непубличный метод. 

На самом деле это похоже на вопрос «если дерево упало в лесу»: почему нас волнует, изменилось ли  внутреннее  состояние?

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

Чтобы проверить внутреннее состояние, мы можем добавить метод «getter». Добавить функцию «получения» легко, и если она не имеет логики (и не должна), она может раскрыть информацию без какого-либо вреда. Если инструмент рефакторинга просит вас добавить «сеттер», вы можете установить его как частный, так что никто больше не будет его использовать.

СМЕНА РОЛЕЙ

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

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

Посмеиваясь над   методом getBank, мы можем вернуть  mockBank  (в соответствии с нашими инструментами выбора):

when(testedObject.getBank()).thenReturn(mockBank);

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

verify(account).setBalance(3);

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

В этом примере   функция addMoney вычисляет  interimBalance  перед установкой значения обратно в  currentBalance .

public void addMoney(int amount) {
    int interimBalance = currentBalance;
    interimBalance += amount;
    currentBalance = interimBalance;
}

Если мы хотим проверить `currentBalance` перед вычислением, мы можем изменить метод так:

public void addMoney(int amount) {
    int interimBalance = setInterim(currentBalance);
    interimBalance += amount;
    currentBalance = interimBalance;
}protected void setInterim (int balance){
    return balance;
}

Тогда в нашем тесте мы можем использовать проверку в качестве предварительного условия:

verify(account).setInterim(100);

Добавление аксессоров — это решение проблемы, созданной до того, как мы подумали о тестах: дизайн не достаточно модульный и имеет много обязанностей. Он содержит в себе информацию, и тесты (и будущие клиенты) не могут получить к ней доступ. Если бы мы написали «правильно» в первый раз, класс бога, вероятно, был бы написан как набор классов. С нашими тестами на месте, мы хотим получить модульную конструкцию.

Тесты дают нам возможность сменить код. Как и инструменты автоматизированного рефакторинга. Мы можем начать разделение еще до наших тестов, используя шаблон рефакторинга Extract Method. 

Мы собираемся обсудить это дальше.