Меню

Какие виды ошибок обычно пропускаются на уровне модульного тестирования

Аннотация: Рассматриваются особенности модульного тестирования, обсуждаются
подходы к тестированию на основе потока управления, потока данных.
Обсуждаются динамические и статические методы при структурном
подходе. Рассматривается пример модульного тестирования.
Рассматривается взаимосвязь сборки модулей и методов интеграционного
тестирования. Обсуждаются подходы монолитного, инкрементального,
нисходящего и восходящего тестирования. Рассматриваются особенности
интеграционного тестирования в процедурном программировании.

Модульное

Модульное тестирование — это тестирование программы на уровне
отдельно взятых модулей, функций или классов. Цель модульного
тестирования
состоит в выявлении локализованных в модуле ошибок в
реализации алгоритмов, а также в определении степени готовности
системы к переходу на следующий уровень разработки и тестирования. Модульное тестирование проводится по принципу «белого ящика», то есть
основывается на знании внутренней структуры программы, и часто
включает те или иные методы анализа покрытия кода.

Модульное тестирование обычно подразумевает создание вокруг каждого
модуля определенной среды, включающей заглушки для всех интерфейсов
тестируемого модуля. Некоторые из них могут использоваться для подачи
входных значений, другие для анализа результатов, присутствие третьих
может быть продиктовано требованиями, накладываемыми компилятором и
сборщиком.

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

Именно эффективность обнаружения тех или иных типов дефектов должна
определять стратегию модульного тестирования, то есть расстановку
акцентов при определении набора входных значений. У организации,
занимающейся разработкой программного обеспечения, как правило,
имеется историческая база данных ( Repository ) разработок, хранящая
конкретные сведения о разработке предыдущих проектов: о версиях и
сборках кода ( build ) зафиксированных в процессе разработки продукта,
о принятых решениях, допущенных просчетах, ошибках, успехах и т.п.
Проведя анализ характеристик прежних проектов, подобных заказанному
организации, можно предохранить новую разработку от старых ошибок,
например, определив типы дефектов, поиск которых наиболее эффективен
на различных этапах тестирования.

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

Являясь по способу исполнения структурным тестированием или
тестированием «белого ящика», модульное тестирование характеризуется
степенью, в которой тесты выполняют или покрывают логику программы
(исходный текст). Тесты, связанные со структурным тестированием,
строятся по следующим принципам:

  • На основе анализа потока управления. В этом случае элементы,
    которые должны быть покрыты при прохождении тестов, определяются на
    основе структурных критериев тестирования С0, С1,С2. К ним относятся
    вершины, дуги, пути управляющего графа программы (УГП), условия,
    комбинации условий и т. п.
  • На основе анализа потока данных, когда элементы, которые должны
    быть покрыты, определяются при помощи потока данных, т. е.
    информационного графа программы.

Тестирование на основе потока управления. Особенности использования
структурных критериев тестирования С0,С1,С2 были рассмотрены в
лекции 3. К ним следует добавить критерий покрытия условий,
заключающийся в покрытии всех логических (булевских) условий в
программе. Критерии покрытия решений (ветвей — С1) и условий не
заменяют друг друга, поэтому на практике используется комбинированный
критерий покрытия условий/решений, совмещающий требования по покрытию
и решений, и условий.

К популярным критериям относятся критерий покрытия функций программы,
согласно которому каждая функция программы должна быть вызвана хотя
бы один раз, и критерий покрытия вызовов, согласно которому каждый
вызов каждой функции в программе должен быть осуществлен хотя бы один
раз. Критерий покрытия вызовов известен также как критерий покрытия
пар вызовов (call pair coverage).

Тестирование на основе потока данных. Этот вид тестирования направлен
на выявление ссылок на неинициализированные переменные и избыточные
присваивания (аномалий потока данных ). Как основа для стратегии
тестирования поток данных впервые был описан в
[
14
]
. Предложенная там
стратегия требовала тестирования всех взаимосвязей, включающих в себя
ссылку (использование) и определение переменной, на которую указывает
ссылка (т. е. требуется покрытие дуг информационного графа
программы). Недостаток стратегии в том, что она не включает критерий
С1, и не гарантирует покрытия решений.

Стратегия требуемых пар
[
15
]
также тестирует упомянутые взаимосвязи.
Использование переменной в предикате дублируется в соответствии с
числом выходов решения, и каждая из таких требуемых взаимосвязей
должна быть протестирована. К популярным критериям принадлежит
критерий СР, заключающийся в покрытии всех таких пар дуг v и w, что
из дуги v достижима дуга w, поскольку именно на дуге может произойти
потеря значения переменной, которая в дальнейшем уже не должна
использоваться. Для «покрытия» еще одного популярного критерия Cdu
достаточно тестировать пары (вершина, дуга), поскольку определение
переменной происходит в вершине УГП, а ее использование — на дугах,
исходящих из решений, или в вычислительных вершинах.

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

  • Конструирование УГП.
  • Выбор тестовых путей.
  • Генерация тестов, соответствующих тестовым путям.

Первая фаза соответствует статическому анализу программы, задача
которого состоит в получении графа программы и зависящего от него и
от критерия тестирования множества элементов, которые необходимо
покрыть тестами.

На третьей фазе по известным путям тестирования осуществляется поиск
подходящих тестов, реализующих прохождение этих путей.

Вторая фаза обеспечивает выбор тестовых путей. Выделяют три подхода к
построению тестовых путей:

  • Статические методы.
  • Динамические методы.
  • Методы реализуемых путей.

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

Динамические методы. Такие методы предполагают построение полной
системы тестов, удовлетворяющих заданному критерию, путем
одновременного решения задачи построения покрывающего множества путей
и тестовых данных. При этом можно автоматически учитывать
реализуемость или нереализуемость ранее рассмотренных путей или их
частей. Основной идеей динамических методов является подсоединение к
начальным реализуемым отрезкам путей дальнейших их частей так, чтобы:
1) не терять при этом реализуемости вновь полученных путей; 2)
покрыть требуемые элементы структуры программы.

Методы реализуемых путей. Данная методика
[
16
]
заключается в
выделении из множества путей подмножества всех реализуемых путей.
После чего покрывающее множество путей строится из полученного
подмножества реализуемых путей.

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

Пример модульного тестирования

Предлагается протестировать класс TCommand, который реализует команду
для склада. Этот класс содержит единственный метод TCommand.GetFullName(), спецификация которого описана (Практикум,
Приложение 2 HLD) следующим образом:

 
  Операция GetFullName() возвращает полное имя команды, соответствующее
ее допустимому коду, указанному в поле NameCommand. В противном
случае возвращается сообщение «ОШИБКА : Неверный код команды».
Операция может быть применена в любой момент.
 

Разработаем спецификацию тестового случая для тестирования метода GetFullName на основе приведенной спецификации класса (Табл. 5.1):

Таблица
5.1.
Спецификация теста

Название класса: TСommand Название тестового случая: TСommandTest1
Описание тестового случая: Тест проверяет правильность работы метода GetFullName — получения полного названия команды на основе кода
команды. В тесте подаются следующие значения кодов команд (входные
значения): -1, 1, 2, 4, 6, 20, (причем -1 — запрещенное значение).
Начальные условия: Нет.

Ожидаемый результат:

Перечисленным входным значениям должны соответствовать следующие
выходные:

Коду команды -1 должно соответствовать сообщение «ОШИБКА: Неверный
код команды»

Коду команды 1 должно соответствовать полное название команды
«ПОЛУЧИТЬ ИЗ ВХОДНОЙ ЯЧЕЙКИ»

Коду команды 2 должно соответствовать полное название команды
«ОТПРАВИТЬ ИЗ ЯЧЕЙКИ В ВЫХОДНУЮ ЯЧЕЙКУ»

Коду команды 4 должно соответствовать полное название команды
«ПОЛОЖИТЬ В РЕЗЕРВ»

Коду команды 6 должно соответствовать полное название команды
«ПРОИЗВЕСТИ ЗАНУЛЕНИЕ»

Коду команды 20 должно соответствовать полное название команды
«ЗАВЕРШЕНИЕ КОМАНД ВЫДАЧИ»

Для тестирования метода класса TCommand.GetFullName() был создан
тестовый драйвер — класс TCommandTester. Класс TCommandTester
содержит метод TCommandTest1(), в котором реализована вся
функциональность теста. В данном случае для покрытия спецификации
достаточно перебрать следующие значения кодов команд: -1, 1, 2, 4, 6,
20, (-1 — запрещенное значение) и получить соответствующее им полное
название команды с помощью метода GetFullName() (Пример 5.1 ). Пары
значений (X, Yв) при исполнении теста заносятся в log-файл для
последующей проверки на соответствие спецификации.

После завершения теста следует просмотреть журнал теста, чтобы
сравнить полученные результаты с ожидаемыми, заданными в спецификации
тестового случая TСommandTest1 (Пример 5.2).

class TCommandTester:Tester // Тестовый драйвер
{
  ...
  TCommand OUT; 
  public TCommandTester()
  {
    OUT=new TCommand();
    Run();
  }
  private void Run()
  {
    TCommandTest1();
  }

  private void TCommandTest1()
  {
    int[] commands = {-1, 1, 2, 4, 6, 20};

    for(int i=0;i<=5;i++)
    {
      OUT.NameCommand=commands[i];
      LogMessage(commands[i].ToString()+
        " : "+OUT.GetFullName());
    }
  }
  ...
}


Пример
5.1.
Тестовый драйвер

-1 : ОШИБКА : Неверный код команды
1 : ПОЛУЧИТЬ ИЗ ВХОДНОЙ ЯЧЕЙКИ
2 : ОТПРАВИТЬ ИЗ ЯЧЕЙКИ В ВЫХОДНУЮ ЯЧЕЙКУ
4 : ПОЛОЖИТЬ В РЕЗЕРВ
6 : ПРОИЗВЕСТИ ЗАНУЛЕНИЕ
20 : ЗАВЕРШЕНИЕ КОМАНД ВЫДАЧИ


Пример
5.2.
Спецификация классов тестовых случаев

С этими терминами часто происходит путаница. Если ссылаться на глоссарий ISTQB, то все они — синонимы:

  • Модуль, юнит (module, unit): См. компонент.
  • Модульное, юнит тестирование (module testing, unit testing): См. компонентное тестирование.
  • Компонент (component): Наименьший элемент программного обеспечения, который может быть протестирован отдельно.
  • Компонентное тестирование (component testing): Тестирование отдельных компонентов программного обеспечения (IEEE 610).

