Чтение онлайн

на главную

Жанры

Программирование. Принципы и практика использования C++ Исправленное издание
Шрифт:

Для того чтобы протестировать функцию

do_dependent
, мы должны проанализировать ряд ее свойств.

• Входные данные функции

• Значение переменной

a
.

• Значения переменной

b
и переменной типа
int
, на которую ссылается переменная
b
.

• Ввод из потока

cin
(в переменную
val
) и состояние потока
cin
.

• Состояние потока

cout
.

• Значение переменной

vec
, в частности значение
vec[val]
.

• Выходные данные функции

• Возвращаемое значение.

• Значение переменной

типа
int
, на которую ссылается переменная
b
(мы ее инкрементировали).

• Состояние объекта

cin
(проверьте состояния потока и формата).

• Состояние объекта

cout
(проверьте состояния потока и формата).

• Состояние массива

vec
(мы присвоили значение элементу
vec[val]
).

• Любые исключения, которые мог сгенерировать массив

vec
(ячейка
vec[val]
может находиться за пределами допустимого диапазона).

Это длинный список. Фактически он длиннее, чем сама функция. Он отражает наше неприятие глобальных переменных и беспокойство о неконстантных ссылках (и указателях). Все-таки в функциях, которые просто считывают свои аргументы и выводят возвращаемое значение, есть своя прелесть: их легко понять и протестировать.

Как только мы идентифицировали входные и выходные данные, мы тут же оказываемся в ситуации, в которой уже побывали, тестируя

binary_search
. Мы просто генерируем тесты с входными значениями (для явного и неявного ввода), чтобы увидеть, приводят ли они к желаемым результатам (явным и неявным). Тестируя функцию
do_dependent
, мы могли бы начать с очень большого значения переменной
val
и отрицательного значения переменной
val
, чтобы увидеть, что произойдет. Было бы лучше, если бы массив
vec
оказался вектором, предусматривающим проверку диапазона (иначе мы можем очень просто сгенерировать действительно опасные ошибки). Конечно, мы могли бы поинтересоваться, что сказано об этом в документации, но плохие функции, подобные этой, редко сопровождаются полной и точной спецификацией, поэтому мы просто “сломаем” эту функцию (т.е. найдем ошибки) и начнем задавать вопросы о ее корректности. Часто такое сочетание тестирования и вопросов приводит к переделке функции.

26.3.3.2. Управление ресурсами

Рассмотрим бессмысленную функцию.

void do_resources1(int a, int b, const char* s) // плохая функция

// неаккуратное использование ресурсов

{

FILE* f = fopen(s,"r"); // открываем файл (стиль C)

int* p = new int[a]; // выделяем память

if (b<=0) throw Bad_arg; // может генерировать исключение

int* q = new int[b]; // выделяем еще немного памяти

delete[] p; // освобождаем память,

// на которую ссылается указатель p

}

Для того чтобы протестировать функцию

do_resources1
, мы должны проверить, правильно ли распределены ресурсы, т.е. освобожден ли выделенный ресурс или передан другой функции.

Перечислим очевидные недостатки.

• Файл

s
не закрыт.

• Память, выделенная для указателя

p
, не освобождается, если
b<=0
или если второй оператор new генерирует исключение.

• Память, выделенная для указателя

q
, не освобождается, если
0<b
.

Кроме того, мы всегда

должны рассматривать возможность того, что попытка открыть файл закончится неудачей. Для того чтобы получить этот неутешительный результат, мы намеренно использовали устаревший стиль программирования (функция
fopen
— это стандартный способ открытия файла в языке C). Мы могли бы упростить работу тестировщиков, если бы просто написали следующий код:

void do_resources2(int a, int b, const char* s) // менее плохой код

{

ifstream is(s); // открываем файл

vector<int>v1(a); // создаем вектор (выделяем память)

if (b<=0) throw Bad_arg; // может генерировать исключение

vector<int> v2(b); // создаем другой вектор (выделяем память)

}

Теперь каждый ресурс принадлежит объекту и освобождается его деструктором. Иногда, чтобы выработать идеи для тестирования, полезно попытаться сделать функцию более простой и ясной. Общую стратегию решения задач управления ресурсами обеспечивает метод RAII (Resource Acquisition Is Initialization — получение ресурса есть инициализация), описанный в разделе 19.5.2.

