Файл: Отладка и тестирование программ: основные подходы и ограничения (Отладка программ).pdf
Добавлен: 28.03.2023
Просмотров: 169
Скачиваний: 4
И для каждого аспекта качества программы есть соответствующий вид тестирования. Например, функциональное тестирование проверяет пригодность к использованию, правильность работы и защищенность программы.
Классификация тестирования [16.] по масштабу (как их видит тестировщик):
- Модульное тестирование (unit testing) – тестирование отдельных операций, методов и функций;
- Интеграционное тестирование – проверка корректности взаимодействия модулей между собой;
- Системное тестирование – тестирование на уровне пользовательского интерфейса.
С технической точки зрения эти три вида тестирования похожи друг на друга: если есть какой-то инструмент, например, модульного тестирования, то его иногда можно применить и к системному тестированию. Крайне желательно иметь тестировщика у себя в команде, когда проект разрастётся, но кто может быть тестировщиком на начальных этапах [14., 15.]?
-
- Программист или Старший программист (Team Leader) не могут быть тестировщиками. Они ходят только «по протоптанному», жалеют собственный код и не сильно придираются к нему [7.];
- Менеджер проекта, технический писатель, эксперт предметной области или представитель заказчика могут, потому что тестировщик должен не знать деталей реализации, воспринимать программу как чёрный ящик и не быть слишком привязанным к ней [9.];
- Методика test-driven development (TDD) позволяет программисту быть самому себе эффективным тестировщиком. Когда ошибка найдена (самостоятельно или с помощью тестировщика), необходимо отладить программу [9.]:
- Понять суть ошибки и обнаружить её причину;
- Локализовать её в исходном тексте программы;
- Устранить ошибку.
Типичные задачи отладки: узнать текущие значения переменных и выяснить, по какому пути выполнялась программа. Существуют два пути отладки [10.]:
-
- Использование отладчиков («дебаггеров»);
- Логгирование (вывод отладочных сведений в файл).
2.2 Контрактное программирование
Поиск ошибок в программе — это очень дорогостоящее, неприятное и утомительное занятие. Поэтому есть методики, уменьшающие количество ошибок и позволяющие избежать их еще на этапе создания программы. Одна из них – это проектирование по контракту. Design by contract – это метод проектирования программ, основанный на идее взаимных обязательств и преимуществ взаимодействующих элементов программы. Также называется «контрактное программирование». Автор – Бертран Мейер.
Взаимодействующие элементы программы:
– «клиент» – это вызывающая функция, объект или модуль;
– «поставщик» – это вызываемая функция, объект или модуль.
«Контракт» между ними – это взаимные
– обязательства – то, что требуется каждой стороне соблюсти при взаимодействии;
– преимущества – та выгода, которая получается при соблюдении обязательств другой стороной.
Архитектор программы определяет формальные, точные и верифицируемые спецификации интерфейсов.
Как и в бизнесе, клиент и поставщик действуют в соответствии с определенным контрактом. Архитектор программы должен определить формальные, точные и верифицируемые спецификации интерфейсов для функций и методов. Содержание контракта [19.]:
- Предусловия – обязательства клиента перед вызовом функции-поставщика услуги;
- Постусловия – обязательства функции-поставщика, которые обязаны быть выполнены в итоге её работы;
- Инварианты — условия, которые должны выполняться как при вызове функции-поставщика, так и при окончании его работы.
Предусловия, постусловия, инварианты записываются через формальные утверждения корректности — assertions:
- Синтаксис: assertion, ”Сообщение об ошибке!” ;
- Пример: assert 0 <= hour <= 23, ”Hours should be in range of 0..23”;
- Жёсткое падение облегчает проверку выполнения контрактов во время отладки программы;
- Проверка assert работает только в режиме отладки (__debug__ is True).
Библиотека PyContracts позволяет элегантно ввести в Python элементы проектирования по контракту (в том числе и проверку типов). Способы описания предусловий:
- Через параметры декоратора (рисунок 1).
Рисунок 1. Проверка через параметры декоратора
2. Через аннотации типов (рисунок 2).
Рисунок 2. Проверка через аннотации типов
3. Через документ-строки (рисунок 3).
Рисунок 3. Проверка через строки внутри определения функции
Пример контракта для функции умножения матриц приведен на рисунке 4.
Рисунок 4. Пример контракта для функции умножения двух матриц
Следует обратить внимание на то, что можно потребовать не только положительного значения ширины или высоты матрицы, но и того, чтобы у матриц a и b соответствующие размеры были равны друг другу.
Библиотека PyContracts позволяет еще и отключить все проверки, когда программа будет отлажена и отправлена в релиз. Это делается вызовом функции contracts.disable_all() или установкой переменной окружения DISABLE_CONTRACTS.
Плюсы контрактного программирования [11.]:
- улучшает дизайн программы;
- повышает надёжность работы программы;
- повышает читаемость кода, поскольку контракт документирует обязательства функций и объектов;
- увеличивает шанс повторного использования кода;
- актуализирует документацию программного продукта.
2.3 Модульное тестирование
Декомпозиция программы на функции, классы и модули позволяет осуществлять модульное или компонентное тестирование (unit testing). Юниттесты – это способ проверить работу функции или метода отдельно от всей программы, вместе взятой. На рисунке 5 приведен пример юнит тестинга пузырьковой сортировки.
Рисунок 5. Юнит-тестирование пузырьковой сортировки
Поскольку тестирование вручную займет время и его придется делать вручную каждый раз (при этом основная часть программы может ещё не работать), то будет удобнее проверять работу функции отдельно от основного кода и автоматизировать это тестирование. Юнит тест для сортировки из рисунка 5 приведен на рисунке 6.
Рисунок 6. Тест пузырьковой сортировки
Для большей гарантии стоит добавить несколько различных тестов, которых достаточно для проверки работоспособности функции (рисунок 7).
Рисунок 7. Тестирование на нескольких примерах
Общая схема юнит-тестирования приведена на рисунке 8.
Рисунок 8. Общая схема юнит-тестирования
Опережающее тестирование [6.] – способ совмещения роли тестировщика и программиста, при котором сначала пишутся unit-тесты, а потом уже пишется код согласно техническому заданию. Разработка через тестирование или Test-Driven Development (TDD) – это итеративная методика разработки программ, в которой (опережающее) тестирование ставится во главу угла. И оно управляет процессом дизайна программного продукта. Если существующие тесты проходят нормально, значит, в коде нет известных проблем. Создав тест для выявления недостающего функционала, можно чётко выявить задачу, которую нужно решить. И вот такими циклами разработки проект двигается вперед – создается все более новая функциональность программы, которая всегда гарантированно покрыта модульными тестами.
Кроме этого, разработка тестов выявляет дефекты дизайна приложений:
-
- Каковы обязанности тестируемой системы?
- Что и когда она должна делать?
- Какой API удобен для того, чтобы тестируемый код выполнял задуманное?
- Что нужно тестируемой системе для выполнения своих обязательств?
- Что будет на выходе?
- Какие есть побочные эффекты работы?
- Как узнать, что система работает правильно?
- Достаточно ли хорошо определена эта правильность?
Преимущества TDD:
- Эффективное совмещение ролей (тестирование собственного кода);
- Рефакторинг без риска испортить код;
- Реже нужно использовать отладчик;
- Повышает уверенность в качестве программного кода.
Но работы станет больше: вместо одной функции, придётся писать две (саму функцию и её юнит-тест). Зато будет затрачено меньше времени на поиск ошибок.
2.4 Метод черного ящика
Тестирование по методу черного ящика основано на предположении, что тест не знает содержимого и алгоритмов программного обеспечения, подразумевая только набор входных данных и предсказуемые выходные данные [3.]. Этот метод проводится командой специалистов по тестированию или конечными пользователями. Иногда его также называют функциональным тестом, который не имеет внутреннего механизма системы.
Этот метод включает в себя следующие техники:
- Разделение эквивалентности
Эта методика делит входную область программы на классы эквивалентности, из которых могут быть получены тестовые наборы, что позволяет сократить количество тестовых случаев.
- Анализ граничных значений
Основное внимание уделяется тестированию на границах или там, где выбраны крайние граничные значения. Он включает в себя минимум, максимум, области только внутри / вне границ, значения ошибок и типичные значения.
- Фаззинг
Эта методика подает случайное входное значение в приложение. Он используется для поиска ошибок реализации, используя внедрение некорректных данных в автоматическом или полуавтоматическом режиме.
- Причинно-следственная диаграмма
В этой методике тестирование начинается с создания диаграммы и установления связи между следствием и его причиной.
- Тестирование ортогональных массивов
Эта методика может применяться, когда входная область очень мала, но в то же время слишком велика для проведения исчерпывающего тестирования.
- Метод попарного тестирования
В этой методике тестовые случаи предназначены для выполнения всех возможных дискретных комбинаций каждой пары входных параметров. Ее главная цель – создать набор тестовых случаев, охватывающих все пары.
- Тестирование переходов состояний
Этот тип тестирования полезен для тестирования конечного автомата, а также для навигации по графическому интерфейсу пользователя.
Преимущества [4.]:
- Тестеры могут не иметь никаких специальных знаний конкретного языка программирования.
- Тестирование проводится с точки зрения пользователя.
- Тестирование помогает выявить любые неясности или несоответствия в спецификациях требований.
- Программист и тестер не зависят друг от друга.
Недостатки:
- Тестовые случаи сложно разработать без четких спецификаций.
- Велика вероятность повторения тестов, которые уже выполнены программистом.
- Некоторые части бэкенда вообще не тестируются.
2.5 Метод белого ящика
Тестирование белого ящика в основном сфокусировано на внутренней логике и структуре кода. Выполнение этого метода предполагает, что программист обладает методиками, полностью знающими структуру программы. С помощью этой техники можно протестировать каждую ветку и решение в программе [11.]. Метод белого ящика иногда называют структурным тестированием, в котором также проверяется внутренний механизм системы. Тестер знает код и структуру данных, предназначенных для тестирования конкретного метода, который будет выполняться с определенными параметрами. Поэтому, в отличие от тестирования по методу черного ящика, метод белого ящика требует знаний конкретного языка программирования.
Этот метод включает в себя следующие техники:
1) Ручная имитация работы программы
Это первичное тестирование кода. Специалисты, хорошо владеющие необходимым языком программирования, будут участвовать в контрольных тестах.
2) Разбор кода
В этом процессе тестирования группа технических специалистов подробно разбирает код. Это один из видов техники полуформального изложения.
3) Формальные инспекции
Инспекция – это формальный, эффективный и экономичный метод поиска ошибок в дизайне и коде. Это официальное ревью кода, направленное на выявление всех неисправностей, нарушений и других побочных эффектов.
4) Тестирование потоков управления