Тем не менее, некоторые источники описывают ситуацию несколько иначе и я решил выписать другую точку зрения.

Модульное тестирование (оно же юнит-тестирование) используется для тестирования какого-либо одного логически выделенного и изолированного элемента системы (отдельные методы класса или простая функция, subprograms, subroutines, классы или процедуры) в коде. Очевидно, что это тестирование методом белого ящика и чаще всего оно проводится самими разработчиками. Целью тестирования модуля является не демонстрация правильного функционирования модуля, а демонстрация наличия ошибки в модуле, а также в определении степени готовности системы к переходу на следующий уровень разработки и тестирования. На уровне модульного тестирования проще всего обнаружить дефекты, связанные с алгоритмическими ошибками и ошибками кодирования алгоритмов, типа работы с условиями и счетчиками циклов, а также с использованием локальных переменных и ресурсов. Ошибки, связанные с неверной трактовкой данных, некорректной реализацией интерфейсов, совместимостью, производительностью и т.п. обычно пропускаются на уровне модульного тестирования и выявляются на более поздних стадиях тестирования. Изоляция тестируемого блока достигается с помощью заглушек (stubs), манекенов (dummies) и макетов (mockups).

Компонентное тестирование — тип тестирования ПО, при котором тестирование выполняется для каждого отдельного компонента отдельно, без интеграции с другими компонентами. Его также называют модульным тестированием (Module testing), если рассматривать его с точки зрения архитектуры. Как правило, любое программное обеспечение в целом состоит из нескольких компонентов. Тестирование на уровне компонентов (Component Level testing) имеет дело с тестированием этих компонентов индивидуально. Это один из самых частых типов тестирования черного ящика, который проводится командой QA. Для каждого из этих компонентов будет определен сценарий тестирования, который затем будет приведен к Test case высокого уровня -> детальным Test case низкого уровня с предварительными условиями.

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

  • Тестирование компонентов в малом (CTIS — Component testing In Small): тестирование компонентов может проводиться с или без изоляции остальных компонентов в тестируемом программном обеспечении или приложении. Если это выполняется с изоляцией другого компонента, то это называется CTIS;
  • Тестирование компонентов в целом (CTIL — Component testing In Large) — тестирование компонентов, выполненное без изоляции других компонентов в тестируемом программном обеспечении или приложении;
Module/Unit testing Component testing
Тестирование отдельных классов, функций для демонстрации того, что программа выполняется согласно спецификации Тестирование каждого объекта или частей программного обеспечения отдельно с или без изоляции других объектов
Проверка в(на) соответствии с design documents Проверка в(на) соответствии с test requirements, use case
Пишутся и выполняются разработчиками Тестировщиками
Выполняется первым Выполняется после Unit

Другой источник:

По-существу эти уровни тестирования представляют одно и тоже, разница лишь в том, что в компонентном тестировании в качестве параметров функций используют реальные объекты и драйверы, а в модульном/unit тестировании — конкретные значения.

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

*Иногда юнит-тесты называют одинокими (solitary) в случае тотального применения имитаций и заглушек или общительными (sociable) в случае реальных коммуникаций с другими участниками.

*Правило трех А(AAA) (arrange, act, assert) или триада «дано, когда, тогда» — хорошая мнемоника, чтобы поддерживать хорошую структуру тестов.

Источники:

  • What is Component Testing? Techniques, Example Test Cases
  • Лекция 5: Модульное и интеграционное тестирование

Доп. материал:

  • ГОСТ Р 56920-2016/ISO/IEC/IEEE 29119-1:2013 “D.11 Подпроцесс покомпонентного тестирования”
  • Я сомневался в юнит-тестах, но…
  • Юнит-тесты переоценены
  • Elliotte Rusty Harold — Effective Unit Testing
  • Kevlin Henney — What we talk about when we talk about unit testing
  • Андрей Сербин — Компонентное тестирование инфраструктуры
  • Анатомия юнит тестирования
  • Unit Test
  • Component Test
  • Анатомия юнит-теста
  • Почему большинство юнит тестов — пустая трата времени? (перевод статьи)
  • Unit Testing Guide
  • Лекция 2: Тестирование программного кода (методы+окружение)
  • Starting to Unit Test: Not as Hard as You Think
  • 6 оправданий для того, чтобы не писать юнит-тесты
  • Принципы юнит-тестирования. Часть первая

Модульное тестирование (оно же юнит-тестирование) используется для тестирования какого-либо одного логически выделенного и изолированного элемента системы (отдельные методы класса или простая функция, subprograms, subroutines, классы или процедуры) в коде. Очевидно, что это тестирование методом белого ящика и чаще всего оно проводится самими разработчиками. Целью тестирования модуля является не демонстрация правильного функционирования модуля, а демонстрация наличия ошибки в модуле, а также в определении степени готовности системы к переходу на следующий уровень разработки и тестирования. На уровне модульного тестирования проще всего обнаружить дефекты, связанные с алгоритмическими ошибками и ошибками кодирования алгоритмов, типа работы с условиями и счетчиками циклов, а также с использованием локальных переменных и ресурсов. Ошибки, связанные с неверной трактовкой данных, некорректной реализацией интерфейсов, совместимостью, производительностью и т.п. обычно пропускаются на уровне модульного тестирования и выявляются на более поздних стадиях тестирования. Изоляция тестируемого блока достигается с помощью заглушек (stubs), манекенов (dummies) и макетов (mockups).

Дефекты программного обеспечения можно обнаружить на каждом этапе разработки и тестирования продукта. Чтобы гарантировать исправление наиболее серьезных дефектов программного обеспечения, тестировщикам важно иметь хорошее представление о различных типах дефектов, которые могут возникнуть.

20 ВИДОВ ПРОГРАММНЫХ ДЕФЕКТОВ, КОТОРЫЕ ДОЛЖЕН ЗНАТЬ КАЖДЫЙ ТЕСТЕР

В этой статье мы обсудим самые распространенные типы ПО дефекты и способы их выявления.

Что такое дефект?

Дефект программного обеспечения — это ошибка, изъян, сбой или неисправность в компьютерной программе, из-за которой она выдает неправильный или неожиданный результат или ведет себя непреднамеренным образом. Программная ошибка возникает, когда фактические результаты не совпадают с ожидаемыми. Разработчики и программисты иногда допускают ошибки, которые создают ошибки, называемые дефектами. Большинство ошибок возникает из-за ошибок, которые допускают разработчики или программисты.

Обязательно прочтите: Разница между дефектом, ошибкой, ошибкой и сбоем

Типы программных ошибок при тестировании программного обеспечения

Существует множество различных типов дефектов программного обеспечения, и тестировщикам важно знать наиболее распространенные из них, чтобы они могут эффективно тестировать их.

Ошибки программного обеспечения подразделяются на три типа:

  1. Дефекты программного обеспечения по своей природе
  2. Дефекты программного обеспечения по их приоритету
  3. Дефекты программного обеспечения по их серьезности

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

#1. Дефекты программного обеспечения по своей природе

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

#1. Функциональные ошибки

Как следует из названия, функциональные ошибки — это те, которые вызывают сбои в работе программного обеспечения. Хорошим примером этого может служить кнопка, при нажатии на которую должно открываться новое окно, но вместо этого ничего не происходит.

Функциональные ошибки можно исправить, выполнив функциональное тестирование.

#2. Ошибки на уровне модуля

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

Ошибки на уровне модуля можно исправить, выполнив модульное тестирование.

#3. Ошибки уровня интеграции

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

Ошибки интеграции можно исправить, выполнив интеграционное тестирование.

#4. Дефекты юзабилити

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

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

Ошибки, связанные с удобством использования, можно исправить, выполнив тестирование удобства использования.

#5. Дефекты производительности

Ошибки производительности — это дефекты, влияющие на производительность программного обеспечения. Это может включать в себя такие вещи, как скорость программного обеспечения, объем используемой памяти или количество потребляемых ресурсов. Ошибки уровня производительности сложно отследить и исправить, поскольку они могут быть вызваны рядом различных факторов.

Ошибки юзабилити можно исправить, выполнив тестирование производительности.

#6. Дефекты безопасности

Ошибки безопасности — это тип дефекта программного обеспечения, который может иметь серьезные последствия, если его не устранить. Эти дефекты могут позволить злоумышленникам получить доступ к конфиденциальным данным или системам или даже позволить им получить контроль над уязвимым программным обеспечением. Таким образом, очень важно, чтобы ошибкам уровня безопасности уделялось первоочередное внимание и устранялись как можно скорее.

Ошибки безопасности можно исправить, выполнив тестирование безопасности.

#7. Дефекты совместимости

Дефекты совместимости — это те ошибки, которые возникают, когда приложение несовместимо с оборудованием, на котором оно работает, или с другим программным обеспечением, с которым оно должно взаимодействовать. Несовместимость программного и аппаратного обеспечения может привести к сбоям, потере данных и другому непредсказуемому поведению. Тестировщики должны знать о проблемах совместимости и проводить соответствующие тесты. Программное приложение, имеющее проблемы с совместимостью, не работает последовательно на различных видах оборудования, операционных системах, веб-браузерах и устройствах при подключении к определенным программам или работе в определенных сетевых условиях.

Ошибки совместимости можно исправить, выполнение тестирования совместимости.

#8. Синтаксические ошибки

Синтаксические ошибки являются самым основным типом дефекта. Они возникают, когда код нарушает правила языка программирования. Например, использование неправильной пунктуации или забывание закрыть скобку может привести к синтаксической ошибке. Синтаксические ошибки обычно мешают запуску кода, поэтому их относительно легко обнаружить и исправить.

#9. Логические ошибки

Логические ошибки — это дефекты, из-за которых программа выдает неправильные результаты. Эти ошибки может быть трудно найти и исправить, потому что они часто не приводят к каким-либо видимым ошибкам. Логические ошибки могут возникать в любом типе программного обеспечения, но они особенно распространены в приложениях, требующих сложных вычислений или принятия решений.

Общие симптомы логических ошибок включают:

  • Неверные результаты или выходные данные
  • Неожиданное поведение
  • Сбой или зависание программного обеспечения

Чтобы найти и исправить логические ошибки, тестировщикам необходимо иметь четкое представление о коде программы и о том, как она должна работать. Часто лучший способ найти такие ошибки — использовать инструменты отладки или пошаговое выполнение, чтобы отслеживать выполнение программы и видеть, где что-то идет не так.

