Testing FinTech Products Through Agile Methods

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

Данный продукт представляет собой финансовое ядро приложения, содержащее всю основную бизнес-логику, такую как управление финансовыми транзакциями. Ядро предоставляет свои сервисы через набор запросов, аналогичный системным вызовам в Unix. Есть и другие компоненты продукта, такие как графический и текстовый пользовательские интерфейсы, но они не рассматриваются в данном отчете. Все остальные компоненты используют сервисы ядра для выполнения своих задач. Система реализована на Java с использованием принципов EJB 3 и развертывается в помощью кастомного легковесного EJB контейнера, который также не является предметом данного исследования.

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

Изученное программное обеспечение классифицируется по следующим трем категориям:

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

Помимо этих трех категорий, система также содержит небольшие объемы другого ПО и конфигурационных файлов, таких как вспомогательное ПО для установки, вспомогательные средства для интеграционного тестирования и системные тесты для проверки всего программного комплекса.

С течением времени доля тестового кода, как модульных, так и интеграционных тестов, по отношению к продуктивному коду только увеличивалась. Если продуктивный код вырос с около 42 тыс. строк в 583 файлах в первом предварительном релизе до примерно 460 тыс. строк в 5166 файлах в последнем изученном релизе, то количество строк интеграционных тестов выросло до 1488 тыс. в 11211 файлах. Это связано с принципом тестирования на разных уровнях абстракции и количеством тестов, уменьшающимся при движении к более высокому уровню.

Что касается кода модульных тестов, то начиная с 11-го релиза его объем превышает объем продуктивного кода. В последней изученной версии примерно на 30% больше строк кода модульных тестов, чем продуктивного кода. Таким образом, база кода модульных тестов также растет быстрее, чем продуктивный код, хотя и не так быстро, как интеграционные тесты. Тот факт, что типичный файл модульного теста больше, чем типичный файл продуктивного кода, подтверждает предположение, что тесты в основном представляют собой конкретный код в типичном стиле Arrange-Act-Assert, в то время как продуктивный код состоит из большего числа интерфейсов и абстрактных классов.

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

Далее в статье обсуждается распространенность дефектов по версиям продукта. В начале жизненного цикла их доля была выше из-за меньшего количества продуктивного кода и отсутствия интеграционных тестов. Со временем интеграционные тесты помогли существенно ограничить количество дефектов по мере роста кодовой базы. Например, если в первых релизах доля дефектов на тысячу строк кода достигала 1-1,5, то в последних устоялась на уровне 0,1-0,2. Это наглядно демонстрирует положительный эффект от внедрения интеграционного тестирования.

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

В частности, крайне важно сохранять принципы Arrange-Act-Assert для каждого тестового случая. Кроме того, каждый тест после рефакторинга должен по-прежнему проверять все, что проверял до этого, чтобы не стать более терпимым, чем оригинальный. Фаза Arrange, однако, должна быть настолько лаконичной, насколько это возможно, задавая только минимальное состояние, необходимое для успешного прохождения теста. Различные начальные состояния обычно называют fixtures, и чтобы избежать чрезмерного повторения, важно отслеживать, какие fixtures уже доступны при написании новых тестовых случаев.

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

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

Цена: р.

Заказать