Public class ПосещениеКафе
{
}
Этот код компилируется, тест проходит, и мы готовы добавить необходимую функциональность.
Листинг 16.4.ТестЛакомки.jауа и ПосещениеКафе.jауа
ТестЛакомки.java
import junit.framework.*;
import ПосещениеКафе;
Import java.util.Date
public class ТестЛакомки extends TestCase
{
public TecтЛакомки(String name)
{
super(name):
}
public void тестСоздатьПосещениеКафе()
{
Date дата = new Date();
double булочки = 7.0; // 7 булочек
double стоимость = 12.5 * 7;
// цена 1 булочки - 12.5 руб.
double вес = 60.0; // взвешивание лакомки
double дельта = 0.0001;// точность
ПосещениеКафе v =
new ПосещениеКафе(дата, булочки, стоимость, вес);
assertEquals(дата, v.получитьДату( ));
assertEquals(12.5 * 7, v.получитьСтоииость(), дельта);
assertEquals(7.0, v.получитьБулочки(), дельта);
assertEquals(60.0, v.получитьВес(), дельта);
assertEquals(12.5, v.получитьЦену(). дельта);
}
}
ПосещениеКафе.java
import Java.uti1.Date;
public class ПосещениеКафе
{
private Date егоДата;
private double егоБулочки;
private double егоСтоимость;
private double eroBec;
public ПосещениеКафе(Date дата, double булочки,
double стоимость, double вес)
{
егоДата = дата;
егоБулочки = булочки;
егоСтоимость = стоимость;
егоВес = вес;
}
public Date получитьДату() {return егоДата;}
public double получитьБулочки() {return егоБулочки;}
public double получитьСтоимость() {return егоСтоимость;}
public double получитьЦену(){return егоСтоимость/егоБулочки;}
public double получитьВес() {return eroBec;}
}
На этом шаге мы добавили тесты в класс ТестЛакомки, а также добавили методы в класс ПосещениеКафе. Унаследованные методы assertEquals позволяют проводить сравнение ожидаемых и фактических результатов тестирования.
Очевидно, вы удивитесь этому подходу. Неужели нельзя вначале написать весь код класса ПосещениеКафе, а потом создать тесты? Ответ достаточно прост. Написание тестов перед написанием программного кода дает важное преимущество: мы знаем, что весь ранее созданный код компилируется и выполняется. Следовательно, любая ошибка вызывается текущими изменениями, а не более ранним кодом. И значимость этого преимущества усиливается по мере продвижения вперед.
Далее определимся с хранением объектов класса ПосещениеКафе. Очевидно, что свойство егоВес характеризует лакомку. Таким образом, объект ПосещениеКафе записывает часть состояния лакомки па момент посещения кафе. Следовательно, нужно создать объект Лакомка и содержать объекты класса ПосещениеКафе в нем.
Листинг 16.5.ТестЛакомки.java и Лакомка.java
ТестЛакомки.java
import junit.framework.*;
import ПосещениеКафе;
import java.util.Date
public class ТестЛакомки extends TestCase
{
public TecтЛакомки(String name)
{
super(name);
}
…
public void тестСоздатьЛакомку()
{
Лакомка g = new Лакомка();
assertEquals(0, д.получитьЧислоПосещений());
}
}
Лакомка.Java
Public class Лакомка
{
public int получитьЧислоПосещений()
{
return 0;
}
}
Листинг 16.5 показывает начальный шаг. Мы написали новую тестовую функцию тестСоздатьЛакомку. Эта функция создает объект класса Лакомка и затем убеждается, что хранимое количество посещений равно 0. Конечно, реализация метода получитьЧислоПосещений неверна, но она обеспечивает прохождение теста. Это позволит нам в будущем выполнить рефакторинг (для улучшения решения).
Введем в класс Лакомку объект-контейнер, хранящий данные о разных посещениях (как элементы списка в массиве изменяемого размера). Для его создания используем класс-контейнер Array List из библиотеки Java 2. В будущем нам потребуются три метода контейнера: add (добавить элемент в контейнер), get (получить элемент из контейнера), size (вернуть количество элементов в контейнере).
Листинг 16.6.ЛАKOMKА.java
import java.util.ArrayList;
public class Лакомка
{
private ArrayList егоПосещения = new ArrayList();
// создание объекта егоПосещения - контейнера посещений
public int получитьЧислоПосещений ()
{
return егоПосещения.size();
// возврат количества элементов в контейнере
// оно равно количеству посещений кафе
}
}
Отметим, что после каждого изменения мы прогоняем все тесты, а не только функцию тестСоздатьЛакомку. Это дает гарантию, что изменения не испортили уже работающий код.
На следующем шаге следует определить, как к Лакомке добавляется посещение кафе. Так будет выглядеть простейший тестовый вариант:
Листинг 16.7.TecтЛакомки.java
public void тестДобавитьПосещение()
{
double булочки = 7.0; // 7 булочек
double стоимость = 12.5 * 7; // цена 1 булочки = 12.5 руб.
double вес = 60.0; // взвешивание лакомки
double дельта = 0.0001; // точность
Лакомка g = new Лакомка();
g.добавитьПосещениеКафе(булочки, стоимость, вес);
assertEquals(1, g.получитьЧислоПосещений());
}
В этом тесте объект класса ПосещениеКафе не создается. Очевидно, что создавать объект и добавлять его в список должен метод добавитьПосещениеКафе объекта Лакомка.
Листинг 16.8.Лакомка.jауа
public void добавитьПосещениеКафе((double булочки, double стоимость, double вес)
{
ПосещениеКафе v =
new ПосещениеКафе(new Date(), булочки, стоимость, вес);
егоПосещения.add(v);
// добавление эл-та v в контейнер посещений
}
Опять прогоняются все тесты. Анализ программного кода в функциях тестДобавитьПосещение и тестСоздатьПосещениеКафе показывает, что он частично дублируется. Обе функции создают одинаковые локальные переменные и инициализируют их одинаковыми значениями. Чтобы избавиться от дублирования, проведем рефакторинг тестируемой программы и сделаем локальные переменные свойствами класса.
Листинг 16.9.ТестЛакомки.jауа
import junit.framework.*;
import ПосещениеКафе;
import java.util.Date;
public class ТестЛакомки extends TestCase
{
private double булочки - 7.0;// 7 булочек
private double стоимость = 12.5 * 7;
// цена 1 булочки = 12.5 p.
private double вес = 60.0; //взвешивание лакомки
private double дельта = 0.0001; // точность
public ТестЛакомки(String name)
{
super(name);
}
public void тестСоздатьПосещениеКафе()
{
Date дата = new Date();
ПосещениеКафе v = new ПосещениеКафе(дата. булочки.
стоимость, вес);
assertEquals(date, v.получитьДату());
assertEquals(12.5 * 7. v.получитьСтоимость(). дельта);
assertEquals(7.0. v.получитьБулочки(). дельта);
assertEquals(60.0. v.получитьВес(), дельта);
assertEquals(12.5. v.получитьЦену(). дельта):
}
public void тестСоздатьЛакомку()
{
Лакомка g = new Лакомка ();
assertEquals(0. g.получитьЧислоПосещений());
}
public void тестДобааитьПосещение()
{
Лакомка g = new Лакомка();
g.добавитьПосещениеКафе(булочки. стоимость, вес);
assertEquals(1. g.получитьЧислоПосещениРК));
}
}
Еще раз подчеркнем: наличие тестов позволяет определить, что этот рефакторинг ничего не разрушил в программе. Мы будем убеждаться в этом преимуществе постоянно, после очередного применения рефакторинга для реструктуризации программы. Каждый раз после внесения в код изменений запускаются тесты и проверяется работоспособность программы.
Очередная задача — после добавления к Лакомке объектов ПосещениеКафе у Лакомки можно запрашивать генерацию отчетов. Сначала напишем тесты, начнем с простейшего теста.
Листинг 16.10.TecтЛакомки.java
public void тестОтчетаОдногоПосещения()
{
Лакоика g = new Лакоика();
g.добавитьПосещениеКафе(булочки. стоимость, вес);
Отчет r = g.создатьОтчет();
assertEquals(0. r.получитьИзменениеВеса(), дельта);
assertEqualз(булочки, г.получитьПотреблениеБулочек(),
дельта);
assertEquals(0, r.получитьВесНаБулочку(), дельта);
assertEquals(стоимость. r.получитьСтоимостьБулочек(),
дельта);
}
При создании этого тестового варианта мы обдумали детали генерации отчета. Во-первых, Лакомка должна обладать методом создатьОтчет. Во-вторых, этот метод должен возвращать объект класса с именем Отчет. В-третьих, Отчет должен иметь несколько методов-селекторов.
Значения, возвращаемые методами-селекторами, следует проанализировать. Для вычисления изменения веса (или приращения веса на одну булочку) одного посещения кафе недостаточно. Чтобы вычислить эти значения, необходимы, как минимум, два посещения, С другой стороны, одного визита достаточно, чтобы сосчитать потребление и стоимость булочек.
Разумеется, тестовый вариант не компилируется. Поэтому необходимо добавить соответствующие методы и классы. Сначала добавим код, обеспечивающий компиляцию, но не обеспечивающий выполнение тестов.
Листинг 16.11.Лакомка.java, TecтЛакомки.java и Отчет.jаvа
Лакомка.java
public Отчет создатьОтчет()
{
return new Отчет();
}
ТестЛакомки.java
public void тестОтчетаОдногоПосещения()
{
Лакомка g = new Лакомка();
g.добавитьПосещениеКафе(булочки, стоимость, вес);
Отчет r = g.создатьОтчет();
assertEquals(0, r.получитьИзменениеВеса(), дельта);
assertEquals(булочки. r.получитьПотреблениеБулочек(),
дельта);
assertEquals(0. r.получитьВесНаБулочку(), дельта);
assertEquals(cтоимость,. r.получитьСтоимостьБулочек(),.
дельта);
}
Отчет.java
Public class Отчет
{
public double получитьИзменениеВеса()
{return егоИзменениеВеса;}
public double получитьВесНаБулочку()
{return егоВесНаБулочку;}
public double получитьСтоииостьБулочек()
{return егоСтоимостьБулочек;}
public double получитьЛотреблениеБулочек()
{return егоПотреблениеБулочек;}
private double егоИзменениеВеса;
private double егоВесНаБулочку;
private double егоСтоимостьБулочек;
private double егоПотреблениеБулочек;
}
Код в листинге 16.11 компилируется и запускается, но его недостаточно для того, чтобы прошли тесты. Нужен рефакторинг кода. Для начала сделаем минимально возможные изменения.
Листинг 16.12.Лакомка.java и Отчет.java
Лакомка.java
public Отчет создатьОтчет()
{
Отчет r = new Отчет();
ПосещениеКафе v = (ПосещениеКафе) егоПосещения. Get(0);