Верификация программного обеспечения

Тестирование программного кода (тестовые примеры)

Разбить на страницы
Показывать лекцию целиком

4.1. Тестовые примеры

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

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

4.1.1. Тест-требования как основной источник информации для создания тестовых примеров

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

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

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

Функциональные требования на модуль расчета и проверки контрольной суммы

Внешний интерфейс модуля
   1.  Структура record_type
      struct record_type
      {
         bool A;
         int B[20];
         signed char C[5];
         unsigned int CRC;
         double D[1];
      }
   2.  Переменная Empty
      bool Empty;
   3.  Функция подсчета контрольной суммы записи Set_CRC
      void Set_CRC(record_type record);

       Вход:
        Запись record, c неопределенным значением поля CRC.
       Выход:
        Запись record, с вычисленным по заданным правилам значение поля CRC.
        Переменная Empty.

   4.  Функция проверки контрольной суммы записи Сheck_CRC
      bool Check_CRC(record_type record);

       Вход:
        Запись Rec_Mess c определенным значением поля CRC.
       Выход:
        Возвращаемое значение true или false. Переменная Empty.

Функциональные требования
   1.  Инициализация модуля
      При инициализации модуля переменная Empty должна быть установлена в значение TRUE.

   2.  Подсчет контрольной суммы записи
         a.  Расчет контрольной суммы
              Процедура Set_CRC должна производить подсчет контрольной суммы записи Rec_Mess по алгоритму CRC32. 
           При подсчете контрольной суммы значение поля CRC не должно участвовать в суммировании. На основании 
           произведенных расчетов должно быть вычислено и определено значение поля CRC таким образом, чтобы при 
           подсчете контрольной суммы вместе с установленным значением этого поля контрольная сумма равнялась нулю.

         b.  Установка значения переменной Empty
              Если все байты полей записи (кроме возможно CRC поля) имеют нулевое значение (код 00000000B), 
           то значение переменной Empty должно быть установлено в TRUE.
              Ели хотя бы один байт записи (исключая байты поля CRC) не нулевой, то значение переменной   
           Empty должно быть установлено в FALSE. 

   3.  Проверка контрольной суммы записи

         a.  Проверка контрольной суммы
              Процедура должна вычислять по заданному алгоритму CRC32 контрольную сумму записи Rec_Mess. 
           Возвращаемое процедурой значение должно быть равно TRUE, если подсчитанное значение равно нулю. 
           При ненулевом значении подсчитанной контрольной суммы должно возвращаться значение FALSE.

         b.  Установка значения переменной Empty
              Если все байты полей записи, включая значение CRC поля, имеют нулевое значение (код 00000000B), 
           то значение переменной Empty должно быть установлено в TRUE.
              Ели хотя бы один байт записи не нулевой, то значение переменной   Empty должно быть установлено в FALSE.

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

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

Тест-требования

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

   2.  Проверка подсчета контрольной суммы

         a.  Проверить, что в процедуре Set_CRC вычисление контрольной суммы производится по правилам алгоритма CRC32, 
             как определено в секции 2a функциональных требований.
         b.  Проверить, что вычисленное значение контрольной суммы не зависит от начального значения поля CRC.
         c.  Проверить, что вычисленное значение контрольной суммы не зависит от значений байт выравнивания полей записи.
         d.  Проверить, что значение переменной Empty устанавливается при каждом вызове функции Set_CRC в зависимости от значений 
             полей записи, как определено в секции 2b функциональных требований.  

   3.  Проверка процедуры Check_CRC
 
         a.  Проверить, что при обращении к процедуре Check_CRC вычисление контрольной суммы производится по правилам 
             алгоритма CRC32, как определено в секции 3a функциональных требований.
         b.  Проверить, что возвращаемое значение равно TRUE, если контрольная сумма проверяемой записи правильная, 
             и FALSE - в противном случае.
         c.  Проверить, что проверка правильности  значения контрольной суммы не зависит от значений байт выравнивания 
             полей записи.
         d.  Проверить, что значение переменной Empty устанавливается при каждом вызове функции Check_CRC в зависимости 
             от значений полей записи, как определено в секции 3b функциональных требований.

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

