Юнит-тесты и интеграционные

топ 100 блогов levgem03.10.2022

Пропустив через себя API-first подход с полноценным повсеместным внедрением OpenAPI в компании, стало сильно яснее, что у нас происходит с тестами.

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

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

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

Интеграционные тесты


Грубо говоря задача интеграционных тестов не допустить:

Юнит-тесты и интеграционные

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

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

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

Итого, интеграционные тесты:

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

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

Юнит-тесты

Есть довольно простой способ начать писать тесты на модули: берем и проверяем, что функции из какого-то шмотка кода ведут себя так, как мы задумали. Такие тесты пишутся легко, непринужденно и так же бессмысленно. Доходит до проверок, что 2+2 всё ещё равняется 4. Т.е. вокруг модуля создается обвязка, которая проверяет, что он ведет себя согласно ожиданиям.

В чём подвох юнит-тестов? В том, что их быстро и часто удаляют. Обычный интеграционный тест — это контракт, который может жить очень и очень долго. У нас есть те, что живут по 10 лет, потому что проверяют то, что не поменялось за эти 10 лет (какой-нибудь rtmp). А вот юнит тест проверяет только функции модуля. Захотели сделать рефакторинг — тесты в помойку. А ведь за каждым тестом кроется какая-то проверка и какие-то оцифрованные знания, баг репорт или чуйка предыдущего автора.

Получится ли сохранить ньюансы юнит-тестов при рефакторинге кода? Практика показывает, что редко.

Но юнит-тесты могут быть в 1000 раз быстрее, и в примере выше будет прогон не 1000 тестов, а лишь 30: по 10 на каждый модуль из 3-х штук.

Итого, такие тесты часто удаляются, с ними теряются знания и опыт, которые осели в виде кода. Дорого и обидно. Да и зачастую бессмысленно, погуглите «two unit tests, zero integration». Без интеграционных тестов совсем грустно.

Изменение подхода

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

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

Например, в эрланге есть апи вокруг TCP-сокетов: https://www.erlang.org/doc/man/gen_tcp.html   Послать данные, прочитать, принять подключение.

По аналогии с ним в OTP team сделали такое же апи для SSL. Очень удобно и уместно: https://www.erlang.org/doc/man/ssl.html

Соответственно мы делаем такое же апи для RTSP и RTMP протоколов и получаются такие же функции listen, accept, connect, send, recv, но только работающие не с потоком байт, а с потоком сообщений, имеющих четкую и внятно описанную структуру.

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

  1. нарушение слева
  2. нарушение справа
  3. неправильно сформулированный контракт

Получаются может не совсем юнит-тесты, но всё же это сильно ниже, чем общие интеграционные.

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

Мы сейчас встаем на дорогу переделывание тестов на такой манер. Это очень долго и сложно, но нужно.

Что дальше?

Планируем инструмент для описания и валидации таких внутренних контрактов в реальном времени и в тестах.


Оставить комментарий

Архив записей в блогах:
Съела пару кусочков, и только тогда увидела, что они с надписями
В соцсетях популярны задачки, в которых показаны не простые ситуации на дорогах и нужно вспомнить свои знания ПДД, чтобы понять кто, куда и когда едет. Вот вам перекресток равнозначных дорог. Казалось бы, чего проще. Однако я уверен, что половина из вас ответит не правильно. Выберите ...
...
Глаза  не зеркало души, Они бездонный омут. Иной раз видишь - там, внутри Лишь абсолютный холод. В них отражение твое - Досадная помеха. Тебя не помнят. Ты никто. Ты даже не причина смеха. ...
28 августа 1941 года для всех Немцев, проживающих в России памятный день, т.к. в этот день мы вспоминаем трагические события которые затронули весь немецкий народ Советского союза. 70 лет назад одним росчерком пера были перечёркнуты все заслуги ...