#2. Дефекты программного обеспечения по степени серьезности

Уровень серьезности присваивается дефекту по его влиянию. В результате серьезность проблемы отражает степень ее влияния на функциональность или работу программного продукта. Дефекты серьезности классифицируются как критические, серьезные, средние и незначительные в зависимости от степени серьезности.

#1. Критические дефекты

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

#2. Серьезные дефекты

Серьезный дефект — это программная ошибка, существенно влияющая на работу приложения. Серьезные дефекты могут привести к замедлению работы приложения или другому неожиданному поведению. Они также могут привести к потере данных или уязвимостям в системе безопасности. Разработчики и тестировщики часто придают первостепенное значение серьезным дефектам, поскольку их необходимо исправить как можно скорее.

#3. Незначительные дефекты

Незначительный дефект — это программная ошибка, которая оказывает небольшое или незначительное влияние на работу приложения. Незначительные дефекты могут привести к тому, что приложение будет работать немного медленнее или демонстрировать другое неожиданное поведение. Разработчики и тестировщики часто не придают незначительным дефектам приоритет, потому что их можно исправить позже.

#4. Тривиальные дефекты

Тривиальный дефект – это программная ошибка, не влияющая на работу приложения. Тривиальные дефекты могут привести к тому, что приложение отобразит сообщение об ошибке или проявит другое неожиданное поведение. Разработчики и тестировщики часто присваивают тривиальным дефектам самый низкий приоритет, потому что они могут быть исправлены позже.

#3. Дефекты программного обеспечения по приоритету

#1. Дефекты с низким приоритетом

Дефекты с низким приоритетом, как правило, не оказывают серьезного влияния на работу программного обеспечения и могут быть отложены для исправления в следующей версии или выпуске. В эту категорию попадают косметические ошибки, такие как орфографические ошибки, неправильное выравнивание и т. д.

#2. Дефекты со средним приоритетом

Дефекты со средним приоритетом — это ошибки, которые могут быть исправлены после предстоящего выпуска или в следующем выпуске. Приложение, возвращающее ожидаемый результат, которое, однако, неправильно форматируется в конкретном браузере, является примером дефекта со средним приоритетом.

#3. Дефекты с высоким приоритетом

Как следует из названия, дефекты с высоким приоритетом — это те, которые сильно влияют на функционирование программного обеспечения. В большинстве случаев эти дефекты необходимо исправлять немедленно, так как они могут привести к серьезным нарушениям нормального рабочего процесса. Дефекты с высоким приоритетом обычно классифицируются как непреодолимые, так как они могут помешать пользователю продолжить выполнение поставленной задачи.

Некоторые распространенные примеры дефектов с высоким приоритетом включают:

  • Дефекты, из-за которых приложение не работает. сбой
  • Дефекты, препятствующие выполнению задачи пользователем
  • Дефекты, приводящие к потере или повреждению данных
  • Дефекты, раскрывающие конфиденциальную информацию неавторизованным пользователям
  • Дефекты, делающие возможным несанкционированный доступ к системе
  • Дефекты, приводящие к потере функциональности
  • Дефекты, приводящие к неправильным результатам или неточным данным
  • Дефекты, вызывающие проблемы с производительностью, такие как чрезмерное использование памяти или медленное время отклика

#4. Срочные дефекты

Срочные дефекты — это дефекты, которые необходимо устранить в течение 24 часов после сообщения о них. В эту категорию попадают дефекты со статусом критической серьезности. Однако дефекты с низким уровнем серьезности также могут быть классифицированы как высокоприоритетные. Например, опечатка в названии компании на домашней странице приложения не оказывает технического влияния на программное обеспечение, но оказывает существенное влияние на бизнес, поэтому считается срочной.

#4. Дополнительные дефекты

#1. Отсутствующие дефекты

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

#2. Неправильные дефекты

Неправильные дефекты — это те дефекты, которые удовлетворяют требованиям, но не должным образом. Это означает, что хотя функциональность достигается в соответствии с требованиями, но не соответствует ожиданиям пользователя.

#3. Дефекты регрессии

Дефект регрессии возникает, когда изменение кода вызывает непреднамеренное воздействие на независимую часть программного обеспечения.

Часто задаваемые вопросы — Типы программных ошибок< /h2>

Почему так важна правильная классификация дефектов?

Правильная классификация дефектов важна, поскольку она помогает эффективно использовать ресурсы и управлять ими, правильно приоритизировать дефекты и поддерживать качество программного продукта.

Команды тестирования программного обеспечения в различных организациях используют различные инструменты отслеживания дефектов, такие как Jira, для отслеживания дефектов и управления ими. Несмотря на то, что в этих инструментах есть несколько вариантов классификации дефектов по умолчанию, они не всегда могут наилучшим образом соответствовать конкретным потребностям организации.

Следовательно, важно сначала определить и понять типы дефектов программного обеспечения, которые наиболее важны для организации, а затем соответствующим образом настроить инструмент управления дефектами.

Правильная классификация дефектов также гарантирует, что команда разработчиков сможет сосредоточиться на критических дефектах и ​​исправить их до того, как они повлияют на конечных пользователей.

Кроме того, это также помогает определить потенциальные области улучшения в процессе разработки программного обеспечения, что может помочь предотвратить появление подобных дефектов в будущих выпусках.

Таким образом, отслеживание и устранение дефектов программного обеспечения может показаться утомительной и трудоемкой задачей. , правильное выполнение может существенно повлиять на качество конечного продукта.

Как найти лежащие в основе ошибки программного обеспечения?

Определение основной причины программной ошибки может быть сложной задачей даже для опытных разработчиков. Чтобы найти лежащие в основе программные ошибки, тестировщики должны применять систематический подход. В этот процесс входят различные этапы:

1) Репликация. Первым этапом является воспроизведение ошибки. Это включает в себя попытку воспроизвести тот же набор шагов, в котором возникла ошибка. Это поможет проверить, является ли ошибка реальной или нет.
2) Изоляция. После того, как ошибка воспроизведена, следующим шагом будет попытка ее изоляции. Это включает в себя выяснение того, что именно вызывает ошибку. Для этого тестировщики должны задать себе несколько вопросов, например:
– Какие входные данные вызывают ошибку?
– При каких различных условиях возникает ошибка?
– Каковы различные способы проявления ошибки?
3) Анализ: после Изолируя ошибку, следующим шагом будет ее анализ. Это включает в себя понимание того, почему возникает ошибка. Тестировщики должны задать себе несколько вопросов, таких как:
– Какова основная причина ошибки?
– Какими способами можно исправить ошибку?
– Какое исправление было бы наиболее эффективным? эффективно?
4) Отчет. После анализа ошибки следующим шагом является сообщение о ней. Это включает в себя создание отчета об ошибке, который включает всю соответствующую информацию об ошибке. Отчет должен быть четким и кратким, чтобы разработчики могли его легко понять.
5) Проверка. После сообщения об ошибке следующим шагом является проверка того, была ли она исправлена. Это включает в себя повторное тестирование программного обеспечения, чтобы убедиться, что ошибка все еще существует. Если ошибка исправлена, то тестер может подтвердить это и закрыть отчет об ошибке. Если ошибка все еще существует, тестировщик может повторно открыть отчет об ошибке.

Заключение

В индустрии программного обеспечения дефекты — неизбежная реальность. Однако благодаря тщательному анализу и пониманию их характера, серьезности и приоритета дефектами можно управлять, чтобы свести к минимуму их влияние на конечный продукт.

Задавая правильные вопросы и применяя правильные методы, тестировщики могут помочь обеспечить чтобы дефекты обнаруживались и исправлялись как можно раньше в процессе разработки.
TAG: qa

Перевод статьи
«3 Mistakes You’re Probably Making When Unit Testing».

Модульное тестирование

Модульное тестирование это одна из тех вещей, которые разработчики либо любят, либо ненавидят — третьего не дано. Также это одна из тех задач, которые во многих командах поручают «новичку», разработчику-джуниору или любому, кто не занят ничем другим, более важным.

Обычно так происходит, потому что модульное тестирование считается тривиальной задачей, чем-то таким, в чем никто не сумеет ошибиться. Но знаете, что? Таки есть способы напортачить с тестами! В этой статье я расскажу о трех ошибках, которые вы (как и любой другой) можете допустить в ходе тестирования.

1. Тестирование больше одной
вещи за раз

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

Позвольте спросить: кто в этом виноват?
Ваш разработчик, занимающийся тестами?
Или тот, кто написал функцию на сотни
строк?

Модульные тесты подразумевают фокус
на отдельных модулях вашего кода. Если
функция, которую вам нужно протестировать,
не позволяет это сделать, нужно не тесты
менять — это неверный подход. Вместо
этого уделите время рефакторингу кода,
разбейте его на более поддерживаемые
модули, а затем разделите и test cases, чтобы
точно иметь возможность тестировать
все части по отдельности.

Для примера возьмем следующую функцию:

function sayHi(first_name, last_name) {
    if(!first_name) return false;
    if(!last_name) return false;

    [fn_fl, ...fn_tail] = first_name.split("");
    first_name = [fn_fl.toUppercase(), ...fn_tail].join("");

    [ln_fl, ...ln_tail] = last_name.split("");
    last_name = [ln_fl.toUppercase(), ...ln_tail].join("");


    console.log("Hello there " + first_name + " " + last_name)
    return true
}

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

function validateData(fn, ln) {
    if(!fn) return false
    if(!ln) return false
    return true
}

function transform(string) {
    [fl, ...tail] = string.split("");
    return [fl.toUppercase(), ...tail].join("");
}

function sayHi2(first_name, last_name) {
    if(!validateData(first_name, last_name)) {
        return false;
    }

    first_name = transform(first_name)
    last_name = transform(last_name)

    console.log("Hello there " + first_name + " " + last_name)
    return true
}

Благодаря этому вы сможете протестировать
различные части предыдущего поведения.

2. Принятие в расчет внешних
сервисов

Чаще всего код пишется не для того,
чтобы работать изолированно. По факту,
скорее всего он будет работать со
внешними сервисами. Под словом «сервисы»
здесь следует понимать что угодно, от
запроса к Google Map’s API до сохранения файла
на вашем локальном жестком диске.

