Сделать переменную amount закрытым членом класса
Побочные эффекты в классе Dollar?
Округление денежных величин?
equals()
hashCode()
Равенство значению null
Равенство объектов
Теперь, когда определена операция проверки равенства, с ее помощью можно повысить наглядность тестов. По идее, метод Dollar.times() должен возвращать новый объект Dollar, величина которого равна величине исходного объекта (метод которого мы вызываем), умноженной на коэффициент. Однако наш тест не показывает этого явно:
public void testMultiplication() {
Dollar five = new Dollar(5);
Dollar product = five.times(2);
assertEquals(10, product.amount);
product = five.times(3);
assertEquals(15, product.amount);
}
Мы можем переписать первую проверку и сравнить в ней объекты Dollar:
public void testMultiplication() {
Dollar five = new Dollar(5);
Dollar product = five.times(2);
assertEquals(new Dollar(10), product);
product = five.times(3);
assertEquals(15, product.amount);
}
Выглядит неплохо, поэтому перепишем и вторую проверку:
public void testMultiplication() {
Dollar five = new Dollar(5);
Dollar product = five.times(2);
assertEquals(new Dollar(10), product);
product = five.times(3);
assertEquals(new Dollar(15), product);
}
Теперь нам не нужна вспомогательная переменная product, поэтому устраним ее:
public void testMultiplication() {
Dollar five= new Dollar(5);
assertEquals(new Dollar(10), five.times(2));
assertEquals(new Dollar(15), five.times(3));
}
Согласитесь, этот вариант теста значительно нагляднее.
Учтем внесенные изменения. Теперь только класс Dollar использует переменную экземпляра amount, поэтому мы можем сделать ее закрытой:
Dollar
private int amount;
$5 + 1 °CHF = $10, если курс обмена 2:1
$5 * 2 = $10
Сделать переменную amount закрытым членом класса
Побочные эффекты в классе Dollar?
Округление денежных величин?
equals()
hashCode()
Равенство значению null
Равенство объектов
Вычеркиваем еще один пункт из списка задач. Заметьте, мы подвергли себя риску: если тест, проверяющий равенство, не смог бы точно определить корректность операции сравнения, тогда и тест умножения не смог бы проверить, правильно ли оно работает. В TDD принято активное управление риском. Мы не гонимся за совершенством. Выражая все двумя способами – тестами и кодом, – мы надеемся уменьшить дефекты настолько, чтобы уверенно идти дальше. Время от времени наши рассуждения будут нас подводить, позволяя появляться ошибкам. Когда это случится, мы вспомним урок о том, что надо написать тест и двигаться дальше. Все остальное время мы отважно продвигаемся вперед под победно развевающейся зеленой полоской нашего индикатора (вообще-то мой индикатор не развевается, но я люблю помечтать).
Подведем итоги:
• использовали только что разработанную функциональность для улучшения теста;
• заметили, что, если одновременно два теста терпят неудачу, наши дела плохи;
• продолжили несмотря на риск;
• использовали новую функциональность тестируемого объекта для уменьшения зависимости между тестами и кодом.
5. Поговорим о франках
$5 + 1 °CHF = $10, если курс обмена 2:1
$5 * 2 = $10
Сделать переменную amount закрытым (private) членом
Побочные эффекты в классе Dollar?Округление денежных величин?
equals()
hashCode()
Равенство значению null
Равенство объектов
5 CHF * 2 = 1 °CHF
Можем ли мы приступить к реализации первого, самого интересного теста в данном списке? Мне все еще кажется, что это будет слишком большой шаг. Я не представляю себе, как можно написать этот тест за один маленький шажок. Мне кажется, что вначале необходимо создать объект наподобие Dollar, который соответствовал бы не долларам, а франкам. Пусть это будет объект с названием Franc. Для начала объект Franc может функционировать в точности как объект Dollar – если у нас будет такой объект, нам будет проще размышлять о реализации теста, связанного со смешанным сложением двух разных валют.
А если объект Franc работает так же, как объект Dollar, значит, мы можем просто скопировать и слегка отредактировать тест для объекта Dollar:
public void testFrancMultiplication() {
Franc five = new Franc(5);
assertEquals(new Franc(10), five.times(2));
assertEquals(new Franc(15), five.times(3));
}
(Хорошо, что в главе 4 мы упростили тест для Dollar. Благодаря этому работа по редактированию теста существенно упростилась. Похоже, в данной книге дела идут довольно гладко, однако я не могут гарантировать, что в будущем все будет так же хорошо.)
Теперь нам надо получить зеленую полоску. Какой способ будет самым простым? Проще всего скопировать код класса Dollar и заменить Dollar на Franc.
Стоп. Подождите-ка. Я уже вижу, как некоторые наиболее ярые сторонники правильных подходов начинают морщиться и плеваться. Повторное использование кода путем его дублирования через буфер обмена? Пренебрежение абстракцией? А как же все эти разговоры об основополагающих принципах ООП и чистом дизайне?
Если вам не по себе, глубоко вдохните через нос, досчитайте до трех и медленно выдохните через рот. Вам лучше? Теперь вспомните, что наш цикл состоит из пяти этапов. Иногда последовательное выполнение всех этапов занимает всего несколько секунд, однако в любом случае мы обязательно выполняем каждый из них:
1. Написать тест.
2. Добиться его безошибочной компиляции.
3. Запустить тест и убедиться, что он потерпел неудачу.
4. Добиться успешного выполнения теста.
5. Устранить дублирование.
На разных этапах решаются разные задачи, преследуются разные цели. То, что совершенно недопустимо для одного из этапов, может быть вполне приемлемым для другого этапа. Однако в целом методика TDD работает только в случае, если ни один из этапов не упущен. Если вы пропустите хотя бы одно звено, развалится вся цепочка.
Первые три фазы цикла разработки TDD должны выполняться как можно быстрее. Определяющая характеристика этих этапов – скорость. На этих этапах в жертву скорости можно принести очень многое, в том числе чистоту дизайна. Честно говоря, сейчас я несколько волнуюсь. Я только что разрешил вам забыть о принципах хорошего дизайна. Представляю, как вы приходите к своим коллегам, подчиненным и во всеуслышание объявляете: «Кент сказал,