Куда пристроить модульные тесты в ABAP. Часть пятая. Тридцать восемь попугаев

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

Процент покрытия – цифра сомнительная, ровно настолько же сомнительная, как и “средняя температура по больнице”. Процент покрытия по проекту – это среднее покрытие его частей. То есть: Модуль-1 имеет покрытие 90%, Модуль-2 имеет покрытие 10%, в среднем покрытие будет равно 50%, если допустить что модули примерно равны по содержимому.

А что значит равны? И что такое 10%?

В ABAP UNIT есть три различные метрики покрытия:

  • по процедурам (procedure coverage)
  • по инструкциям (statement coverage)
  • по ветвям (branch coverage)

Например, есть класс, группа функций или пул подпрограмм:

form do_something.
c = a.
d = b.
endform.

form do_something_else.
if a > b.
c = a.
d = a.
else.
c = b.
d = b.
if d > 1000.
d = 1000.
endif.
endif.
endform.

form do_nothing.
if 1 = 2.
c = d = 0.
endif.
endform.

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

И предположим, что мы написали по одному простому тесту на каждую подпрограмму, функцию, метод. Для всех подпрограмм мы будем использовать значения [A = 7, B = 77].

class lcl_test definition for testing
duration short
risk level harmless.

private section.
methods: setup.
methods: do_something for testing.
methods: do_something_else for testing.
methods: do_nothing for testing.
endclass.

class lcl_test implementation.
method setup.
a = 7.
b = 77.
endmethod.

method do_something.
perform do_something.
endmethod.

method do_something_else.
perform do_something_else.
endmethod.

method do_nothing.
perform do_nothing.
endmethod.
endclass.

Procedure coverage

Это самый простой случай, можно посчитать на пальцах.

Покрытие по процедурам будет 100% = ( 1 + 1 + 1 ) / ( 1 + 1 + 1 ) * 100.

Statement coverage

А если для тех же функций мы посчитаем количество инструкций?

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

  • DO_SOMETHING: отработало три инструкции из трех
  • DO_SOMETHING_ELSE: отработало пять инструкций из восьми
  • DO_NOTHING: отработало две инструкции из трёх

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

Если мы посчитаем метрику по инструкциям, то будет уже 71% = ( 3 + 5 + 2 ) / ( 3 + 8 + 3 ) * 100.

Рассмотрим работу метрики на DO_SOMETHING_ELSE. Инструменты ABAP могут соответственно раскрасить строки исходного кода:

image

Наглядно, быстро, понятно. Просто удивительно, даже не ожидал такого от ABAP.

Из этой раскраски становится очевидно, что если бы мы взяли другие исходные параметры, то процент покрытия могу бы быть другим. В случае [A = 77, B = 7]:

image

При этом становится очевидным, что полного покрытия по данной метрике можно достичь только используя более одного теста. Например, при двух тестах [A = 77, B = 7] и [A = 7, B = 7777] всё позеленеет:

image

Таким образом метрика выходит на 100%. Можно немного успокоиться.

Branch coverage

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

Посмотрим на базе последнего примера:

Первая инструкция [IF A > B] на двух тестах отработала два раза: один раз по TRUE [A = 77, B = 7] и один раз по FALSE [A = 7, B = 7777].

А вот вторая инструкция [IF D > 1000] отработала только один раз на TRUE [A = 7, B = 7777].

Сам вызов функции считается за безусловную единицу, плюс первый IF даёт два из двух, второй IF даёт только единицу из двух. Значит наша метрика будет равна 80% = (1 + 2 + 1 ) / (1 + 2 + 2) * 100.

И тут уже выходит что для одной функции двух тестов уже мало, а нужно три. К предыдущим двум можно ещё добавить сценарий [A = 7, B = 77], чтобы второй IF отработал на FALSE.

После добавления третьего сценария метрика по этой функции вышла на 100%.

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

Сто процентов!

Понятно, что метрика Procedure coverage менее показательна в деталях. К ней можно внимательно присматриваться только на ранних этапах, если кода много а тестов ещё почти нет. А вот к какой метрике из двух оставшихся приглядываться после? Если первая метрика просто показывает насколько широко вы охватили функционал, то последние показывают, насколько вы его качественно охватили.

Как вы заметили, можно получить 100% по инструкциям, но при этом не будет 100% по ветвям. Но не наоборот (или я не могу придумать такой пример). Если вы уж получили 100% по ветвям, то значит вы зашли во все закоулки и все инструкции отработали. Но кому-то может показаться, что метрика по ветвлению даёт менее показательные весовые коэффициенты, так как игнорирует один из явных весовых показателей – количество строк кода.

Следует ли стремиться к 100%? Стремиться следует, не забывая о принципе Парето. Сделайте хотя бы 20% покрытие, которое будет делать 80% необходимой работы, а там будет видно.

PS. Хотя пустая функция выдаёт 100% по всем метрикам, но в рамках метрик по инструкциям и ветвям её вес становится несущественным.

PPS. Нельзя написать больше тестов и получить покрытие более 100%, а так хотелось.

PPPS. У Умпутуна есть магическое число, кажется где-то порядка 80%, которое он считает идеальным значением для процента покрытия. И это значение “из опыта” для его собственных проектов, где с покрытием кода всё отлично, по его мнению. Может быть и стремление к 100% он считает почти таким же предосудительным, как и отсутствие тестов вообще. Но это неточно.

Добавить комментарий

Ваш адрес email не будет опубликован.