То есть, ваш код будет взаимодействовать
с чем-то вне среды его выполнения, но
ваши модульные тесты не должны это
учитывать. Почему? Просто потому, что в
ходе выполнения тестов вы не отвечаете
(или не должны отвечать) за статус
сторонних сервисов. Другими словами,
если Google внезапно решит отключить свой
Maps API на несколько часов, почему в таком
случае ваши модульные тесты должны
провалиться?

Если ваши тесты полагаются на
взаимодействие со внешними сервисами,
вы тестируете не только свой код, но и
надежность этих сервисов, а это не должно
быть вашей целью. Внешние сервисы это
совершенно отдельные продукты, они
тестируются в ходе своего цикла
разработки, вам-то зачем сейчас о них
беспокоиться?

Возможно, вы думаете: «Так я же тестирую
код, связанный с этими сервисами!» Ну,
если только вы не пишете драйвера для
них, это сторонние библиотеки, которые
тестируются их создателями.

В общем, что я хочу сказать: фокусируйтесь
на собственном коде. Если код взаимодействует
с базой данных, сделайте заглушку (mock),
если посылаете запрос к какому-нибудь
API, сделайте макеты ответов. В общем, вы
поняли суть.

Модули

3. Цель — покрытие тестами

Наконец, еще одна классическая ошибка
это написание тестов ради покрытия
тестами. Сколько раз вам доводилось
слышать призвыв «Пиши тесты, пока не
покроешь ими 90% своего кода»?

Покрытие это лишь один из показателей
для ваших тестов, так же как количество
строк кода — показатель сложности
функций. Но, я уверен, вам встречался
очень краткий код, совершенно непригодный
для чтения.

Не нужно рассматривать покрытие как
способ определить, сколько кода покрыто
тестами. Этот показатель следует
использовать для понимания того, сколько
кода тестами НЕ покрыто, а также для
понимания того, нужно ли его покрывать.

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

Представьте, что у вас есть совершенно
реальная и полезная функция:

function div(a, b) {
    if(a > 0) {
        console.log("Debug: a is a positive number")
    }
    return a / b;
}

Проверив, что div(1,1) возвращает 1, а
console.log вызывается, вы можете достигнуть
100% покрытия тестами. Это прекрасно,
однако я уверен, что вы видите, насколько
нерелевантными окажутся эти тесты в ту
минуту, когда вы в качестве второго
аргумента функции передадите 0.

Будет гораздо лучше, если вы добавите
несколько тестов, проверяющих, что все
работает с различными значениями a и b,
пускай даже и не сделаете тестов для
контента вашего if-блока.

Помните, что концентрироваться нужно
на функциональности!

Заключение

Я описал три ошибки, часто допускаемые разработчиками в ходе модульного тестирования (естественно, сам я их тоже допускал не раз). Знаете и другие варианты ошибок при написании тестов? Поделитесь в комментариях!

ТЕСТИРОВАНИЕ  ПРОГРАММ

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

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

Уровни тестирования:


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


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


системное тестирование. Тестируется
интегрированная  система на ее соответствие
исходным требованиям:


альфа-тестирование — имитация
реальной работы с  системой штатными
разработчиками либо реальная работа с системой потенциальными  пользователями/заказчиком на стороне
разработчика. Часто альфа-тестирование применяется для законченного продукта в
качестве внут-

реннего приемочного тестирования. Иногда альфа  тестирование выполняется под отладчиком или
с  использованием окружения, которое
помогает быстро  выявлять найденные
ошибки. Обнаруженные ошибки могут быть переданы тестировщикам для
дополнительного  исследования в
окружении, подобном тому, в котором 
будет использоваться ПО;

бета-тестирование — в
некоторых случаях выполняется распространение версии с ограничениями (по  функциональности или времени работы) для
некоторой группы

лиц с тем, чтобы убедиться, что продукт содержит  достаточно мало ошибок. Иногда
бета-тестирование  выполняется для того,
чтобы получить обратную связь о продукте от его будущих пользователей.

5.1. Термины и определения

Выполнение программы с целью обнаружения ошибок  называется тестированием. Виды ошибок и
способы их  обнаружения приведены в табл.
5.1.

       Эффективность контроля 1-го
вида зависит и от языка, и от компилятора. Контроль 2-го вида осуществляется с
помощью исключений — Exceptions и весьма полезен для проверки правдоподобности
промежуточных результатов. Тест — это набор контрольных входных данных
совместно с ожидаемыми  результатами. В
число входных данных времязависимых программ

входят события и временные параметры. Ключевой вопрос — полнота
тестирования: какое количество каких тестов 
гарантирует, возможно, более полную проверку программы? 

Исчерпывающая проверка на всем множестве входных данных  недостижима. Пример: программа, вычисляющая
функцию двух  переменных: Y=f(X, Z). Если
X, Y, Z — real, то полное число тестов

(232) 2= 264= 1031 Если на
каждый тест тратить 1 мс, то 264 мс = = 800 млн лет. Следовательно:

• в любой нетривиальной программе на любой стадии ее  готовности содержатся необнаруженные ошибки;

• тестирование — технико-экономическая проблема,  основанная на компромиссе время — полнота.
Поэтому нужно стремиться к возможно меньшему количеству хороших  тестов с желательными свойствами.

    Детективность:
тест должен с большой вероятностью 
обнаруживать возможные ошибки

    Покрывающая способность:
один тест должен выявлять как можно больше ошибок.

    Воспроизводимость:
ошибка должна выявляться независимо от изменяющихся условий (например, от
временных  соотношений) — это
труднодостижимо для времязависимых программ,

результаты которых часто невоспроизводимы.

     Только на основании выбранного
критерия можно  определить тот момент
времени, когда конечное множество тестов 
окажется достаточным для проверки программы с некоторой 

полнотой (степень полноты, впрочем, определяется  экспериментально). Используется два вида
критериев (табл. 5.2):

• функциональные тесты
составляются исходя из  спецификации
программы;

• структурные тесты составляются
исходя из текста  программы.

      На рис. 5.1, а видно отличие
тестирования команд  (достаточен один
тест) от С1 (необходимы два теста как минимум). 

Рисунок 5.1, б иллюстрирует различие С1 (достаточно двух тестов,
покрывающих пути 1, 4 или 2, 3) от С2 (необходимо четыре  теста для всех четырех путей). С2 недостижим
в реальных программах из-за их цикличности, поэтому ограничиваются тремя  путями для каждого цикла: 0, 1 и N повторений
цикла.

      Остаются проблемы назначения
классов входных/выходных данных для функционального тестирования и проектирования
тестов для структурного тестирования. Классы, как правило,  назначаются исходя из семантики решаемой
задачи [6].

        Рассмотрим пример. Найти
минимальный набор тестов для программы нахождения вещественных корней
квадратного  уравнения ах2 +
bх +
с — 0.

Решение представлено в табл. 5.3.

Таким образом, для этой программы предлагается  минимальный набор функциональных тестов,
исходя из 7 классов  выходных данных.

5.2. Тестирование «белого
ящика» и «черного ящика»

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

      При тестировании «белого ящика»
(англ. white-box testing,  также говорят
— прозрачного ящика) разработчик теста имеет доступ к исходному коду и может
писать код, который связан с 
библиотеками тестируемого ПО. Это типично для юнит-тестирования (англ.
unit testing), при котором тестируются только отдельные части системы. Оно
обеспечивает то, что компоненты 
конструкции работоспособны и устойчивы до определенной степени.

      При тестировании «черного
ящика» (англ. black-box testing) тестировщик имеет доступ к ПО только через те
же интерфейсы, что и заказчик или пользователь, либо через внешние 

интерфейсы, позволяющие другому компьютеру либо другому процессу
подключиться к системе для тестирования. Например,  тестирующий модуль может виртуально нажимать
клавиши или кнопки мыши в тестируемой программе с помощью механизма  взаимодействия процессов с уверенностью в
том, что эти события  вызывают тот же
отклик, что и реальные нажатия клавиш и кнопок мыши.

      Если альфа- и бета-тестирование
относятся к стадиям до  выпуска продукта
(а также, неявно, к объему тестирующего 
сообщества и ограничениям на методы тестирования), тестирование «белого
ящика» и «черного ящика» имеет отношение к 
способам, которыми тестировщик достигает цели.

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

5.3. Порядок разработки
тестов

     По внешней спецификации
разрабатываются тесты [3]:

• для каждого класса входных данных;

• для граничных и особых значений входных данных.

     Контролируется, все ли классы
выходных данных при этом проверяются, и добавляются при необхопимости нужные
тесты.

     Разрабатываются тесты для тех
функций, которые не  проверяются в п. 1.

     По тексту программы проверяется,
все ли условные  переходы выполнены в
каждом направлении (С1). При необходимости добавляются новые тесты.

      Аналогично проверяется,
проходятся ли пути для каждого цикла: без выполнения тела, с однократным и
максимальным числом повторений.

      Готовятся тесты, проверяющие
исключительные ситуации, недопустимые входные данные, аварийные ситуации.

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

Согласно статистике 1-й и 2-й пункты обеспечивают степень  охвата С1 в среднем 40—50 %. Проверка по С1
(пункт 3) обычно выявляет 90 % всех ошибок, найденных при тестировании. (Все
программное обеспечение ВВС США принимается с проверкой

по С1.)

       Систематическое тестирование
предполагает также ведение журнала отладки (Bug Book), в котором фиксируется
ошибка (описание, дата обнаружения, автор модуля) и в дальнейшем — исправление
(дата, автор).

      Приведем так называемые аксиомы
тестирования.

1. Тест должен быть направлен на обнаружение ошибки, а не на подтверждение
правильности программы.

2. Автор теста — не автор программы.

3. Тесты разрабатываются одновременно или до разработки программы.

4. Необходимо предсказывать ожидаемые результаты теста до его выполнения и
анализировать причины расхождения 
результатов.

5. Предыдущее тестирование необходимо повторять после каждого внесения
исправлений в программу.

6. Следует повторять полное тестирование после внесения изменений в
программу или после переноса ее в другую среду.

7. В те программы, в которых обнаружено много ошибок,  необходимо дополнить первоначальный набор
тестов [6].

5.4. Автоматизация
тестирования

 A.Автоматизация прогона тестов
актуальна для 5-й и 6-й  аксиом Майерса.
Пишутся командные файлы для запуска 
программы с каждым тестом из набора и сравнением реального  результата с ожидаемым. Существуют
специальные средства (например система MIL-S для PL/1 фирмы IBM). Разрабатывается
стандарт
IEEE
скриптового языка для описания тестовых 
наборов [3].

Б. Средства автоматизации
подготовки тестов и анализа их 
результатов.

