Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Орлов_Технологии разработки программного обеспе...doc
Скачиваний:
106
Добавлен:
07.09.2019
Размер:
4.57 Mб
Скачать

Import ПосещениеКафе;

public class ТестЛакомки extends TestCase

{

public ТестЛакомки (String name)

{

super(name);

}

public void тестСоздатьПосещениеКафе()

{

ПосещениеКафе v = new ПосещениеКафе();

}

}

ПосещениеКафе.java

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);

// занести в 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) последнийЗамер =