4.1.2. Типы тестовых примеров

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

  • Допустимые данные

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

    record_type   test_value1;
    int       i;
    
    test_value1.A         = false;
    for (i=0;i<20;i++)
       test_value1.B[i]   = i;
    for (i=0;i<5;i++)
       test_value1.C[i]   = i+5;
    test_value1.D[0]     = i+8;
    test_value1.CRC     = 0;
    
    Set_CRC(test_value1);
    
    printf("%d\n", test_value1.CRC);

    Сценарием будет вызов функции Set_CRC, а ожидаемым выходным значением - корректное значение поля CRC, рассчитанное по алгоритму CRC32.

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

  • Граничные данные

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

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

    record_type   test_value2;
    record_type   test_value3;
    int         i;
    
    test_value2.A       = false;
    for (i=0;i<20;i++)
       test_value2.B[i]    = 0;
    for (i=0;i<5;i++)
       test_value2.C[i]    = 0;
    test_value2.D[0]    = 0;
    test_value2.CRC  = 0;
    
    Set_CRC(test_value2);
    
    printf("%d\n", test_value2.CRC);
    
    test_value3.A       = true;
    for (i=0;i<20;i++)
       test_value3.B[i]   = pow(2,sizeof(test_value3.B[i])*8)-1;
    for (i=0;i<5;i++)
       test_value3.C[i]    = pow(2,sizeof(test_value3.C[i])*8)-1;
    test_value3.D[0]    = pow(2,sizeof(test_value3.D[0])*8)-1;
    test_value3.CRC  = pow(2,sizeof(test_value3.CRC)*8)-1;
    
    Set_CRC(test_value3);
    
    printf("%d\n", test_value3.CRC);
  • Отсутствие данных

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

    record_type test_value4;
    
    Set_CRC(test_value4);
    
    printf("%d\n", test_value4.CRC);
  • Повторный ввод данных

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

    record_type   test_value5;
    int       i;
    
    test_value5.A       = false;
    for (i=0;i<20;i++)
       test_value5.B[i]    = i;
    for (i=0;i<5;i++)
       test_value5.C[i]    = i+5;
    test_value5.D[0]    = i+8;
    test_value5.CRC  = 0;
    
    Set_CRC(test_value5);
    printf("%d\n", test_value5.CRC);
    
    Set_CRC(test_value5);
    printf("%d\n", test_value5.CRC);
  • Неверные данные

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

    struct record_type2
    {
      int F;
      int G[45];
      int H[8];
      unsigned int CRC;
      int K[2];
    }
    record_type2   test_value6;
    
    Set_CRC((record_type)test_value6);
    
    printf("%d\n", test_value6.CRC);
  • Реинициализация системы

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

    Примером реинициализации модуля вычисления CRC может служить принудительное обнуление переменной empty.

    record_type   test_value7;
    int       i;
    
    test_value7.A       = false;
    for (i=0;i<20;i++)
       test_value7.B[i]    = i;
    for (i=0;i<5;i++)
       test_value7.C[i]    = i+5;
    test_value7.D[0]    = i+8;
    test_value7.CRC  = 0;
    
    Set_CRC(test_value7);
    
    printf("%d\n", test_value1.CRC);
    
    empty=true;
    
    Set_CRC(test_value7);
    
    printf("%d\n", test_value1.CRC);
  • Устойчивость системы

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

    record_type   test_value8;
    int       i;
    
    test_value8.A       = false;
    for (i=0;i<20;i++)
       test_value8.B[i]    = i;
    for (i=0;i<5;i++)
       test_value8.C[i]    = i+5;
    test_value8.D[0]    = i+8;
    test_value8.CRC  = 0;
    
    for (i=0;i<10000;i++)
       Set_CRC(test_value8);
    
    printf("%d\n", test_value1.CRC);

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

  • Нештатные состояния среды выполнения

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

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

    record_type   test_value9;
    int       i;
    int       *heap;
    
    heap = malloc(_MAXMEM);
    
    test_value9.A       = false;
    for (i=0;i<20;i++)
       test_value9.B[i]    = i;
    for (i=0;i<5;i++)
       test_value9.C[i]    = i+5;
    test_value9.D[0]    = i+8;
    test_value9.CRC  = 0;
    
    Set_CRC(test_value9);
    
    free(heap);
    
    printf("%d\n", test_value9.CRC);
  • 4.1.2.1. Граничные условия

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

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

  • Значение внутри диапазона
  • Минимальное значение
  • Минимальное значение + 1
  • Максимальное значение
  • Максимальное значение - 1
  • Такой способ проверки называется проверкой на граничных значениях. Такая проверка позволяет выявлять проблемы, связанные с выходом за границы диапазона. Например, если в функцию

    char sum(char a, char b)
    {
       return a+b;
    }

    вычисляющую сумму чисел a и b, будут переданы значения 255 и 255, то в случае отсутствия специальной обработки ситуации переполнения сумма будет вычислена неверно.

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

    void abs_array(char array[], char size)
    {
       for (int i=1;i<=size;i++)
       {
             array[i] = abs(array[i]);
       }
       return;
    }

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

    4.1.3. Проверка робастности (выхода за границы диапазона)

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

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

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

    Таким образом, если изобразить допустимый интервал как на Рис 4.1, то можно видеть, что для тестирования интервальных значений достаточно 7 тестовых примеров - пяти допустимых и двух на робастность.

    (рис 4.1) Рекомендуемые проверочные значения

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

    4.1.4. Классы эквивалентности

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

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

    Рассмотренные выше граничные условия могут служить примером классов эквивалентности:

  • Значение из середины интервала.
  • Граничные значения.
  • Недопустимые значения за границами интервала.
  • Таким образом, тестирование граничных условий и робастности является частным случаем тестирования с использованием классов эквивалентности - вместо того, чтобы тестировать все недопустимые значения, выбираются только соседние с граничными.

    При определении классов эквивалентности следует руководствоваться следующими правилами:

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

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

    Можно выделить следующие классы эквивалентности:

    По длине имени:

  • Длина имени меньше 11 символов
  • Длина имени равна 11 символам
  • Длина имени больше 11 символов
  • По символам:

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

    4.1.5. Тестирование операций сравнения чисел

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

    В таблице 4.1 приведены тестовые наборы для блоков, реализующих операции сравнения, в случае, когда на один из входов блока подается константа.

    Блоки сравнения и определенные для них тестовые наборы
    greaterThan блок. Реализует операцию сравнения greaterEq блок. Реализует операцию сравнения
    № набора 1 2 3* 4 5 № набора 1 2 3* 4 5
    Вход ab - d b + d b min max Вход ab - d b + d b min max
    ВыходF T F F T ВыходF T T F T
    lessThan блок. Реализует операцию сравнения lessEq блок. Реализует операцию сравнения
    № набора 1 2 3* 4 5 № набора 1 2 3* 4 5
    Вход ab - d b + d b min max Вход ab - d b + d b min max
    ВыходT F F T F ВыходT F T T F
    equalTo блок. Реализует операцию сравнения notEqualTo блок. Реализует операцию сравнения
    № набора 1 2 3 4 № набора 1 2 3 4
    Вход a$$\ne$$ b b min max Вход a$$\ne$$ b b min max
    ВыходF T F F ВыходT F T T

    * тестовый набор реализуем, только если переменная на входе a - переменная целого типа

    В приведенных тестовых наборах используются следующие обозначения:

  • d - шаг изменения ( resolution ) переменной на входе a. Если переменная на входе a - переменная целого типа, то d равно 1;
  • min - минимальное значение переменной на входе a ;
  • max - максимальное значение переменной на входе a.
  • В таблице 4.2 приведены тестовые наборы для блоков, реализующих операции сравнения, в случае, когда на оба входа блока подаются переменные.

    Блоки сравнения и определенные для них тестовые наборы (продолжение)
    greaterThan блок. Реализует операцию сравнения greaterEq блок. Реализует операцию сравнения
    № набора 1 2 3* 4 5 № набора 1 2 3* 4 5
    Вход aval val val min max Вход aval val val min max
    Вход bval + d2 val - d2 val max min Вход bval + d2 val - d2 val max min
    ВыходF T F F T ВыходF T T F T
    lessThan блок. Реализует операцию сравнения lessEq блок. Реализует операцию сравнения
    № набора 1 2 3* 4 5 № набора 1 2 3* 4 5
    Вход aval val val min max Вход aval val val min max
    Вход bval + d2 val - d2 val max min Вход bval + d2 val - d2 val max min
    ВыходT F F T F ВыходT F T T F
    equalTo блок. Реализует операцию сравнения notEqualTo блок. Реализует операцию сравнения
    № набора 1 2 3 4 № набора 1 2 3 4
    Вход aval1 val min max Вход aval1 val min max
    Вход bval2 val max min Вход bval2 val max min
    ВыходF T F F ВыходT F T T

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

    В приведенных тестовых наборах используются следующие обозначения:

  • d2 - шаг изменения ( resolution ) переменной на входе b. Если переменная на входе b - переменная целого типа, то d2 равно 1;
  • val, val1, val2 - значения, которые взяты из середины диапазона, полученного при пересечении диапазонов переменных на входах a и b ;
  • min - минимальное значение переменной на входе блока;
  • max - максимальное значение переменной на входе блока.
  • Страницы:

    4.1. Тестовые примеры

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

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

    4.1.1. Тест-требования как основной источник информации для создания тестовых примеров

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

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

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

    Функциональные требования на модуль расчета и проверки контрольной суммы
    
    Внешний интерфейс модуля
       1.  Структура record_type
          struct record_type
          {
             bool A;
             int B[20];
             signed char C[5];
             unsigned int CRC;
             double D[1];
          }
       2.  Переменная Empty
          bool Empty;
       3.  Функция подсчета контрольной суммы записи Set_CRC
          void Set_CRC(record_type record);
    
           Вход:
            Запись record, c неопределенным значением поля CRC.
           Выход:
            Запись record, с вычисленным по заданным правилам значение поля CRC.
            Переменная Empty.
    
       4.  Функция проверки контрольной суммы записи Сheck_CRC
          bool Check_CRC(record_type record);
    
           Вход:
            Запись Rec_Mess c определенным значением поля CRC.
           Выход:
            Возвращаемое значение true или false. Переменная Empty.
    
    Функциональные требования
       1.  Инициализация модуля
          При инициализации модуля переменная Empty должна быть установлена в значение TRUE.
    
       2.  Подсчет контрольной суммы записи
             a.  Расчет контрольной суммы
                  Процедура Set_CRC должна производить подсчет контрольной суммы записи Rec_Mess по алгоритму CRC32. 
               При подсчете контрольной суммы значение поля CRC не должно участвовать в суммировании. На основании 
               произведенных расчетов должно быть вычислено и определено значение поля CRC таким образом, чтобы при 
               подсчете контрольной суммы вместе с установленным значением этого поля контрольная сумма равнялась нулю.
    
             b.  Установка значения переменной Empty
                  Если все байты полей записи (кроме возможно CRC поля) имеют нулевое значение (код 00000000B), 
               то значение переменной Empty должно быть установлено в TRUE.
                  Ели хотя бы один байт записи (исключая байты поля CRC) не нулевой, то значение переменной   
               Empty должно быть установлено в FALSE. 
    
       3.  Проверка контрольной суммы записи
    
             a.  Проверка контрольной суммы
                  Процедура должна вычислять по заданному алгоритму CRC32 контрольную сумму записи Rec_Mess. 
               Возвращаемое процедурой значение должно быть равно TRUE, если подсчитанное значение равно нулю. 
               При ненулевом значении подсчитанной контрольной суммы должно возвращаться значение FALSE.
    
             b.  Установка значения переменной Empty
                  Если все байты полей записи, включая значение CRC поля, имеют нулевое значение (код 00000000B), 
               то значение переменной Empty должно быть установлено в TRUE.
                  Ели хотя бы один байт записи не нулевой, то значение переменной   Empty должно быть установлено в FALSE.

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

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

    Тест-требования
    
       1.  Проверка инициализация модуля
           Проверить, что начальное значение переменной Empty  установлено TRUE.
    
       2.  Проверка подсчета контрольной суммы
    
             a.  Проверить, что в процедуре Set_CRC вычисление контрольной суммы производится по правилам алгоритма CRC32, 
                 как определено в секции 2a функциональных требований.
             b.  Проверить, что вычисленное значение контрольной суммы не зависит от начального значения поля CRC.
             c.  Проверить, что вычисленное значение контрольной суммы не зависит от значений байт выравнивания полей записи.
             d.  Проверить, что значение переменной Empty устанавливается при каждом вызове функции Set_CRC в зависимости от значений 
                 полей записи, как определено в секции 2b функциональных требований.  
    
       3.  Проверка процедуры Check_CRC
     
             a.  Проверить, что при обращении к процедуре Check_CRC вычисление контрольной суммы производится по правилам 
                 алгоритма CRC32, как определено в секции 3a функциональных требований.
             b.  Проверить, что возвращаемое значение равно TRUE, если контрольная сумма проверяемой записи правильная, 
                 и FALSE - в противном случае.
             c.  Проверить, что проверка правильности  значения контрольной суммы не зависит от значений байт выравнивания 
                 полей записи.
             d.  Проверить, что значение переменной Empty устанавливается при каждом вызове функции Check_CRC в зависимости 
                 от значений полей записи, как определено в секции 3b функциональных требований.

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

    4.1.2. Типы тестовых примеров

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

  • Допустимые данные

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

    record_type   test_value1;
    int       i;
    
    test_value1.A         = false;
    for (i=0;i<20;i++)
       test_value1.B[i]   = i;
    for (i=0;i<5;i++)
       test_value1.C[i]   = i+5;
    test_value1.D[0]     = i+8;
    test_value1.CRC     = 0;
    
    Set_CRC(test_value1);
    
    printf("%d\n", test_value1.CRC);

    Сценарием будет вызов функции Set_CRC, а ожидаемым выходным значением - корректное значение поля CRC, рассчитанное по алгоритму CRC32.

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

  • Граничные данные

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

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

    record_type   test_value2;
    record_type   test_value3;
    int         i;
    
    test_value2.A       = false;
    for (i=0;i<20;i++)
       test_value2.B[i]    = 0;
    for (i=0;i<5;i++)
       test_value2.C[i]    = 0;
    test_value2.D[0]    = 0;
    test_value2.CRC  = 0;
    
    Set_CRC(test_value2);
    
    printf("%d\n", test_value2.CRC);
    
    test_value3.A       = true;
    for (i=0;i<20;i++)
       test_value3.B[i]   = pow(2,sizeof(test_value3.B[i])*8)-1;
    for (i=0;i<5;i++)
       test_value3.C[i]    = pow(2,sizeof(test_value3.C[i])*8)-1;
    test_value3.D[0]    = pow(2,sizeof(test_value3.D[0])*8)-1;
    test_value3.CRC  = pow(2,sizeof(test_value3.CRC)*8)-1;
    
    Set_CRC(test_value3);
    
    printf("%d\n", test_value3.CRC);
  • Отсутствие данных

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

    record_type test_value4;
    
    Set_CRC(test_value4);
    
    printf("%d\n", test_value4.CRC);
  • Повторный ввод данных

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

    record_type   test_value5;
    int       i;
    
    test_value5.A       = false;
    for (i=0;i<20;i++)
       test_value5.B[i]    = i;
    for (i=0;i<5;i++)
       test_value5.C[i]    = i+5;
    test_value5.D[0]    = i+8;
    test_value5.CRC  = 0;
    
    Set_CRC(test_value5);
    printf("%d\n", test_value5.CRC);
    
    Set_CRC(test_value5);
    printf("%d\n", test_value5.CRC);
  • Неверные данные

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

    struct record_type2
    {
      int F;
      int G[45];
      int H[8];
      unsigned int CRC;
      int K[2];
    }
    record_type2   test_value6;
    
    Set_CRC((record_type)test_value6);
    
    printf("%d\n", test_value6.CRC);
  • Реинициализация системы

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

    Примером реинициализации модуля вычисления CRC может служить принудительное обнуление переменной empty.

    record_type   test_value7;
    int       i;
    
    test_value7.A       = false;
    for (i=0;i<20;i++)
       test_value7.B[i]    = i;
    for (i=0;i<5;i++)
       test_value7.C[i]    = i+5;
    test_value7.D[0]    = i+8;
    test_value7.CRC  = 0;
    
    Set_CRC(test_value7);
    
    printf("%d\n", test_value1.CRC);
    
    empty=true;
    
    Set_CRC(test_value7);
    
    printf("%d\n", test_value1.CRC);
  • Устойчивость системы

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

    record_type   test_value8;
    int       i;
    
    test_value8.A       = false;
    for (i=0;i<20;i++)
       test_value8.B[i]    = i;
    for (i=0;i<5;i++)
       test_value8.C[i]    = i+5;
    test_value8.D[0]    = i+8;
    test_value8.CRC  = 0;
    
    for (i=0;i<10000;i++)
       Set_CRC(test_value8);
    
    printf("%d\n", test_value1.CRC);

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

  • Нештатные состояния среды выполнения

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

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

    record_type   test_value9;
    int       i;
    int       *heap;
    
    heap = malloc(_MAXMEM);
    
    test_value9.A       = false;
    for (i=0;i<20;i++)
       test_value9.B[i]    = i;
    for (i=0;i<5;i++)
       test_value9.C[i]    = i+5;
    test_value9.D[0]    = i+8;
    test_value9.CRC  = 0;
    
    Set_CRC(test_value9);
    
    free(heap);
    
    printf("%d\n", test_value9.CRC);
  • 4.1.2.1. Граничные условия

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

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

  • Значение внутри диапазона
  • Минимальное значение
  • Минимальное значение + 1
  • Максимальное значение
  • Максимальное значение - 1
  • Такой способ проверки называется проверкой на граничных значениях. Такая проверка позволяет выявлять проблемы, связанные с выходом за границы диапазона. Например, если в функцию

    char sum(char a, char b)
    {
       return a+b;
    }

    вычисляющую сумму чисел a и b, будут переданы значения 255 и 255, то в случае отсутствия специальной обработки ситуации переполнения сумма будет вычислена неверно.

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

    void abs_array(char array[], char size)
    {
       for (int i=1;i<=size;i++)
       {
             array[i] = abs(array[i]);
       }
       return;
    }

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

    4.1.3. Проверка робастности (выхода за границы диапазона)

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

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

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

    Таким образом, если изобразить допустимый интервал как на Рис 4.1, то можно видеть, что для тестирования интервальных значений достаточно 7 тестовых примеров - пяти допустимых и двух на робастность.

    (рис 4.1) Рекомендуемые проверочные значения

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

    4.1.4. Классы эквивалентности

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

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

    Рассмотренные выше граничные условия могут служить примером классов эквивалентности:

  • Значение из середины интервала.
  • Граничные значения.
  • Недопустимые значения за границами интервала.
  • Таким образом, тестирование граничных условий и робастности является частным случаем тестирования с использованием классов эквивалентности - вместо того, чтобы тестировать все недопустимые значения, выбираются только соседние с граничными.

    При определении классов эквивалентности следует руководствоваться следующими правилами:

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

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

    Можно выделить следующие классы эквивалентности:

    По длине имени:

  • Длина имени меньше 11 символов
  • Длина имени равна 11 символам
  • Длина имени больше 11 символов
  • По символам:

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

    4.1.5. Тестирование операций сравнения чисел

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

    В таблице 4.1 приведены тестовые наборы для блоков, реализующих операции сравнения, в случае, когда на один из входов блока подается константа.

    Блоки сравнения и определенные для них тестовые наборы
    greaterThan блок. Реализует операцию сравнения greaterEq блок. Реализует операцию сравнения
    № набора 1 2 3* 4 5 № набора 1 2 3* 4 5
    Вход ab - d b + d b min max Вход ab - d b + d b min max
    ВыходF T F F T ВыходF T T F T
    lessThan блок. Реализует операцию сравнения lessEq блок. Реализует операцию сравнения
    № набора 1 2 3* 4 5 № набора 1 2 3* 4 5
    Вход ab - d b + d b min max Вход ab - d b + d b min max
    ВыходT F F T F ВыходT F T T F
    equalTo блок. Реализует операцию сравнения notEqualTo блок. Реализует операцию сравнения
    № набора 1 2 3 4 № набора 1 2 3 4
    Вход a$$\ne$$ b b min max Вход a$$\ne$$ b b min max
    ВыходF T F F ВыходT F T T

    * тестовый набор реализуем, только если переменная на входе a - переменная целого типа

    В приведенных тестовых наборах используются следующие обозначения:

  • d - шаг изменения ( resolution ) переменной на входе a. Если переменная на входе a - переменная целого типа, то d равно 1;
  • min - минимальное значение переменной на входе a ;
  • max - максимальное значение переменной на входе a.
  • В таблице 4.2 приведены тестовые наборы для блоков, реализующих операции сравнения, в случае, когда на оба входа блока подаются переменные.

    Блоки сравнения и определенные для них тестовые наборы (продолжение)
    greaterThan блок. Реализует операцию сравнения greaterEq блок. Реализует операцию сравнения
    № набора 1 2 3* 4 5 № набора 1 2 3* 4 5
    Вход aval val val min max Вход aval val val min max
    Вход bval + d2 val - d2 val max min Вход bval + d2 val - d2 val max min
    ВыходF T F F T ВыходF T T F T
    lessThan блок. Реализует операцию сравнения lessEq блок. Реализует операцию сравнения
    № набора 1 2 3* 4 5 № набора 1 2 3* 4 5
    Вход aval val val min max Вход aval val val min max
    Вход bval + d2 val - d2 val max min Вход bval + d2 val - d2 val max min
    ВыходT F F T F ВыходT F T T F
    equalTo блок. Реализует операцию сравнения notEqualTo блок. Реализует операцию сравнения
    № набора 1 2 3 4 № набора 1 2 3 4
    Вход aval1 val min max Вход aval1 val min max
    Вход bval2 val max min Вход bval2 val max min
    ВыходF T F F ВыходT F T T

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

    В приведенных тестовых наборах используются следующие обозначения:

  • d2 - шаг изменения ( resolution ) переменной на входе b. Если переменная на входе b - переменная целого типа, то d2 равно 1;
  • val, val1, val2 - значения, которые взяты из середины диапазона, полученного при пересечении диапазонов переменных на входах a и b ;
  • min - минимальное значение переменной на входе блока;
  • max - максимальное значение переменной на входе блока.
  • Вернуться к учебному плану