1. Генераторы случайных тестов в заданных областях  входных данных.

2. Отладчики (для локализации ошибок).

3. Анализаторы динамики (profilers). Обычно входят в состав отладчиков;
применяются для проверки соответствия тестовых наборов структурным критериям
тестирования.

4. Средства автоматической генерации структурных тестов методом
«символического выполнения» Кинга.

5.5. Модульное тестирование

      Модульное тестирование — это
тестирование программы на уровне отдельно взятых модулей, функций или классов.
Цель модульного тестирования заключается в выявлении 

локализованных в модуле ошибок в реализации алгоритмов, а также в  определении степени готовности системы к
переходу на  следующий уровень разработки
и тестирования. Модульное  тестирование
проводится по принципу «белого ящика», т. е. основывается на знании внутренней
структуры программы и часто включает те или иные методы анализа покрытия кода.

      Модульное тестирование обычно
подразумевает создание  вокруг каждого
модуля определенной среды, включающей 
заглушки для всех интерфейсов тестируемого модуля. Некоторые из них могут
использоваться для подачи входных значений, другие — для анализа результатов,
присутствие третьих может быть продиктовано требованиями, накладываемыми
компилятором и сборщиком.

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

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

обеспечения, как правило, имеется историческая база данных (Repository)
разработок, хранящая конкретные сведения о 
разработке предыдущих проектов: о версиях и сборках кода (build),  зафиксированных в процессе разработки
продукта, о принятых  решениях,
допущенных просчетах, ошибках, успехах и т. п. Проведя анализ характеристик
прежних проектов, подобных заказанному разработчику, можно предохранить новую
разработку от старых ошибок, например, определив типы дефектов, поиск которых
наиболее эффективен на различных этапах тестирования.

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

довольно легко обнаруживаются.

       Являясь по способу исполнения
структурным тестированием или тестированием «белого ящика», модульное
тестирование  характеризуется степенью, в
которой тесты выполняют или  покрывают
логику программы (исходный текст). Тесты, связанные со структурным
тестированием, строятся по следующим принципам:

• на основе анализа потока управления. В этом случае  элементы, которые должны быть покрыты при
прохождении тестов, определяются на основе структурных критериев

тестирования СО, CI, C2. К ним относятся вершины, дуги, пути управляющего
графа программы (УГП), условия, 
комбинации условий и т. п.

• на основе анализа потока данных, когда элементы,  которые должны быть покрыты, определяются на
основе  потока данных, т. е.
информационного графа программы.

      Тестирование на основе потока управления.
Особенности использования структурных критериев тестирования СО, CI, С2 были
рассмотрены в разд. 5.2. К ним следует добавить критерий покрытия условий,
заключающийся в покрытии всех логических (булевых) условий в программе.
Критерии покрытия решений (ветвей — С1) и условий не заменяют друг друга,
поэтому на практике используется комбинированный критерий покрытия

условий/решений, совмещающий требования по покрытию и решений, и условий.

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

покрытия вызовов, согласно которому каждый вызов каждой функции в программе
должен быть осуществлен хотя бы 1 раз. Критерий покрытия вызовов известен также
как критерий покрытия пар вызовов (call pair coverage).

     Тестирование на основе потока данных. Этот вид  тестирования направлен на выявление ссылок на
неинициализированные переменные и избыточные присваивания (аномалий потока  данных). Как основа для стратегии
тестирования поток данных впервые был описан в [14]. Предложенная там
стратегия  требовала тестирования всех
взаимосвязей, включающих в себя  ссылку
(использование) и определение переменной, на которую 

указывает ссылка (т. е. требуется покрытие дут информационного графа
программы). Недостаток стратегии в том, что она не включает критерий С1 и не
гарантирует покрытия решений.

       Стратегия требуемых пар [15]
также тестирует упомянутые взаимосвязи. Использование переменной в
предикате  дублируется в соответствии с
числом выходов решения, и каждая из таких

требуемых взаимосвязей должна быть протестирована. К  популярным критериям принадлежит критерий СР,
заключающийся в покрытии всех таких пар дуг v и w, что из дуги v достижима дуга
w, поскольку именно на дуге может произойти потеря  значения переменной, которая в дальнейшем уже
не должна  использоваться. Для покрытия
еще одного популярного критерия Cdu достаточно тестировать пары (вершина,
дуга), поскольку определение переменной происходит в вершине УГП, а ее  использование — на дугах, исходящих из
решений, или в  вычислительных вершинах.

       Методы проектирования тестовых
путей для достижения  заданной степени
тестированности в структурном тестировании.

Процесс построения набора тестов при структурном  тестировании принято делить на три фазы:

• конструирование УГП;

• выбор тестовых путей;

• генерация тестов, соответствующих тестовым путям.

      Первая фаза соответствует
статическому анализу программы, задача которого состоит в получении графа
программы и  зависящего от него и от
критерия тестирования множества элементов, которые необходимо покрыть тестами.

      На третьей фазе по известным
путям тестирования  осуществляется поиск
подходящих тестов, реализующих прохождение этих путей.

Вторая фаза обеспечивает выбор тестовых путей. Выделяют три подхода к
построению тестовых путей:

• статические методы;

• динамические методы;

• методы реализуемых путей.

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

       Динамические методы.
Такие методы предполагают  построение
полной системы тестов, удовлетворяющих заданному  критерию, путем одновременного решения задачи
построения  покрывающего множества путей
и тестовых данных. При этом можно

автоматически учитывать реализуемость или нереализуемость ранее
рассмотренных путей или их частей. Основной идеей  динамических методов является подсоединение к
начальным  реализуемым отрезкам путей
дальнейших их частей так, чтобы:

1) не терять при этом реализуемости вновь полученных путей;

2) покрыть требуемые элементы структуры программы.

        Методы реализуемых путей.
Данная методика [16]  заключается в
выделении из множества путей подмножества всех реализуемых путей. После этого
покрывающее множество путей строится из полученного подмножества реализуемых
путей.

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

Кроме того, в этих системах переход от покрывающего  множества путей к полной системе тестов
пользователь должен  осуществить вручную,
а эта работа достаточно трудоемкая. 

Динамические методы требуют значительно больших ресурсов как при  разработке, так и при эксплуатации, однако
увеличение затрат происходит в основном за счет разработки и эксплуатации  аппарата определения реализуемости пути
(символический  интерпретатор, решатель
неравенств). Достоинство этих методов 
заключается в том, что их продукция имеет некоторый качественный уровень
— реализуемость путей. Методы реализуемых путей дают самый лучший результат
[33].

5.6. Интеграционное
тестирование

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

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

включая заглушки (Stub) на месте отсутствующих модулей. 

Основная разница между модульным и интеграционным  тестированием состоит в целях, т. е. в типах
обнаруживаемых дефектов, которые, в свою очередь, определяют стратегию выбора
входных данных и методов анализа. В частности, на уровне  интеграционного тестирования часто
применяются методы, связанные с 
покрытием интерфейсов, например, вызовов функций или  методов, или анализ использования
интерфейсных объектов, таких как глобальные ресурсы, средства коммуникаций,  предоставляемых операционной системой.

     На рис. 5.2 приведена структура
комплекса программ К,  состоящего из
оттестированных на этапе модульного тестирования модулей М1, М,2
М11, М,12 М21, М22. Задача,
решаемая  методом интеграционного
тестирования, — тестирование 
межмодульных связей, реализующихся при исполнении программного
обеспечения комплекса К. Интеграционное тестирование  использует модель «белого ящика» на модульном
уровне. 

Поскольку тестировщику текст программы известен с детальностью до вызова
всех модулей, входящих в тестируемый комплекс, 
применение структурных критериев на данном этапе возможно и  оправданно.

Рис. 5.2. Пример структуры комплекса программ

      Интеграционное тестирование
применяется на этапе сборки модульно оттестированных модулей в единый
комплекс. 

Известны два метода сборки модулей:

монолитный, характеризующийся
одновременным  объединением всех модулей
в тестируемый комплекс;

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

— «сверху вниз» и соответствующее ему восходящее  тестирование;

— «снизу вверх» и соответственно нисходящее 
тестирование.

        Особенности монолитного тестирования заключаются в  следующем: для замены не разработанных к
моменту тестирования модулей, кроме самого верхнего (К на рис. 5.2),
необходимо  дополнительно разрабатывать
драйверы (test driver) и/или заглушки(stub) [9], замещающие отсутствующие на
момент сеанса  тестирования модули нижних
уровней.

       Сравнение монолитного и
интегрального подходов дает  следующие
результаты.

      Монолитное тестирование требует
больших трудозатрат, связанных с дополнительной разработкой драйверов и
заглушек и со сложностью идентификации ошибок, проявляющихся в  пространстве собранного кода.

       Пошаговое тестирование связано
с меньшей трудоемкостью идентификации ошибок за счет постепенного
наращивания  объема тестируемого кода и
соответственно локализации 

добавленной области тестируемого кода.

       Монолитное тестирование предоставляет
большие  возможности распараллеливания
работ, особенно на начальной фазе тестирования.

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

Например, порядок тестирования комплекса К (см. рис. 5.2) при нисходящем
тестировании может быть таким, как показано в примере 5.3, где тестовый набор,
разработанный для модуля M
i, обозначен как XYi =
(X, Y)
i.

1) К -> XYk

2) М1 -> XY1

3)M11->XY
11

4) М2
-> XY
2

5) М22
-> XY
22

6) M21
-> XY
21

7) M12->
XY
12

Пример 5.1.
Возможный порядок тестов при нисходящем тестировании (html, txt).

     Недостатки нисходящего
тестирования:

• проблема разработки достаточно «интеллектуальных»  заглушек, т. е. заглушек, способных к использованию
при моделировании различных режимов работы комплекса, 

необходимых для тестирования;

• сложность организации и разработки среды для реализации исполнения
модулей в нужной последовательности;

• параллельная разработка модулей верхних и нижних  уровней приводит к не всегда эффективной
реализации  модулей из-за подстройки
(специализации) еще не 

тестированных модулей нижних уровней к уже оттестированным  модулям верхних уровней.

Особенности восходящего
тестирования
в организации 
порядка сборки и перехода к тестированию модулей,  соответствующему порядку их реализации.

Например, порядок тестирования комплекса К (см. рис. 5.2) при восходящем
тестировании может быть следующим (см. 
пример 5.4).

1)М11->ХУ11

2) M12
-> XY
12

3) Мi
-> XY
i