Отметим, что управление ресурсами не сводится к простой проверке, освобожден ли каждый выделенный фрагмент памяти. Иногда мы получаем ресурсы извне (например, как аргумент), а иногда сами передаем его какой-нибудь функции (как возвращаемое значение). В этих ситуациях довольно трудно понять, правильно ли распределятся ресурсы. Рассмотрим пример.

FILE* do_resources3(int a, int* p, const char* s) // плохая функция

// неправильная передача ресурса

{

FILE* f = fopen(s,"r");

delete p;

delete var;

var = new int[27];

return f;

}

Правильно ли, что функция

do_resources3
передает (предположительно) открытый файл обратно как возвращаемое значение? Правильно ли, что функция
do_resources3
освобождает память, передаваемую ей как аргумент
p
? Мы также добавили действительно коварный вариант использования глобальной переменной var (очевидно, указатель). В принципе передача ресурсов в функцию и из нее является довольно распространенной и полезной практикой, но для того чтобы понять, корректно ли выполняется эта операция, необходимо знать стратегию управления ресурсами. Кто владеет ресурсом? Кто должен его удалять/освобождать? Документация должна ясно и четко отвечать на эти вопросы. (Помечтайте.) В любом случае передача ресурсов изобилует возможностями для ошибок и представляет сложность для тестирования.

Обратите внимание на то, что мы (преднамеренно) усложнили пример управления ресурсами, использовав глобальную переменную. Если в программе перемешано несколько источников ошибок, ситуация может резко ухудшиться. Как программисты мы стараемся избегать таких ситуаций. Как тестировщики — стремимся найти их.

26.3.3.3. Циклы

Мы уже рассматривали циклы, когда обсуждали функцию
binary_search
.

Поделиться:
Популярные книги

Толян и его команда

Иванов Дмитрий
6. Девяностые
Фантастика:
попаданцы
альтернативная история
7.17
рейтинг книги
Толян и его команда

Кодекс Крови. Книга IХ

Борзых М.
9. РОС: Кодекс Крови
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Кодекс Крови. Книга IХ

Кодекс Охотника. Книга XV

Винокуров Юрий
15. Кодекс Охотника
Фантастика:
попаданцы
аниме
5.00
рейтинг книги
Кодекс Охотника. Книга XV

Мастер Разума II

Кронос Александр
2. Мастер Разума
Фантастика:
героическая фантастика
попаданцы
аниме
5.75
рейтинг книги
Мастер Разума II

Идеальный мир для Социопата 4

Сапфир Олег
4. Социопат
Фантастика:
боевая фантастика
6.82
рейтинг книги
Идеальный мир для Социопата 4

Проклятый Лекарь V

Скабер Артемий
5. Каратель
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Проклятый Лекарь V

Live-rpg. эволюция-5

Кронос Александр
5. Эволюция. Live-RPG
Фантастика:
боевая фантастика
5.69
рейтинг книги
Live-rpg. эволюция-5

Сонный лекарь 6

Голд Джон
6. Сонный лекарь
Фантастика:
альтернативная история
аниме
5.00
рейтинг книги
Сонный лекарь 6

Сумеречный стрелок

Карелин Сергей Витальевич
1. Сумеречный стрелок
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Сумеречный стрелок

Мимик нового Мира 8

Северный Лис
7. Мимик!
Фантастика:
юмористическая фантастика
постапокалипсис
рпг
5.00
рейтинг книги
Мимик нового Мира 8

Титан империи 4

Артемов Александр Александрович
4. Титан Империи
Фантастика:
боевая фантастика
попаданцы
аниме
5.00
рейтинг книги
Титан империи 4

Бездомыш. Предземье

Рымин Андрей Олегович
3. К Вершине
Фантастика:
фэнтези
попаданцы
рпг
5.00
рейтинг книги
Бездомыш. Предземье

Возвышение Меркурия. Книга 16

Кронос Александр
16. Меркурий
Фантастика:
попаданцы
аниме
5.00
рейтинг книги
Возвышение Меркурия. Книга 16

Генерал Скала и сиротка

Суббота Светлана
1. Генерал Скала и Лидия
Любовные романы:
любовно-фантастические романы
6.40
рейтинг книги
Генерал Скала и сиротка