Тестовый двойник (mock, stub, fake)
Модуль, который управляет нагревателем, вызывает функции АЦП и ШИМ. Чтобы проверить его логику на компьютере, эти функции подменяют: вместо настоящего драйвера тест подставляет двойника, который возвращает нужные числа и запоминает, что с ним делали.
Обновлено Редакция GAW
Содержание
Тестовый двойник (test double) — подставной объект, который в модульном тесте заменяет настоящую зависимость проверяемого модуля: драйвер, функцию чтения времени, регистры периферии. Модуль не знает о подмене и вызывает те же функции, а тест управляет их ответами и проверяет, как модуль с ними обращался.
Разновидности
| Вид | Что делает |
|---|---|
| Заглушка (stub) | возвращает заранее заданные значения, ничего не проверяет |
| Мок (mock) | проверяет вызовы: сколько раз, с какими аргументами и, при необходимости, в каком порядке |
| Имитация (fake) | упрощённая рабочая реализация, например флеш-память в массиве ОЗУ |
| Шпион (spy) | заглушка, которая дополнительно записывает вызовы, чтобы тест проверил их после действия (определение Мессароша) |
Граница между видами условная, общее название для всех — двойник.
Как это делают в C
В C нет классов и виртуальных методов, поэтому подмена происходит при компоновке: в тестовую сборку вместо файла драйвера подключают файл с теми же функциями. Для больших интерфейсов их генерирует CMock — по описанию авторов, он берёт заголовочный файл и для каждого прототипа создаёт функцию, которую можно скомпоновать вместо настоящей. Тест задаёт ожидания перед вызовом:
void test_heater_turns_on_when_cold(void)
{
adc_read_ExpectAndReturn(ADC_CH_TEMP, 1200); /* mock: expects this call */
pwm_set_Expect(PWM_HEATER, 100);
heater_step();
}
Если модуль вызовет adc_read() с другим каналом или не вызовет pwm_set(), тест провалится. Вариант _IgnoreAndReturn превращает мок в заглушку, _AddCallback вызывает вашу функцию после проверки аргументов, а _Stub подставляет её вместо всех проверок. Старое имя _StubWithCallback объявлено устаревшим. Ожидания для одной функции CMock проверяет в заданном порядке; чтобы проверить и порядок между adc_read() и pwm_set(), включают опцию :enforce_strict_ordering.
Регистры
Двойником может быть и периферия. Если драйвер обращается к регистрам через указатель на структуру, в тестовой сборке указатель направляют на обычную переменную в памяти. Тест записывает в неё флаги состояния, вызывает функцию и проверяет, какие биты она установила. Учитывать стоит и типы доступа к регистрам: бит, который в железе сбрасывается записью единицы, двойник должен вести себя так же, иначе тест проверит не то. Связка Unity и CMock на примере реального модуля разобрана в статье о модульном тестировании прошивки.
Частые вопросы
Чем мок отличается от заглушки?
Заглушка (stub) только отвечает: возвращает заданное значение и ничего не проверяет. Мок (mock) ещё и проверяет взаимодействие — что функция вызвана нужное число раз и с нужными аргументами, и проваливает тест, если нет. В CMock это видно по именам: _IgnoreAndReturn работает как заглушка, _ExpectAndReturn — как мок. Порядок вызовов одной функции CMock проверяет всегда, а порядок между разными функциями — только с опцией :enforce_strict_ordering.
Как подменить функцию драйвера в тесте на C?
Через компоновку. Модуль вызывает драйвер через функции из заголовка, а в тестовую сборку вместо файла драйвера подключают файл двойника с теми же функциями. CMock генерирует такой файл автоматически из заголовка; вручную его пишут как небольшой набор функций с глобальными переменными для ответов и счётчиков вызовов.
Можно ли подменить регистры периферии?
Да, если обращение к ним проходит через указатель или макрос, который в тестовой сборке переопределён на обычную переменную в памяти. Тогда тест может записать в «регистр» значение, вызвать функцию и проверить, какие биты она выставила. Именно поэтому драйвер удобно писать с доступом к периферии через одну структуру или функцию.
Источники
- ThrowTheSwitch. CMock: A Summary (docs/CMock_Summary.md), лицензия CC BY-SA 3.0 — CMock берёт заголовочные файлы и создаёт для них мок-интерфейс, чтобы проще тестировать модули, которые обращаются к другим модулям; для каждого прототипа, например int DoesSomething(int a, int b), генерируется функция, которую компонуют вместо настоящей; она проверяет полученные аргументы и возвращает заданное значение; управляющие функции DoesSomething_ExpectAndReturn(), DoesSomething_IgnoreAndReturn(); func_AddCallback() проверяет аргументы и порядок перед вызовом функции пользователя, func_Stub() пропускает проверки; func_StubWithCallback — устаревший псевдоним; для void func(void) — func_Expect(); порядок вызовов одной функции проверяется всегда, порядок между разными функциями — при :enforce_strict_ordering (по умолчанию выключено)
- ThrowTheSwitch. Unity Test Framework v2.7.0: docs/UnityGettingStartedGuide.md — тест собирается из Unity, тестового файла и проверяемого модуля; тесты удобнее запускать как приложение для компьютера, на железе нет полного контроля над регистрами
- G. Meszaros. xUnit Test Patterns: Test Spy (xunitpatterns.com) — Test Spy — тестовый двойник, который служит точкой наблюдения: записывает вызовы, сделанные проверяемой системой, чтобы тест затем проверил записанные значения; если косвенные выходы проверять не нужно, достаточно Test Stub
Нашли ошибку?
Смотрите также
- Модульное тестирование прошивкиТесты на ПК, Unity, подмена регистров и времени, санитайзеры, тесты на плате
- Модульный тестАвтоматическая проверка одной функции или модуля в изоляции от остального кода
- Утверждение (assert)Проверка внутреннего условия программы во время работы или при сборке
- Типы доступа к регистрамЧто делают чтение и запись поля регистра: RO, R/W, W1C, W1TS
- volatileКвалификатор C для регистров и переменных прерываний