4) М21
-> XY
21

5) M2(M21,
Stub(M
22))
-> XY
2

6) К(М1 ,M2(M21,
Stub(M
22))
-> XY
k

7) М22
-> XY
22

8) М2 -*
XY
2

9) К -> XYk

Пример 5.2. Возможный порядок тестов при восходящем  тестировании.

Недостатки восходящего тестирования:

• запаздывание проверки концептуальных особенностей  тестируемого комплекса;

• необходимость в разработке и использовании драйверов [33].

5.7. Системное тестирование

      Системное тестирование
качественно отличается от 
интеграционного и модульного уровней. Системное тестирование  рассматривает тестируемую систему в целом и
оперирует на уровне пользовательских интерфейсов, в отличие от последних
фаз 

интеграционного тестирования, которое оперирует на уровне  интерфейсов модулей. Различны и цели этих
уровней  тестирования. На уровне системы часто
сложно и малоэффективно  анализировать
прохождение тестовых траекторий внутри программы

или отслеживать правильность работы конкретных функций. 

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

       Системное тестирование
производится над проектом в целом с помощью метода «черного ящика». Структура
программы не имеет никакого значения, для проверки доступны только входы и
выходы, видимые пользователю. Тестированию подлежат коды и пользовательская
документация.

       Категории тестов системного
тестирования:

1. Полнота решения функциональных задач.

2. Стрессовое тестирование — на предельных объемах  нагрузки входного потока.

3. Корректность использования ресурсов (утечка памяти, возврат ресурсов).

4. Оценка производительности.

5. Эффективность защиты от искажения данных и  некорректных действий.

6. Проверка инсталляции и конфигурации на разных  платформах.

7. Корректность документации.

        Поскольку системное
тестирование проводится на 
пользовательских интерфейсах, создается иллюзия того, что построение
специальной системы автоматизации тестирования не всегда  необходимо. Однако объемы данных на этом уровне
таковы, что

обычно более эффективным подходом является полная или  частичная автоматизация тестирования, что
приводит к созданию тестовой системы, гораздо более сложной, чем система  тестирования, применяемая на уровне
тестирования модулей или их комбинаций.

5.8. Эффективность и
оптимизация программ

       Эффективными считаются
программы, требующие  минимального
времени выполнения и/или минимального объема оперативной памяти. Особые
требования к эффективности 

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

существенному ухудшению технологических свойств,  необходимо это требование иметь в виду [7].

       Разумный подход к обеспечению
эффективности  разрабатываемого
программного обеспечения состоит в том, чтобы в 
первую очередь оптимизировать те фрагменты программы, которые
существенно влияют на характеристики эффективности. Для

уменьшения времени выполнения некоторой программы в  первую очередь следует проанализировать
циклические фрагменты с большим количеством повторений: экономия времени  выполнения одной итерации цикла будет
умножена на количество  итераций.

      Не следует забывать и о
том, что многие способы снижения временных затрат приводят к увеличению
емкостных и,  наоборот, уменьшение объема
памяти может потребовать 

дополнительного времени на обработку.

      И тем более не следует
«платить» за увеличение  эффективности
снижением технологичности разрабатываемого  программного обеспечения. Исключения возможны
лишь при очень жестких требованиях и наличии соответствующего контроля за
качеством.

      Частично проблему
эффективности программ решают за программиста компиляторы.

     Средства оптимизации,
используемые компиляторами, делят на две группы:

машинно-зависимые, т. е.
ориентированные на конкретный машинный язык, выполняют оптимизацию кодов
на  уровне машинных команд, например,
исключение лишних 

пересылок, использование более эффективных команд и т. п.

машинно-независимые выполняют
оптимизацию на уровне входного языка, например, вынесение вычислений  константных (независящих от индекса цикла)
выражений из

циклов и т. п.

       Естественно, нельзя
вмешаться в работу компилятора, но 
существует много возможностей оптимизации программы на  уровне команд.

       Способы экономии памяти. Принятие мер по
экономии  памяти предполагает, что в
каких-то случаях эта память неэкономно использовалась. Учитывая, что
анализировать имеет смысл  только
операции размещения данных, существенно влияющие на  характеристику эффективности, следует
обращать особое  внимание на выделение
памяти под данные структурных типов 
(массивов, записей, объектов и т. п.).

       Прежде всего при наличии
ограничений на использование памяти следует выбирать алгоритмы обработки, не
требующие дублирования исходных данных структурных типов в процессе обработки.
Примером могут служить алгоритмы сортировки 
массивов, выполняющие операцию в заданном массиве, например хорошо
известная сортировка методом «пузырька».

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

      Также следует помнить, что
при передаче структурных  данных в
подпрограмму «по значению» копии этих данных 
размещаются в стеке. Избежать копирования иногда удается, если  передавать данные «по ссылке», но как
неизменяемые (описанные

const). В последнем случае в стеке размещается только адрес данных,
например:

Type Mas.4iv array [I. 100] of real;

function Summa (Const a:Massiv; .)

         Способы уменьшения времени выполнения. Как уже  упоминалось выше, для уменьшения времени
выполнения в первую  очередь необходимо
анализировать циклические участки 

программы с большим количеством повторений. При их написании  необходимо по возможности:

        
выносить вычисление константных, т. е. не зависящих от
параметров цикла, выражений из циклов;

        
избегать «длинных» операций умножения и деления,  заменяя их сложением, вычитанием и сдвигами;

        
минимизировать преобразования типов в выражениях;

        
оптимизировать запись условных выражений — исключать
лишние проверки;

        
исключать многократные обращения к элементам массивов по
индексам (особенно многомерных, так как при 
вычислении адреса элемента используются операции умножения на значение
индексов), первый раз прочитав из памяти элемент массива, следует запомнить его
в скалярной  переменной и использовать в
нужных местах;

        
избегать использования различных типов в выражении и т.
п.

Рассмотрим следующие примеры.

Пример 5.3. Пусть имеется цикл следующей структуры (Pascal):

for у: 0 to 99 do

for x: 0 to 99 do

а [320*х+у] S [k,l];

      В этом цикле операции
умножения и обращения к элементу S[k] выполняются 10 000 раз.

Оптимизируем цикл, используя, что 320 = 28 + 26:

ski: =S [k,l]; {выносим обращение к элементу массива из цикла}

for x: 0 to 99 do (меняем циклы местами}

begin

х shl 8 + х shl 6; {умножение заменяем на сдвиги и выносим из цикла)

for у; 0 to 99 do

a [i+y] =skl;

end;

В результате вместо 10 000 операций умножения будут  выполняться 200 операций сдвига, а их время
приблизительно сравнимо со временем выполнения операции сложения. 

Обращение к элементу массива S[k] будет выполнено 1 раз.

Пример 5.4. Пусть имеется цикл, в теле которого  реализовано сложное условие:

for k: 2 to n do

begin

ifx[k] > ук then S: S+y[k]-x [к];

if (x [k]< = yk) and (y[k]<yk) then S: = S+yk-x[k];

end;…

В этом цикле можно убрать лишние проверки:

for k: =2 to n do

begin

ifx [k]>yk then S:=S+y[к]-х[к]

else

ify[k]<yk then S: =S+yk-x [k];

end;

       Обратите внимание на то,
что в примере 5.3 понять, что  делает
программа, стало сложнее, а в примере 5.4 — практически нет. Следовательно,
оптимизация, выполненная в первом 

случае, может ухудшить технологичность программы, а потому не очень
желательна.

Поскольку тестировщику текст программы известен с детальностью до вызова
всех модулей, входящих в тестируемый комплекс, 
применение структурных критериев на данном этапе возможно и  оправданно.

Рис. 5.2. Пример структуры комплекса программ

      Интеграционное тестирование
применяется на этапе сборки модульно оттестированных модулей в единый
комплекс. 

монолитный, характеризующийся
одновременным  объединением всех модулей
в тестируемый комплекс;

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

— «снизу вверх» и соответственно нисходящее 
тестирование.

        Особенности монолитного тестирования заключаются в  следующем: для замены не разработанных к
моменту тестирования модулей, кроме самого верхнего (К на рис. 5.2),
необходимо  дополнительно разрабатывать
драйверы (test driver) и/или заглушки(stub) [9], замещающие отсутствующие на
момент сеанса  тестирования модули нижних
уровней.

       Сравнение монолитного и
интегрального подходов дает  следующие
результаты.

      Монолитное тестирование требует
больших трудозатрат, связанных с дополнительной разработкой драйверов и
заглушек и со сложностью идентификации ошибок, проявляющихся в  пространстве собранного кода.

       Пошаговое тестирование связано
с меньшей трудоемкостью идентификации ошибок за счет постепенного
наращивания  объема тестируемого кода и
соответственно локализации 

добавленной области тестируемого кода.

       Монолитное тестирование предоставляет
большие  возможности распараллеливания
работ, особенно на начальной фазе тестирования.

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

Например, порядок тестирования комплекса К (см. рис. 5.2) при нисходящем
тестировании может быть таким, как показано в примере 5.3, где тестовый набор,
разработанный для модуля M
i, обозначен как XYi =
(X, Y)
i.

Лабораторная работа. Тестирование программного обеспечения

Технологии разработки программного обеспечения: «Разработка через тестирование»

Цель работы: Знакомство с технологией «разработка через тестирование». Изучение инструментов
позволяющих применять данную технологию.

Общие сведения:

Разработка через тестирование (англ. test-driven development, TDD) — техника разработки программного
обеспечения, которая основывается на повторении очень коротких циклов разработки: сначала пишется тест,
покрывающий желаемое изменение, затем пишется код, который позволит пройти тест, и под конец проводится
рефакторинг нового кода к соответствующим стандартам.

TDD требует от разработчика создания автоматизированных модульных тестов, определяющих требования к коду
непосредственно перед написанием самого кода. Тест содержит проверки условий, которые могут либо выполняться,
либо нет. Когда они выполняются, говорят, что тест пройден. Прохождение теста подтверждает поведение,
предполагаемое программистом. Разработчики часто пользуются библиотеками для тестирования (англ. testing
frameworks) для создания и автоматизации запуска наборов тестов. На практике модульные тесты покрывают
критические и нетривиальные участки кода. Это может быть код, который подвержен частым изменениям, код, от
работы которого зависит работоспособность большого количества другого кода, или код с большим количеством
зависимостей.

