Занести в v первый элемент из контейнера посещений
r.устВесНаБулочку(0);
r.устИзменениеВеса(0);
r.устСтоимостьБулочек(v.получитьСтоимость());
r.устПотреблениеБулочек(v.получитьБулочки()):
return r;
}
Отчет.java
public void устВесНаБулочку (double wpb)
{егоВесНаБулочку = wpb;}
public void устИзменениеВес(double kg)
{егоИзменениеВеса = kg;}
public void устСтоимостьБулочек(double ct)
(егоСтоимостьБулочек = ct;}
public void устПотреблениеБулочек (double b)
{егоПотреблениеБулочек = b;}
Предполагаем, что Лакомке разрешено только одно посещение. В этой версии метода создатьОтчет устанавливаются и возвращаются значения свойств Отчета.
Такой способ разработки метода создатьОтчет может показаться странным, ведь его реализация не завершена. Однако преимущество по-прежнему в том, что между каждой компиляцией и тестированием вносятся только контролируемые добавления. Если что-то отказывает, можно просто вернуться к предыдущей версии и начать сначала, необходимость в сложной отладке отсутствует.
Для завершения кода продумаем тесты для Лакомки без посещений и с несколькими посещениями кафе. Начнем с теста и кода для варианта без посещений.
Листинг 16.13.TecтЛакомки.java и Лакомка.jауа
ТестЛакомки.java
public void тестОтчетаБезПосещений()
{
Лакомка g = new Лакомка();
Отчет r= g.создатьОтчет();
assertEquals(0, r.получитьИзменениеВеса(). дельта);
assertEquals(0, r.получитьПотреблениеБулочек(), дельта);
assertEquals(0, r.получитьВесНаБулочку()), дельта;
assertEquals(0, r.получитьСтоимостьБулочек(), дельта);
}
Лакомка.Java
public Отчет создатьОтчет()
{
Отчет r = new Отчет();
if (егоПосещения.size() = 0)
{
r.устВесНаБулочку(0);
r.устИзиенениеВеса(0);
r.устСтоимостьБулочек(0);
r.устПотреблениеБулочек(0);
}
Else
{
ПосещениеКафе v = (ПосещениеКафе) егоПосещения.get(0);
// занести в v первый элемент из контейнера посещений
r.устВесНаБулочку(0);
r.устИзменениеВеса(0);
r.устСтоимостьБулочек(v.получитьСтоимость());
r. устПотреблениеБулочек (v.получитьБулочки ()):
}
return r;
}
Теперь начнем создавать тестовый вариант для нескольких посещений.
Листинг 16.14.ТестЛакомки.jауа
public void тестОтчетаНесколькихПосещений()
{
Лакомка g = new Лакомка();
g.добавить(ПосещениеКафе(7. 87.5, 60.7);
g.добавитьПосещениеКафе(14. 175, 62.1);
g.добавитьПосещениеКафе(28, 350. 64.9);
Отчет r= g.создатьОтчет();
assertEquals(4.2, r.получитьИзменениеВеса(), дельта);
assertEquals(49. r.получитьПотреблениеБулочек(), дельта);
assertEquals(0.086, r.получитьВесНаБулочку(), дельта);
assertEquals(612.5, r.получитьСтоииостьБулочек(), дельта);
}
Мы установили число посещений для Лакомки равным трем. Предполагается, что цена булочки составляет 12,5 руб., а изменение веса — 0,1 кг на одну булочку. Таким образом, за 175 руб. лакомка покупает и съедает 14 булочек, полнея на 1,4 кг.
Но здесь какая-то ошибка. Скорость изменения веса должна определяться коэффициентом 0,1 кг на одну булочку. А если разделить 4,2 (изменение веса) на 49 (количество булочек), то получаем коэффициент 0,086. В чем причина несоответствия?
После размышлений становится понятно, что вес лакомки регистрируется на выходе из кафе. Поэтому приращение веса и потребление булочек во время первого посещения не учитывается. Изменим исходные данные теста.
Листинг 16.15.ТестЛакомки.jауа
public void тестОтчетаНесколькихПосещений()
{
Лакомка g = new Лакомка();
g.добавитьПосещениеКафе(7. 87.5. 60.7);
g.добавитьПосещениеКафе(14. 175. 62.1);
g.добавитьПосещениеКафе(28. 350. 64.9);
Отчет r - g.создатьОтчет();
assertEquals(4.2, r.получитьИзменениеВеса(), дельта);
assertEquals(42, r.получитьПотреблениеБулочек(), дельта);
assertEquals(0.1, r.получитьВесНаБулочку(), дельта);
assertEquals(612.5, r.получитьСтоимостьБулочек(), дельта);
}
Этот тест корректен. Никогда не известно, с чем встретишься при написании тестов. Можно быть уверенным лишь в том, что, определяя понятия дважды (при написании тестов и кода), вы найдете больше ошибок, чем при простом написании кода.
Теперь добавим код, обеспечивающий прохождение теста из листинга 16.15.
Листинг 16.16.Лакомка.java
public Отчет создатьОтчет()
{
Отчет r = new Отчет ();
if (егоПосещения.size() = 0)
{
r.устВесНаБулочку(0);
r.устИзменениеВеса(0);
r.устСтоимостьБулочек(0);
r.устПотреблениеБулочек(0);
}
else if (егоПосещения.size() = 1)
{
ПосещениеКафе v = (ПосещениеКафе) егоПосещения.get(0);
// занести в v первый элемент из контейнера посещений
r.устВесНаБулочку(0);
r.устИзменениеВеса(0);
r.устСтоимостьБулочек(v.получитьСтоимость());
r.устПотреблениеБулочек(v.получитьБулочки());
}
else
{
double первыйЗамер = 0;
double последнийЗамер = 0;
double общаяСтоиность = 0;
double потреблениеБулочек = 0;
for (int i = 0; i < егоПосещения.size(); i++)
// проход по всем элементам контейнера посещений
{
ПосещениеКафе v = (ПосещениеКафе)
егоПосещения.get(i);
// занести в v 1-й элемент из контейнера посещений
if (i = = 0)
{
первыйЗамер = v.получитьВес();
// занести в первыйЗамер вес при 1-м посещении
потреблениеБулочек -= v.получитьБулочки();
}
if (i= = егоПосещения.size()- 1) последнийЗамер =
v.получитьВес();
// занести в последнийЗамер вес при послед, посещении
общаяСтоимость += v.получитьСтоиность();
потреблениеБулочек += v.получитьБулочки();
}
double изменение = последнийЗамер - первыйЗамер;
r.устВесНаБулочкуСизменение/потреблениеБулочек);
r.устИзненениеВеса(иэненение);
r.устСтоиностьБулочек(общаяСтоиность):
r.устПотреблениеБулочек(потреблениеБулочек);
}
return r;
}
Данный код из-за множества специальных случаев выглядит неуклюже. Для устранения специальных случаев нужно провести рефакторинг. Поскольку третий специальный случай наиболее универсален, надо убрать первые два случая.
Когда мы это сделаем, запуск тестового варианта тестОтчетаОдногоПосещения завершится неудачей. Причина в том, что обработка одного посещения включала булочки, купленные только во время первого посещения. А как мы уже обнаружили, если число посещений равно единице, то потребление булочек должно быть равно нулю. Поэтому исправим тестовый вариант и код.
Листинг 16.17.Лакомка.java
public Отчет создатьОтчет()
{
Отчет r = new Отчет();
double первыйЗамер = 0;
double последнийЗамер = 0;
double общаяСтоимость = 0;
double потреблениеБулочек = 0;
for (int i= 0; i< егоПосещения.size(); i++)
// проход по всем элементам контейнера посещений
{
ПосещениеКафе v = (ПосещениеКафе) егоПосещения.get(i);
// занести в v i-й элемент из контейнера посещений
if (i = = 0)
{
первыйЗамер = v. ПолучитьВес();
//занести в первыйЗамер вес при 1-м посещении
потреблениеБулочек -= v.получитьБулочки();
}
if (i= = егоПосещения.size()- 1) последнийЗамер =
v. получитьВес();
// занести в последнийЗамер вес при послед. посещении
общаяСтоимость += v.получитьСтоимость();
потреблениеБулочек += v.получитьБулочки();
}
double изменение = последнийЗамер – первыйЗамер;
r.устВесНаБулочку(изменение/потреблениеБулочек);
r.устИзменениеВеса(изменение);
r.устСтоимостьБулочек(общаяСтоимость);
r.устПотреблениеБулочекСпотреблениеБулочек);
return r;
}
Теперь попытаемся сделать функцию короче и понятнее. Переместим фрагменты кода так, чтобы их можно было вынести в отдельные функции.
Листинг 16.18.Лакомка.java
public Отчет создатьОтчет()
{
Отчет г = new Отчет();
double изменение = 0;
double общаяСтоимость = 0;
double потреблениеБулочек =0;
double первыеБулочки = 0; double wpb =0;
if (егоПосещения.size() > 0)
{
ПосещениеКафе первоеПосещение =
(ПосещениеКафе) егоПосещения.get(0);
ПосещениеКафе последнееПосещение = (ПосещениеКафе)
егоПосещения.get(егоПосещения.size() - 1);
double первыйЗамер = первоеПосещение.получитьВес();
double последнийЗамер =
последнееПосещение. ПолучитьВес();
изменение = последнийЗамер – первыйЗамер;
первыеБулочки = первоеПосещение.получитьБулочки();
for (int i = 0; i < егоПосещения.size(); i++)
{
ПосещениеКафе v = (ПосещениеКафе)
егоПосещения.get(i);
общаяСтоимость += v.получитьСтоимость();
потреблениеБулочек += v.получитьБулочки();
}
потреблениеБулочек -= первыеБулочки;
if (потреблениеБулочек > 0)
wpb = изменение / потреблениеБулочек;
}
r.устВесНаБулочку(wpb );
r.устИзменениеВеса(изненение);
r.устСтоимостьБулочек(общаяСтоиность);
r.устПотреблениеБулочек(потреблениеБулочек);
return r;
}
Листинг 16.18 иллюстрирует промежуточный шаг в перемещении фрагментов кода. На пути к нему мы выполнили несколько более мелких шагов. Каждый из этих шагов тестировался. И вот теперь тесты стали завершаться успешно. Облегченно вздохнув, мы увидели, как можно улучшить код. Начнем с разбиения единственного цикла на два цикла.
Листинг 16.19.Лакомка.java
if (егоПосещения.size() > 0)
{
ПосещениеКафе первоеПосещение =
(ПосещениеКафе) егоПосещения.get(0);
ПосещениеКафе последнееПосещение = (ПосещениеКафе)
егоПосещения.get(егоПосещения.size() - 1):
double первыйЗамер = первоеПосещение.получитьВес();
double последнийЗамер = последнееПосещение.получитьВес();
изменение = последнийЗамер – первыйЗамер;
первыеБулочки = первоеПосещение.получитьБулочки();
for (int i =0; i < егоПосещения.size(); i++)
{
ПосещениеКафе v = (ПосещениеКафе) егоПосещения.get(i);
потреблениеБулочек += v.получитьБулочки();
}
for (int i =0; i < егоПосещения.size(); i++)
{
ПосещениеКафе v = (ПосещениеКафе) егоПосещения.get(i);
общаяСтоимость += v.получитьСтоимость();
}
потреблениеБулочек -= первыеБулочки;
if (потреблениеБулочек > 0)
wpb = изменение / потреблениеБулочек;
}
Выполним тестирование. На следующем шаге поместим каждый цикл в отдельный приватный метод.
Листинг 16.20.Лакомка.java
public Отчет создатьОтчет()
{
Отчет г = new Отчет();
double изменение = 0;
double общаяСтоимость = 0;
double потреблениеБулочек = 0;
double первыеБулочки =0;
double wpb = 0;
if (егоПосещения. Size() > О)
{
ПосещениеКафе первоеПосещение =
(ПосещениеКафе) егоПосещения.get(0);
ПосещениеКафе последнееЛосещение = (ПосещениеКафе)
егоПосещения.get(егоПосещения.size() - 1);
double первыйЗамер = первоеПосещение.получитьВес();
double последнийЗамер =
последнееПосещение.получитьВес();
изменение - последнийЗамер – первыйЗамер;
первыеБулочки = первоеПосещение.получитьБулочки();
потреблениеБулочек = вычПотреблениеБулочек();
общаяСтоимость = вычОбщуюСтоимость();
потреблениеБулочек -= первыеБулочки;
if (потреблениеБулочек > 0)
wpb = изменение / потреблениеБулочек;
}
r.устВесНаБулочку(wpb);
r.устИзменениеВеса(изменение);
r.устСтоимостьБулочек(общаяСтоимость);
r.устПотреблениеБулочек(потреблениеБулочек);
return r;
}
private double вычОбщуюСтоимость()
{
double общаяСтоииость = 0;
for (int i = 0; i < егоПосещения.size(); i++);
{
ПосещениеКафе v = (ПосещениеКафе) егоПосещения.get(i);
общаяСтоимость += v.получитьСтоимость();
}
return общаяСтоимость;
}
private double вычПотреблениеБулочек()
{
double потреблениеБулочек = 0;
for (int i - 0; i < егоПосещения.size(); i++)
{
ПосещениеКафе v = (ПосещениеКафе) егоПосещения.get(i);
потреблениеБулочек += v.получитьБулочки();
}
return потреблениеБулочек;
}
После соответствующего тестирования перенесем обработку вариантов потребления булочек в метод вычПотреблениеБулочек.
Листинг 16.21.Лакомка.java
public Отчет создатьОтчет()
{
…
if (егоПосещения.size() > 0)
{
ПосещениеКафе первоеПосещение =
(ПосещениеКафе) егоПосещения.get(0);
ПосещениеКафе последнееПосещение - (ПосещениеКафе)
егоПосещения.get(егоПосещения.size() - 1);
double первыйЗамер = первоеПосещение.получитьВес();
double последнийЗамер =
последнееПосещение.получитьВес();
изменение = последнийЗамер - первыйЗамер;
потреблениеБулочек = вычПотреблениеБулочек();
общаяСтоимость - вычОбщуюС тонкость ();
if (потреблениеБулочек > 0)
wpb = изменение / потреблениеБулочек;
}
…
return r;
}
private double вычОбщуюСтоимость()
{
double общаяСтоимость = 0;
for (int i= 0; i < егоПосещения.size(); i++);
{
ПосещениеКафе v = (ПосещениеКафе) егоПосещения.get(i);
общаяСтоимость += v.получитьСтоимость();
}
return общаяСтоимость;
}
private double вычПотреблениеБулочек()
{
double потреблениеБулочек = 0;
if (егоПосещения.size() > 0)
{
for (int i = 1; i < егоПосещения.size(); i++)
{
ПосещениеКафе v = (ПосещениеКафе)
егоПосещения.get(i);
потреблениеБулочек += v.получитьБулочки();
}
}
return потреблениеБулочек;
}
Заметим, что функция вычПотреблениеБулочек теперь суммирует потребление булочек, начиная со второго посещения. И опять выполняем тестирование. На следующем шаге выделим функцию для расчета изменения веса.
Листинг 16.22.Лакомка.java
public Отчет создатьОтчет()
{
Отчет r = new Отчет ();
double изменение = 0;
double общаяСтоимость = 0;
double потреблениеБулочек = 0;
double первыеБулочки = 0;
double wpb = 0;
if (егоПосещения.size() > 0)
{
изменение = вычИзменение();
потреблениеБулочек = вычПотреблениеБулочек();
общаяСтоимость = вычОбщуюСтоимость();
if (потреблениеБулочек > 0)
wpb = изменение / потреблениеБулочек;
}
r.устВесНаБулочку(wpb);
r.устИзменениеВеса(изменение);
r.устСтоимостьБулочек(общаяСтоимость);
r.устПотреблениеБулочек(потреблениеБулочек):
return r;
}
private double вычИзменение()
{
double изменение = 0;
if (егоПосещения.size() > 0)
{
ПосещениеКафе первоеПосещение =
(ПосещениеКафе) егоПосещения.get(0);
ПосещениеКафе последнееПосещение = (ПосещениеКафе)
егоПосещения.get;(егоПосещения.sizе() - 1);
double первыйЗамер = первоеПосещение.получитьВес();
double последнийЗамер =
последнееПосещение. получитьВес();
изменение = последнийЗамер - первыйЗамер;
}
return изменение;
}
После очередного запуска тестов переместим условия в главном методе создатьОтчет и подчистим лишние места.
Листинг 16.23.Лакомка.java
public Отчет создатьОтчет()
{
double изменение = вычИзменение();
double потреблениеБулочек = вычПотреблениеБулочек();
double общаяСтоимость = вычОбщуюСтоимость();
double wpb = 0;
if (потреблениеБулочек > 0)
wpb = изменение / потреблениеБулочек;
Отчет г = new Отчет ();
r.устВесНаБулочку(wpb);
r.устИзменениеВеса(изменение);
r.устСтоимостьБулочек(общаяСтоимость);
r.устПотреблениеБулочек(потреблениеБулочек);
return r;
}
private double вычИзменение()
{
double изменение = 0;
if (eroПосещения.size() > 1)
{
ПосещениеКафе первоеПосещение =
(ПосещениеКафе) егоПосещения.get(0);
ПосещениеКафе последнееПосещение = (ПосещениеКафе)
егоПосещения.get(егоПосещения.size() - 1);
double первыйЗамер = первоеПосещение.получитьВес(0;
double последнийЗамер =
последнееПосещение. получитьВес();
изменение = последнийЗамер – первыйЗамер;
}
return изменение;
}
private double вычОбщуюСтоимость()
{
double общаяСтоимость =0;
for (int i= 0; i < егоПосещения.size(); i++);
{
ПосещениеКафе v = (ПосещениеКафе) егоПосещения.get(i);
общаяСтоимость += v.получитьСтоимость();
}
return общаяСтоимость;
}
private double вычПотреблениеБулочек()
{
double потреблениеБулочек = 0;
if (егоПосещения.size() > 1)
{
for (int i = 1; i < егоПосещения.size(); i++)
{
ПосещениеКафе v = (ПосещениеКафе)
егоПосещения.get(i);
потреблениеБулочек += v.получитьБулочки();
}
}
return потреблениеБулочек;
}
После окончательного прогона тестов констатируем, что цель достигнута — код стал компактным и понятным, обязанности разнесены по отдельным функциям.
Таким образом, в рассмотренном подходе программа считается завершенной не тогда, когда она заработала, а когда она стала максимально простой и ясной.
Контрольные вопросы
1. Что такое CRC-карта? Как ее применить для тестирования визуальных моделей?
2. Поясните особенности тестирования объектно-ориентированных модулей.
3. В чем состоит суть методики тестирования интеграции объектно-ориентированных систем, основанной на потоках?
4. Поясните содержание методики тестирования интеграции объектно-ориентированных систем, основанной на использовании.
5. В чем заключаются особенности объектно-ориентированного тестирования правильности?
6. К чему приводит учет инкапсуляции, полиморфизма и наследования при проектировании тестовых вариантов?
7. Поясните содержание тестирования, основанного на ошибках.
8. Поясните содержание тестирования, основанного на сценариях.
9. Чем отличается тестирование поверхностной структуры от тестирования глубинной структуры системы?
10. В чем состоит стохастическое тестирование класса?
11. Охарактеризуйте тестирование разбиений на уровне классов. Как в этом случае получить категории разбиения?
12. Поясните на примере разбиение на категории по состояниям.
13. Приведите пример разбиения на категории по свойствам.
14. Перечислите известные вам методы тестирования взаимодействия классов. Поясните их содержание.
15. Приведите пример стохастического тестирования взаимодействия классов.
16. Приведите пример тестирования взаимодействия классов путем разбиений.
17. Приведите пример тестирования взаимодействия классов на основе состояний. В чем заключается особенность методики «преимущественно в ширину»?
18. Поясните суть предваряющего тестирования.
19. Какую роль в процессе экстремальной разработки играет рефакторинг?