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;

}