Среда разработки должна быстро реагировать на небольшие модификации кода. Архитектура программы должна
базироваться на использовании множества сильно связанных компонентов, которые слабо сцеплены друг с другом,
благодаря чему тестирование кода упрощается.

TDD не только предполагает проверку корректности, но и влияет на дизайн программы. Опираясь на тесты,
разработчики могут быстрее представить, какая функциональность необходима пользователю. Таким образом, детали
интерфейса появляются задолго до окончательной реализации решения

Задание: Изучить библиотеки для тестирования Рассмотреть применение NUnit, ReSharper.

Модульное тестирование 

Цель работы: Овладение навыками модульного тестирования.

Общие сведения

Модульное тестирование — это тестирование программы на уровне отдельно взятых модулей, функций или классов. Цель
модульного тестирования состоит в выявлении локализованных в модуле ошибок в реализации алгоритмов, а также в
определении степени готовности системы к переходу на следующий уровень разработки и тестирования.

Модульное тестирование проводится по принципу «белого ящика», то есть основывается на знании внутренней структуры
программы, и часто включает те или иные методы анализа покрытия кода. Модульное тестирование обычно
подразумевает создание вокруг каждого модуля определенной среды, включающей заглушки для всех интерфейсов
тестируемого модуля. Некоторые из них могут использоваться для подачи входных значений, другие для анализа
результатов, присутствие третьих может быть продиктовано требованиями, накладываемыми компилятором и сборщиком.

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

Задание 1) «Мозговая атака»:

Этапы:

— Получить вопрос (задание) для обсуждения.

— Задать вопросы относительно не понятных моментах в вопросе (задании).

— Высказать свои мысли по данному вопросу (заданию).

— Записать все прозвучавшие высказывания с уточнениями.

— По окончанию прочитать все, что было записано.

— Обсудить все варианты ответов.

— Выяснить, как можно использовать полученные результаты при выполнении данной лабораторной работы.

2) «Работа в команде»

Этапы:

— Разбиться на команды.

— Реализовать полученный вопрос (задание), согласно технологии TDD.

— Представить результаты.

Интеграционное тестирование 

Цель работы: Овладение навыками интеграционного тестирования.

Общие сведения:

Интеграционное тестирование называют еще тестированием архитектуры системы. С одной стороны, это название
обусловлено тем, что интеграционные тесты включают в себя проверки всех возможных видов взаимодействий между
программными модулями и элементами, которые определяются в архитектуре системы — таким образом, интеграционные
тесты проверяют полноту взаимодействий в тестируемой реализации системы. С другой стороны, результаты выполнения
интеграционных тестов — один из основных источников информации для процесса улучшения и уточнения архитектуры
системы, межмодульных и межкомпонентных интерфейсов. Т.е., с этой точки зрения, интеграционные тесты проверяют
корректность взаимодействия компонент системы.

В результате проведения интеграционного тестирования и устранения всех выявленных дефектов получается
согласованная и 7 целостная архитектура программной системы, т.е. можно считать, что интеграционное тестирование
— это тестирование архитектуры и низкоуровневых функциональных требований.

Интеграционное тестирование, как правило, представляет собой итеративный процесс, при котором проверяется
совокупность модулей, возрастающая от итерации к итерации. В интеграционном тестирование выделяют три методы
выполнения: восходящее тестирование; монолитное тестирование; нисходящее тестирование.

Задание:Согласно варианту провести один из методов интеграционного тестирования.

Системное тестирование 

Цель работы: Овладение навыками системного тестирования.

Общие сведения:

Системное тестирование — один из самых сложных видов тестирования. На этапе системного тестирования проводится не
только функциональное тестирование, но и оценка характеристик качества системы — ее устойчивости, надежности,
безопасности и производительности. На этом этапе выявляются многие проблемы внешних интерфейсов системы,
связанные с неверным взаимодействием с другими системами, аппаратным обеспечением, неверным распределением
памяти, отсутствием корректного освобождения ресурсов и т.п.

После завершения системного тестирования разработка переходит в фазу приемо-сдаточных испытаний (для программных
систем, разрабатываемых на заказ) или в фазу альфа- и бетатестирования (для программных систем общего
применения).

Системное тестирование проводится в несколько фаз, на каждой из которых проверяется один из аспектов поведения
системы, т.е. проводится один из типов системного тестирования. Все эти фазы могут протекать одновременно или
последовательно. Следующий 8 раздел посвящен рассмотрению особенностей каждого из типов системного тестирования
на каждой фазе.

Виды системного тестирования:

1)функциональное тестирование;

2)тестирование производительности;

3)нагрузочное или стрессовое тестирование;

4)тестирование конфигурации;

5)тестирование безопасности;

6)тестирование надежности и восстановления после сбоев;

7)тестирование удобства использования.

Задание: Согласно варианту провести несколько видов системного тестирования.

Ручное тестирование, генерация тестов 

Цель работы: Овладение навыками ручного тестирования и составление тестовых случаев.

Общие сведения:

Ручное тестирование заключается в выполнении задокументированной процедуры, где описана методика выполнения
тестов, задающая порядок тестов и для каждого теста — список значений параметров, который подается на вход, и
список результатов, ожидаемых на выходе. Поскольку процедура предназначена для выполнения человеком, в ее
описании для краткости могут использоваться некоторые значения по умолчанию, ориентированные на здравый смысл,
или ссылки на информацию, хранящуюся в другом документе.

Описание тестов разрабатывается для облегчения анализа и поддержки тестового набора. Описание может быть
реализовано в произвольной форме, но при этом должны выполнять следующие задачи:

1.Анализировать степень покрытия продукта тестами на основании описания тестового набора.

2.Для любой функции тестируемого продукта найти тесты, в которых функция используется.

3.Для любого теста определить все функции и их сочетания, которые данный тест использует (затрагивает).

4.Понять структуру и взаимосвязи тестовых файлов.

5.Понять принцип построения системы автоматизации тестирования

Задание: Подготовить тестовый случай, выполнить и задокументировать результаты.

Документация 

Цель работы: Овладение навыками документирования результатов тестирования.

Общие сведения:

Каждый дефект, обнаруженный в процессе тестирования, должен быть задокументирован и отслежен. При обнаружении
нового дефекта его заносят в базу дефектов. При занесении нового дефекта рекомендуется указывать, как минимум,
следующую информацию:

1) Наименование подсистемы, в которой обнаружен дефект.

2) Версия продукта (номер build), на котором дефект был найден.

3) Описание дефекта.

4) Описание процедуры (шагов, необходимых для воспроизведения дефекта).

5) Номер теста, на котором дефект был обнаружен.

6) Уровень дефекта, то есть степень его серьезности с точки зрения критериев качества продукта или заказчика.

Тестовый отчет обновляется после каждого цикла тестирования и должен содержать следующую информацию для каждого
цикла:

1) Перечень функциональности в соответствии с пунктами требований, запланированный для тестирования на данном
цикле, и реальные данные по нему.

2) Количество выполненных тестов – запланированное и реально исполненное.

3) Время, затраченное на тестирование каждой функции, и общее время тестирования.

4) Количество найденных дефектов.

5) Количество повторно открытых дефектов.

6) Отклонения от запланированной последовательности действий, если таковые имели место.

7) Выводы о необходимых корректировках в системе тестов, которые должны быть сделаны до следующего тестового
цикла.

Задание: Задокументировать результаты тестирования. Для выполнения работы использовать тестовые
случае из предыдущей лабораторной работы.

Unit testing — один из обязательных инструментов в арсенале любого уважающего себя разработчика ПО, желающего сделать код более надежным и простым в обслуживании. Не каждый программист им пользуется ввиду отсутствия фундаментальных знаний о самом процессе тестирования и его методах.

Помогаем

Unrecognizable

В этой статье детально рассмотрим данный вид тестирования, а также разберемся с грамотным написанием модульных кейсов.

Содержание:
1. Что такое модульное тестирование?
2. Модульный тест против интеграционного теста
3. Что делает хороший модульный тест?
4. Почему именно модульное тестирование?
5. Как проводить модульное тестирование
6. Тестирование с помощью пирамиды Майка Кона
7. Методы unit-тестирования
8. Инструменты
9. Разработка через тестирование (TDD)
10. Преимущество модульного тестирования
11. Недостатки модульного тестирования
Заключение

1. Что такое модульное тестирование?

Модульное тестирование — это метод тестирования программного обеспечения при котором создаются модули, то есть небольшие части приложения, поведение каждого из которых проверяется отдельно. Модульное тестирование выполняется на этапе разработки приложения. Модулем может быть что угодно, например, процедура или функция.

Модульное тестирование состоит из трех этапов:

Курс

МЕНЕДЖЕР ПО РОБОТІ З КЛІЄНТАМИ

Ставайте затребуваним фахівцем та отримуйте свій офер мрії.

РЕЄСТРУЙТЕСЯ!

manager

  1. Во-первых, инициализация небольшого фрагмента приложения, которое вы хотите протестировать.
  2. Для этого вызывается метод, применяемый в качестве стимула к тестируемой системе.
  3. Последний этап: наблюдение за поведением проверяемого модуля. Если наблюдаемое поведение соответствует ожиданиям пользователя, то модульный тест проходит. Этот пошаговый процесс также называется AAA (Arrange, Act, Assert).

Модульный тест бывает двух видов: на основе состояния, а также на основе взаимодействия. Если вы отслеживаете полученное состояние — это модульное тестирование на основе состояния.

Если тестирование происходит при использовании определенных методов — это модульное тестирование на основе взаимодействия. Пользователи часто путают модульное и интеграционное тестирование. Прежде чем продолжить обзор важно понять эту разницу.

2. Модульный тест против интеграционного теста

Целью модульного тестирования является проверка поведения каждой части программного обеспечения, независимо от других частей. Модульные тесты имеют более узкую область применения, позволяют нам охватить все случаи и гарантировать, что каждый отдельно взятый участок работает безупречно. Такое тестирование довольно легко реализовать.

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

Когда эти два вида тестирования объединяются — мы получаем высокий уровень уверенности в том, что вся система работает должным образом. Однако всегда нужно помнить о том, какой тест мы реализуем: модульный или интеграционный.

Бывают случаи, когда модульные тесты требуют наличия внешних ресурсов, таких как веб-серверы или база данных. В большинстве случаев причина кроется в плохом дизайне модульного теста. Давайте узнаем, как сделать хороший модульный тест, чтобы избежать подобных проблем.

3. Что делает хороший модульный тест?

Модульные тесты применяются для проверки различных аспектов приложения, не затрачивая много времени и усилий со стороны разработчиков.

