Блоки в покрытии кода с VS2010

Я запускаю код C++, чтобы получить результаты покрытия кода, как в этот пост.

#include <iostream>
using namespace std;

int testfunction(int input)
{
    if (input > 0) {
        return 1;
    }
    else {
        return 0;
    }
}

int main()
{
    testfunction(-1);
    testfunction(1);
}

введите здесь описание изображения

Результат покрытия кода говорит о трех блоках в main() и четырех блоках в testfunction(). Что означает блок? Как там 3/4 блока в основной/тестовой функции?

ДОБАВЛЕН

Когда я изменил код следующим образом,

int main()
{
    testfunction(1);
    testfunction(1);
}

или следующим образом

int main()
{
    testfunction(-1);
    testfunction(-1);
}

У меня есть этот результат.

введите здесь описание изображения

И кажется, что testfunction() состоит из четырех блоков.

  1. запись функции
  2. если блок
  3. иначе заблокировать
  4. состояние

Я получил подсказки из этого сообщения.


person prosseek    schedule 10.02.2011    source источник


Ответы (2)


Технический термин для обозначения блока в покрытии кода — базовый блок. Чтобы скопировать данные непосредственно из статьи Википедии:

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

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

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

Для main я вижу сборку ниже (перемежается с первоисточником). Аналогично тому, что Питер делает здесь, я отметил, где начинаются основные блоки.

int main()
{
013B2D20  push        ebp                       <--- Block 0 (initial)
013B2D21  mov         ebp,esp  
013B2D23  sub         esp,40h  
013B2D26  push        ebx  
013B2D27  push        esi  
013B2D28  push        edi  
    testfunction(-1);
013B2D29  push        0FFFFFFFFh  
013B2D2B  call        testfunction (013B10CDh)  
013B2D30  add         esp,4                     <--- Block 1 (due to call)
    testfunction(1);
013B2D33  push        1  
013B2D35  call        testfunction (013B10CDh)  
013B2D3A  add         esp,4                     <--- Block 2 (due to call)
}
013B2D3D  xor         eax,eax  
013B2D3F  pop         edi  
013B2D40  pop         esi  
013B2D41  pop         ebx  
013B2D42  mov         esp,ebp  
013B2D44  pop         ebp  
013B2D45  ret  

Мы видим, что main имеет три основных блока: один начальный блок, а два других из-за вызовов функций. Глядя на код, это кажется разумным. testfunction немного жестче. Просто глядя на источник, кажется, что есть три блока:

  1. Вход в функциональный и логический тест (input > 0)
  2. Условие истинной ветви (return 1)
  3. Условие ложной ветви (return 0)

Однако из-за фактической сгенерированной сборки имеется четыре блока. Я предполагаю, что вы создали свой код с отключенными оптимизациями. Когда я строю с VS2010 в конфигурации Debug (оптимизации отключены), я вижу следующую дизассемблирование для testfunction:

int testfunction(int input)
{
013B2CF0  push        ebp                         <--- Block 0 (initial)
013B2CF1  mov         ebp,esp  
013B2CF3  sub         esp,40h  
013B2CF6  push        ebx  
013B2CF7  push        esi  
013B2CF8  push        edi  
    if (input > 0) {
013B2CF9  cmp         dword ptr [input],0  
013B2CFD  jle         testfunction+18h (013B2D08h)  
        return 1;
013B2CFF  mov         eax,1                        <--- Block 1 (due to jle branch)
013B2D04  jmp         testfunction+1Ah (013B2D0Ah)  
    }
    else {
013B2D06  jmp         testfunction+1Ah (013B2D0Ah) <--- Not a block (unreachable code)
        return 0;
013B2D08  xor         eax,eax                      <--- Block 2 (due to jmp branch @ 013B2D04)
    }
}
013B2D0A  pop         edi                          <--- Block 3 (due to being jump target from 013B2D04)
013B2D0B  pop         esi  
013B2D0C  pop         ebx  
013B2D0D  mov         esp,ebp  
013B2D0F  pop         ebp  
013B2D10  ret  

Здесь у нас есть четыре блока:

  1. Вход в функцию
  2. Условие истинной ветви
  3. Условие ложной ветви
  4. Общий эпилог функции (очистка стека и возврат)

Если бы компилятор продублировал эпилог функции как в ветвях условия true, так и в ветвях условия false, вы бы увидели только три блока. Также, что интересно, компилятор вставил ложную инструкцию jmp в 013B2D06. Поскольку это недостижимый код, он не рассматривается как базовый блок.

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

person Chris Schmich    schedule 11.02.2011
comment
Замечание о том, что компилятор дублирует эпилог и, таким образом, избавляется от базового блока, правильно, если вы настаиваете на инструментировании скомпилированного кода, но ужасно сбивает с толку разработчиков. На самом деле, пользователи ожидают охвата исходного кода, который они видят. Инструменты тестового покрытия моей компании (Semantic Designs) инструментируют исходный код, и, таким образом, количество блоков — это то, что видят программисты, даже если компилятор выполняет такие оптимизации. Плохая практика — применять большое количество преобразований (включая поднятие блоков эпилога) к программе, а затем отображать данные о покрытии измененной программы. - person Ira Baxter; 01.03.2011

Согласно MSDN в обзоре данных о покрытии кода:

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

Основной блок:

  • Запись метода
  • тестовая функция
  • тестовая функция

Тестовый функциональный блок:

  • Запись метода
  • Если еще
  • Возвращение
  • Вызов метода
person Filip Ekberg    schedule 10.02.2011
comment
Спасибо за ответ. Кстати, я не вижу, чтобы вызов возврата/метода содержал блок. Я добавил кое-что к своему исходному сообщению. - person prosseek; 10.02.2011