Все ругают самописные тестовые фреймворки. А мы своим довольны



    Меня зовут Елена Расторгуева, я отвечаю за продукт «Фактор» в HFLabs. «Фактор» — чертовски сложный алгоритмический enterprise, он обрабатывает данные в промышленных масштабах.

    В статье я расскажу, как мы начинали тестировать «Фактор», как развивали автотесты и почему пришли к самописным фреймворкам.

    Что за продукт такой — «Фактор»


    «Фактор» чистит данные в базах с миллионами клиентов: убирает опечатки в ФИО, телефонах и емейлах, проверяет паспорта, делает еще кучу всего. Самое сложное — исправлять почтовые адреса.


    Адреса́ пишут сотнями способов, поэтому под капотом у «Фактора» неслабый алгоритмический аппарат

    «Фактор» работает как сервис: данные на вход — данные на выход.

    Это stateless-система, где каждое обращение не зависит от предыдущих. Stateless сильно упрощает жизнь тестировщика. Намного сложнее тестировать stateful системы, когда важна последовательность действий.

    Продукт должен быть надежным как МКС, потому что им пользуются банки, сотовые операторы, страховые, ритейлеры уровня «Ленты». За ошибки мы отвечаем головой вплоть до того, что отсутствие ошибок — часть SLA в договоре с заказчиком.

    Из-за требований к надежности автотесты мы писали с самого начала разработки. Один из критериев готовности задачи — «Добавлены автотесты».

    Начинали с ручной проверки и автотестов


    Мы зарелизили «Фактор» в 2005 году и сначала тестировали его руками. Утром тестировщик прогонял автотесты на файле с кейсами и сравнивал результат обработки данных с результатом предыдущего дня: что изменилось после вчерашнего коммита кода.

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

    Юнит-тесты такие удобные и работают так быстро, что мы добавляли их тысячами. А потом уперлись: когда тесты выглядят как простыня из тысяч кусочков кода, даже долистать до нужного места непросто. Не говоря о добавлении или обновлении.


    Юнит-тест на проверку формата СНИЛС

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

    Создали свой фреймворк


    В традиционных юнит-тестах данные и код идут вперемешку, выискивать нужные участки тяжело.

    Поэтому мы попробовали автотесты в парадигме Data Driven Testing (DDT). DDT — это когда данные для тестирования хранятся отдельно от кода для тестирования.

    Кейсы загружали из excel-файла, они лежали в колонках «Неочищенные данные» и «Ожидаемый результат». DDT стал прорывом: апдейтить кейсы в «эксельнике» невыразимо проще.

    Понемногу мы развили подход и разработали собственный фреймворк для тестирования. На вход он принимает текстовые файлы, внутри них — исходные данные и ожидаемый результат.


    От excel-файлов как хранилища мы отказались: текстовые быстрее открываются, не меняют содержимое, из них проще забрать данные

    Фреймворку помогают стандартные инструменты:

    • TeamCity автоматически запускает тесты каждую ночь;
    • testNG сравнивает ожидаемый и фактический результаты.


    Если результат отличается от ожидаемого, в TeamCity тест краснеет. Если всё как надо, тест зеленый

    Доработали фреймворк под себя


    С тех пор прошло 12 лет. За это время фреймворк оброс возможностями, которых нет в стандартных решениях.

    Учет статуса задач в Jira. HFLabs придерживается Test Driven Development: сначала пишем тест или добавляем тест-кейсы на новое поведение, а только потом меняем функциональность.

    Новые кейсы мы отключали комментированием строки. Иначе они первое время падали и мешали, поскольку кейсы добавляли раньше фичи или исправления бага.

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

    Поэтому мы добавили к отключенным кейсам номер таска и навернули немного автоматики. Теперь все работает так:

    • тест-кейс отключают, сопоставив ему открытую задачу в Jira;


      Чтобы привязать кейс к задаче, пишем перед ним # и номер таска
    • фреймворк прогоняет тесты даже по отключенным кейсам. Но игнорирует падения, пока задача открыта в Jira;
    • как только задачу закрывают, тест начинает падать на привязанных к ней кейсах. Это сигнал: задачу сдали, а кейсы включить забыли;
    • если вдруг тест по отключенному кейсу начал проходить при открытой задаче, фреймворк об этом тоже сообщит. Возможно, пора включить кейс или закрыть привязанный к нему таск (плюс обновить release notes и сообщить заказчикам).


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

    Так мы сохранили TDD и победили забывчивость при управлении тест-кейсами.


    Все варианты со статусами тест-кейсов и связанных задач мы задокументировали, чтобы не забыть

    Актуализация тест-кейсов в полуавтоматическом режиме. Казалось бы, если тест падает, ищи ошибку в коде. Но для нас это не всегда так. Бывает, актуализировать нужно именно тест-кейсы, потому что изменились требования к результату.

    Например, раньше заказчик в очищенном адресе хотел «г. Москва» одним полем. Теперь он изменил архитектуру БД, хочет «город» одним полем, «Москва» — другим. Пора менять тест-кейсы.

    Для упавшего теста TeamCity показывает разницу между ожидаемым и актуальным результатами. Раньше мы копировали эту разницу и руками обновляли тест-кейсы. Для массовых изменений — очень затратное мероприятие.


    Живой пример: мы научили «Фактор» определять страну по номеру телефона, тесты в TeamCity упали. Новый эталон можно взять из фактического результата, но это долго

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


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

    С новым эталоном тестировщик актуализирует кейсы в три шага.

    1. Скачивает сгенерированный файл.
    2. Проверяет через любой инструмент мерджинга, какие изменения попали в новый эталон. Оставляет только нужные.
    3. Коммитит.


    Тестировщик проверяет, насколько обновления в новом эталоне корректны, и коммитит их

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

    Стабилизация тестовых данных заглушками. «Фактор» возвращает обработанные данные в десятках полей. В одном только адресе куча составляющих: индекс, регион, тип региона, тип города, город, тип улицы, дом, строение, корпус, квартира. К ним «Фактор» цепляет ИФНС, ОКАТО, ОКТМО и еще по мелочи. Так из одной строки на входе получаются десятки значений.

    Не все поля из результата нужно проверять тест-кейсами. Например, распознавание того же адреса прямо зависит от государственного справочника — ФИАС. А в нем регулярно меняются поля, для наших задач совсем посторонние. Обновление каких-нибудь КЛАДР-кодов для домов роняло сотни тест-кейсов.

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

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


    ФИАС ID в адресе есть всегда, поэтому мы проверяем его на всех тестах. Если поле не заполнено, что-то не так. А вот индекса может и не быть, поэтому при проверке ФИАС ID индекс мы игнорируем

    Можно было пойти по другому пути и разделить тесты: на каждое поле свой. Но это сложно, потому что не всё можно изолировать. Например, «город» и «улица» являются частями адреса и друг без друга не имеют смысла.

    Самописный фреймворк удобнее


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

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

    Если нравится делать сложные штуки в энтерпрайзе, приходите к нам. Сейчас ищем java-разработчика, зарплата от 135 000 ₽ без вычета НДФЛ.
    HFLabs
    99,00
    Качество и интеграция клиентских данных
    Поделиться публикацией

    Комментарии 15

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

      Ваш опыт интересен, но вот вывод который вы делаете, скажем так, очень оптимистичен.
      Если «свой велосипед» делает достаточно компетентная команда и немного везения, то получается история успеха. Но вот если нет, то получается долгостройный «геморой» для всех.
      И случаев когда «геморой» значительно больше.
      Опять же, не стоит забывать о таком моменте… тестировщик с опытом самописного фрейворка на рынке не очень востребован.
        0
        Спасибо за первый коммент! Это правда вечный спор :)

        У нас лучшие разработчики, и именно в этом моё везение.

        Я думаю, что соль тестировщика в мышлении: понимать, как продукт используется, проверять все важные случаи и находить интересное. А какие инструменты использовал — вторично. Как математика инструмент для физики. Простите :)

        Ценно, когда человек легко осваивает новые инструменты.
          +1
          Свой фреймворк конечно хорошо, если он не едет дальше по другим отделам вдохновленных вашим успехом. Вот тут начинается головная боль и у тех кто использует, и у тех кто поддерживает
            +1
            >У нас лучшие разработчики
            Нет, у нас.
            +2

            Когда-то фреймворки, ныне существующие на рынке — тоже были чьим-то самописным фреймворком :)
            Если автор оказался хорош, то это используется многими.
            Если плох, то это становится "кривой велосипед"

              0
              Все правильно. Насколько велосипед удачен можно сказать только после того как его опробывали не только создатели
                0
                Это интересно.
                У нас много специфики, но общую часть можно выделить для тех, кто регулярно актуализирует тесты в текстовом файле и хочет добавлять тесты с ссылкой на задачу.
                Есть коллеги, кто не решил ещё эти задачи в своих тестах?
                  0
                  На самом деле даже если и решили все, и вдруг появится/заопенсорсится новый инструмент/способ — всегда найдутся те, кто попробует хотя бы из любопытства… И если этот инструмент решит задачу или сделает решение проще… Молва пойдет :) testng тоже появился после junit, и ничего, нашел нишу/отобрал свой кусок. И потом подготовка проекта к выкладыванию на всеобщее обозрение это всегда стерсс и интересно. Потому что понимание проекта и его применимости сразу на несколько уровней улучшается :)
            +1

            Спасибо за интересную статью, ваш опыт очень радует и снова подтверждает, что построение удобного CI возможно и улучшает качество продукта

              0

              Так вроде же у вас не тестовый фреймворк с нуля, а очень умная параметризация поверх TestNG.


              И это хорошо: берём готовое и допиливаем под себя

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

                Что мешает список кейсов положить в версионное хранилище вместе с кодом и сливать изменения в списке в общий список кейсов тоже вместе с исправленным кодом? Тогда у вас подобных проблем не будет.
                  0
                  Такой опыт тоже был. Но процесс с одним файлом оказался проще, телодвижений меньше, чеклисты короче.
                    +1
                    По моему, вы про какой-то другой вариант говорите. Я про файл со списком — почему он не там же, где код?
                  0
                  Эх, мне бы такой API, как у ТС, я б автоматизировал.
                    0
                    Ну не так чтобы уж прямо совсем уж свой фреймворк получился. Это больше вдумчивая модернизация имеющегося для ваших нужд. Вам повезло, что есть люди которые в это умеют, и знают как сделать так, чтобы было хорошо. И еще у них было/есть на это время. Редко когда (никогда) на тестирование выделяют достаточно ресурсов.

                    Так-то unittest движки очень не удобны для функциональных тестов. Несмотря на то, что они уже давно умеют в DDT из коробки, и умеют брать тестовые данные из разных сорсов, вплоть до БД. Но все же, неудобно феноменально. Именно поэтому появляются Cucuber и Robot Framewok.

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Самое читаемое