Такие тесты:

  • Работают, даже если в тестируемой системе есть ошибка. На хорошие модульные тесты не будут влиять внешние факторы, такие как окружающая среда или порядок выполнения. Если это произойдет, то это определенно недостаток дизайна.
  • Забегая вперед, скажу, что хороший модульный тест ясен и рассказывает историю о поведенческом аспекте приложения. С помощью тестовых примеров легко понять, какой сценарий был протестирован. Если тест не проходит, то исправить ошибки можно быстро и без отладки кода.
  • Модульные тесты написаны таким образом, что их можно использовать многократно.

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

4. Почему именно модульное тестирование?

Иногда разработчики пропускают unit-тестирование из-за нехватки времени, не понимая, к чему это может привести. Это влечет за собой увеличение затрат на исправление дефектов во время интеграции, на бета-тестирование и последующее тестирование системы. Правильное модульное тестирование во время разработки приложения экономит время и деньги. Вот основные его преимущества:

  1. Вы сможете исправить дефекты на ранней стадии разработки, сэкономив ресурсы.
  2. Это помогает разработчику быстро разобраться с кодовой базой и быстро внести необходимые изменения.
  3. Хорошие модульные тесты обычно служат проектной документацией.
  4. Модульные тесты можно использовать повторно и при необходимости быстро перенести в новый проект. Вам всего лишь следует немного подправить код, чтобы он снова запустился.

5. Как проводить модульное тестирование

Модульное тестирование делится на ручное и автоматическое. Автоматизация модульных тестов — хорошая практика, ее также можно выполнять вручную. При ручном подходе используется документ с пошаговыми инструкциями. Перечислим по пунктам все составляющие автоматизированного подхода:

  1. Разработчик пишет модульные тесты, чтобы проверить функциональность конкретной части приложения. Они закомментированы и будут удалены позже, после успешного развертывания приложения.
  2. Функция должна быть изолирована, чтобы ее можно было проверить более тщательно. Лучшая практика unit-тестирования — копировать и вставлять код в тестовую среду, вместо работы в естественной среде. Изолированный код помогает выявить и устранить зависимости между тестируемым кодом и пространствами данных.
  3. Существует среда модульного тестирования для разработки автоматизированных тестовых случаев. Эта среда автоматизации помогает писать код и проверяет, правильно ли написан код. Во время выполнения модульных тестов платформа регистрирует статус тестовых случаев. В зависимости от серьезности сбоев структура может остановить последующее тестирование.
  4. Рабочий процесс модульного тестирования разделен на четыре категории: это создание тестовых примеров, обзор, базовый уровень и выполнение тестовых примеров.

6. Тестирование с помощью пирамиды Майка Кона

Ни один разговор о тестировании не обходится без упоминания пирамиды тестов, подробно описанную разработчиком Майклом Коном в его книге «Scrum: гибкая разработка ПО»:

Unit testing

Источник: leeorengel.com

Пирамида должна определять ваш подход к написанию тестов. Она показывает вам, на чем можно сосредоточить свое время. Наиболее важные выводы из диаграммы:

  • Чем выше вы поднимаетесь по пирамиде, тем медленнее проходят тесты. Это приводит к увеличению продолжительности цикла обратной связи. Более длительное время цикла обратной связи приводит к увеличению времени на отслеживание причины сбоев, поскольку они обычно происходят дальше от источника реальной проблемы.
  • Чем выше вы поднимаетесь по пирамиде, тем дороже становится писать и поддерживать тесты. По мере продвижения вверх по пирамиде элементы тестируемой системы и их взаимодействие становятся все более сложными.
  • Наличие большего количества тестов на более низком уровне является желательной целью. Это очень желательно, но не всегда возможно. Напишите тест там, где он действительно подходит. Подробнее об этом ниже.

Определения тестов

Что касается типов тестов на каждом уровне пирамиды, то существуют разные мнения о том, что включает в себя каждый тип.

  1. Модульные тесты — как мы уже с вами разобрали, тестируют наименьшую единицу кода — обычно функцию, класс или структуру данных. Самым важным аспектом модульных тестов является их скорость.
  2. Компонентные тесты — это по сути интеграционные тесты. Их цель — выделить правильную функцию отдельного компонента.
  3. Интеграционные тесты обычно включают в себя тестирование взаимодействия между двумя или более компонентами системы.
  4. Системные тесты — служат для тестирования экземпляра (или части) вашей реальной системы. Обычно они соответствуют конфигурации тестовой среды вашей системы. Эти тесты могут быть дорогостоящими для начальной загрузки и медленными для выполнения.
  5. Manual или браузерные тесты — это тип системных тестов, ориентирующихся на пожеланиях конечного пользователя. Их следует ограничить сценариями тестирования, охватывающими многоэтапные или многостраничные рабочие процессы, которые невозможно эффективно протестировать другими способами.

7. Методы unit-тестирования

Ниже приведены методы покрытия кода при модульном тестировании:

  • Тестирование белого ящика — с помощью пользовательского интерфейса проверяются ввод и вывод.
  • Тестирование черного ящика — помогает проверить поведение различных функций, используемых в приложении.
  • Тестирование серого ящика — используется для выполнения тестов, рисков и методов оценки.

8.Инструменты

Существует множество автоматизированных инструментов, помогающих при модульном тестировании. Здесь, для примера, рассмотрим самые популярные из них.

  • Jtest — это плагин IDE, использующий фреймворки с открытым исходным кодом, с управляемыми и простыми действиями в один щелчок — для создания, масштабирования и поддержки модульных тестов. Автоматизация этих трудоемких аспектов модульного тестирования позволяет разработчикам больше сосредоточиться на бизнес-логике и создавать более мощные наборы тестов.
  • Junit — бесплатный инструмент для тестирования, основанный на языке программирования Java. Он предоставляет утверждения для определения различных методов тестирования и проверяет данные, прежде чем вставлять их в фрагмент кода.
  • NUnit — широко используемая среда тестирования, позволяющая писать скрипты вручную. Поддерживает параллельное выполнение тестов.
  • JMockit — снова инструмент тестирования с открытым исходным кодом, позволяющий имитировать API с проверкой и записью синтаксиса.
  • EMMA — это набор инструментов с открытым исходным кодом для анализа и составления отчетов по коду, написанному на языке Java. Предназначен для тестирования методов, строк и базовых блоков, он может обращаться к коду без какой-либо внешней библиотеки.
  • PHPUnit — это популярный инструмент тестирования для программистов PHP. Инструмент позволяет разработчикам использовать предопределенные методы утверждения, чтобы убедиться, что система ведет себя определенным образом.

Это всего лишь несколько инструментов unit-тестирования, пользующихся большой популярностью на рынке технологий. Их гораздо больше, и вы можете выбрать любой из них в зависимости от ваших потребностей, требований и вкусов.

9. Разработка через тестирование (TDD)

Unit-testing при разработке предполагает широкое использование специальных фреймворков. Вот несколько фактов о том, что TDD привносит в мир модульного тестинга:

  • Модульные тесты часто пишутся до самого кода.
  • Применение фреймворков тестирования для написания или автоматизации модульных тестов.
  • Используется для тестирования всех классов в приложении.
  • Легкая и быстрая интеграция.

10. Преимущества модульного тестирования

  1. Модульное тестирование упрощает изменение и поддержку кода. Когда написаны хорошие модульные тесты, они могут выявлять проблемы каждый раз, когда код запускается или изменяется.
  2. Unit-тесты можно использовать повторно.
  3. Модульное тестирование ускоряет разработку. Все, что вам нужно — запустить графический интерфейс и предоставить все необходимые входные данные.
  4. Модульные тесты более надежны и в долгосрочной перспективе выполняются быстрее. Усилия, прилагаемые для написания и исправления дефектов во время модульного тестирования, намного меньше по сравнению с усилиями, необходимыми для исправления ошибок во время тестирования системы или приемочного тестирования.
  5. Менее затратное по времени и другим ресурсам.
  6. Модульное тестирование упрощает отладку. Если тест не проходит, последние изменения необходимо снова отладить. Тестирование на более высоких уровнях позволяет сканировать изменения, внесенные за несколько дней, недель, месяцев и т. д.
  7. Такой подход улучшает дизайн кода и позволяет проводить его рефакторинг. В процессе написания тестовых примеров для методов или функций всякий раз, когда изменения вызывают ошибку, ее можно быстро идентифицировать или при необходимости исправить.
  8. Модульные тесты при интеграции также дают равенство сборки.
  9. Разработчики могут понять, какие функции выполняет конкретный модуль, и взглянуть на модульные тесты, чтобы получить базовое представление об API.
  10. Поскольку unit-тесты являются модульными, можно тестировать выбранную часть кода, не дожидаясь завершения другой.

11. Недостатки модульного тестирования

  • Модульное тестирование не выявляет всех ошибок в программе.
  • Не может оценить все пути выполнения, даже в тривиальных программах.
  • В основном фокусируется на единицах измерения и не может выявлять ошибки интеграции на более широком уровне.

Общее правило таково: unit-тестирование следует выполнять в сочетании с другими тестами, чтобы получить более точные результаты.

Правила работы с unit-тестами:

  1. Каждый модуль должен быть независимым. Любое изменение в нем не должно влиять на другие юниты.
  2. Используется для одновременной проверки только одного кода.
  3. Вы всегда должны использовать четкие и последовательные соглашения об именах.
  4. Для каждого модуля должен быть отдельный тестовый пример перед отправкой на реализацию.
  5. Ошибки следует исправить заранее, прежде чем переходить к следующему этапу в SDLC (System/Software Development Life Cycle).
  6. Чем больше кода вы напишете, тем больше у вас будет путей для проверки на наличие ошибок.

Заключение

Unit-тесты могут быть простыми или сложными в зависимости от характера тестируемого объекта, инструментов и стратегий, используемых для тестирования. Здесь можно быть уверенным только в одном: модульное тестирование сделает вашу программу более надежной и функциональной по сравнению с непроверенными приложениями.

Несколько полезных видеороликов по теме:

0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии

А вот еще интересные материалы:

  • Яшка сломя голову остановился исправьте ошибки
  • Ясность цели позволяет целеустремленно добиваться намеченного исправьте ошибки
  • Ясность цели позволяет целеустремленно добиваться намеченного где ошибка
  • Какие виды ошибок измерения вы знаете
  • Какие виды ошибок допускаются при несоблюдении языковых норм