Тестирование софта - статьи

       

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


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

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

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

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

Таким образом, структуру тестовой программы можно описать с помощью формулы test = {⟨initi, actioni, posti⟩}i=1,n, где initi — это инициализирующие инструкции, actioni — тестовое воздействие, posti — тестовый оракул.

Ниже приведен фрагмент тестовой программы для микропроцессора с системой команд MIPS64 , который включает один тестовый вариант. /////////////// Инициализирующие инструкции /////////////// // Инициализация регистров инструкции 1: // загрузка виртуального адреса в базовый регистр a0 // a0[base]=0xffffffff81af1590 ori a0, zero, 0xffff dsll a0, a0, 16 ori a0, a0, 0xffff dsll a0, a0, 16 ori a0, a0, 0xa1af dsll a0, a0, 16 ori a0, a0, 0x1590 // Инициализация памяти для инструкции 1 ori a1, zero, 0xdead sw a1, 0(a0) // Инициализация регистров инструкции 2: // загрузка виртуального адреса в базовый регистр t1 // t1[base]=0xffffffff81c49598 ... // Инициализация памяти для инструкции 2 ... // Инициализация кэша L1 для инструкции 1: // помещение данных в кэш L1 lw t0, 0(a0) // Инициализация кэша L1 для инструкции 2: // вытеснение данных из кэша L1 ... ////////////////// Тестовое воздействие /////////////////// // Зависимость между инструкциями 1 и 2: L1IndexEqual=false lw v0, 0(a0) // L1Hit=true sw a2, 0(t1) // L1Hit=false ///////////////////// Тестовый оракул ///////////////////// // Тестовый оракул для инструкции 1 ori t1, zero, 0xdead // Ошибочное завершение при несоответствии результата bne v0, t1, error_found nop // Тестовый оракул для инструкции 2 ...

Тестовое воздействие состоит из двух инструкций: lw, осуществляющей загрузку слова, и sw, которая сохраняет слово по указанному адресу. Для наглядности тестовые ситуации для этих инструкций затрагивают только кэш-память первого уровня (L1): первая инструкция вызывает попадание в кэш, вторая — промах.

Несколько слов о вспомогательных инструкциях, используемых в примере. Инструкция ori осуществляет побитовое ИЛИ значения второго регистра с 16 битным значением, заданного в третьем операнде, результат записывается в первый регистр; инструкция dsll сдвигает значение второго регистра влево на заданное число разрядов и сохраняет результат в первом регистре; bne — это инструкция условного перехода (переход осуществляется в случае неравенства значений регистров).

Содержание раздела