Блог

Тест-кейсы как код: ручное тестирование, вооружённое Git и IDE

2025-11-05 08:45
Вокруг кода существует множество инструментов, позволяющих упростить и автоматизировать работу с ним. Оказалось, что если превратить какую-то вещь в код, то весь этот инструментарий можно использовать для ускорения работы. Так произошло, например, с конфигурацией установки, документацией, и многим другим.
Ручные тесты тоже можно хранить в коде. Это не значит, что нужно отказаться от TMS; просто многие TMS сейчас умеют синхронизировать тест-кейсы с кодом — например, ТестОпс или TestRail с плагином от JetBrains.
Про концепцию “тест-кейсы как код” есть много материалов в интернете; в частности, на Хабре — вот и вот; о ней говорят и на конференциях — тут и тут. Здесь мы обсудим, какие преимущества даёт этот подход сегодня, в 2025 году, и покажем, как он может выглядеть на практическом примере.

Общее пространство для тестов

Первое преимущество — хранение тестов и кода в одном пространстве. Какой бы TMS мы ни пользовались, это даёт массу преимуществ:
  • Становится гораздо легче рассчитать общее покрытие кода тестами — и ручными, и автоматизированными
  • Как следствие, проще планировать тестирование и создавать общие тест-планы
  • Единый источник правды: все тесты находятся в одном месте
  • Ручные тестировщики сближаются с автоматизаторами благодаря тому, что работают в общем пространстве.
Остановимся подробнее на последнем пункте.

Автоматизация становится общим делом

«Сожительство» ручных тест-кейсов и автотестов многократно упрощает автоматизацию. Как выглядит ручной тест в коде? Вот пример для pytest:
@allure.title("Демонстрационный ручной тест")
@allure.manual(True)
def test_demo_manual():
    with allure.step("Демонстрационный шаг"):
        pass
В сущности, это уже готовый «каркас» для автотеста; осталось только убрать декоратор @allure.manual(True), помечающий тест как ручной, и добавить код вместо pass.
Конечно, в реальности всё сложнее. Автотесты далеко не всегда есть смысл писать один к одному под ручные: многое из того, что вручную проверяется через интерфейс, в автотестах стоит разбивать на несколько проверок через API. И всё же наличие таких «каркасов» в коде с полностью прописанными метаданными в декораторах не может не упрощать автоматизацию.
Кроме того, при таком подходе гораздо проще проследить соответствие между ручным тестом и его автоматизированной версией — что повышает доверие к автоматизации у ручных тестировщиков.

Общие шаги и параметризация

Хранение тестов в коде позволяет ручным тестировщикам использовать всё, что умеет код: например, параметризацию (т.е. запускать один тест несколько раз с разными параметрами) или выделять общие шаги.
Правда, современные TMS позволяют это делать с ручными тестами «из коробки»: вот параметризация, а вот общие шаги в Тестопс. Но несмотря на наличие этой функциональности в TMS, в коде она всё равно более гибкая.
Например, если команда написала общие шаги в Python, она может экспортировать их отдельным пакетом, чтобы их могли использовать другие команды. В этом случае шаги не нужно будет дублировать, и сразу будет понятно, кто за них ответствен.

Использование возможностей IDE

Современные IDE дают множество возможностей для быстрой генерации и рефакторинга когда — в особенности, если они дополнены нейросетью.
Например, очень удобно пользоваться автоподстановкой. Если у нас есть объект steps с шагами в методах этого объекта, то, когда мы начинаем набирать «steps» в IDE, она предлагает нам выбрать из всех имеющихся шагов:
В IDE можно быстро сделать поиск и замену по всему проекту (например, если мы решили заменить кнопку «Отправить форму» на просто «Отправить»). Правда, поиск по тест-кейсам сейчас есть и в TMS, но в IDE этот инструментарий обычно богаче — например, можно использовать регулярные выражения.

Генерация данных

Есть и другие вещи, которые проще сделать в коде, чем в TMS: например, генерировать фейковые данные.
Библиотеки для генерации тестовых данных, такие как Java Faker для Java или Faker для Python, позволяют, в частности, использовать новые сочетания логин/пароль при каждом прогоне теста (и избежать парадокса пестицида).
Если ручные тесты хранятся в коде, все эти инструменты можно применять и к ним. Например, чтобы в Python добавить генерируемое имя в описание шага, можно сделать так:
fake = Faker()

def test_demo_manual():
    with allure.step(f"Шаг с генерируемым именем: {fake.name()}"):
        pass
В нашем ручном тесте такой шаг будет выглядеть так:

Использование VCS

Если тест-кейсы хранятся в коде, ими легко управлять через систему контроля версий (VCS), например, Git. Это даёт следующие преимущества:
  • Изменения в любой момент можно откатить до предыдущей версии.
  • При потере данных можно загрузить последнюю версию из удалённого репозитория.
  • Новые тесты добавляются только через пулл-реквесты, что даёт органичную возможность проводить ревью изменений.
  • Для всех тестов есть история изменений с указанием авторов.
  • Если нужно провести рефакторинг или внести какое-либо рискованное изменение, это можно сделать в отдельной ветке, не беспокоя других людей, работающих над проектом. Новая версия заливается в главную ветку только когда она уже отлажена.

Лёгкость миграции

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

Знакомство со стеком

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

Тесты как код с ТестОпс: практический пример

Итак, как может выглядеть инфраструктура подхода «тест-кейсы как код» сегодня? Давайте разберёмся.
Нам понадобится инстанс ТестОпс и репозиторий на гитхабе (в примере будет использован этот). Пример написан для pytest, но все приёмы работают и для других фреймворков.

Предварительная настройка

Скачиваем чистый проект с Allure Start и заливаем проект в наш репозиторий.
Настраиваем интеграцию ТестОпс с GitHub Actions. Для этого действуем по инструкции, доступной в документации ТестОпс — вплоть до пункта 2.3 Измените workflows. Указанный в инструкции workflow написан для Java и JUnit; поскольку мы работаем с pytest, в workflow придётся внести изменения:
name: Run tests

on:
  push:
  workflow_dispatch:
    inputs:
      ALLURE_JOB_RUN_ID:
        description: ALLURE_JOB_RUN_ID service parameter. Leave blank.
        required: false
      ALLURE_USERNAME:
        description: ALLURE_USERNAME service parameter. Leave blank.
        required: false

env:
  ALLURE_TOKEN: ${{ secrets.ALLURE_TOKEN }}
  ALLURE_JOB_RUN_ID: ${{ github.event.inputs.ALLURE_JOB_RUN_ID }}
  ALLURE_ENDPOINT: ${{ secrets.ALLURE_ENDPOINT }}
  ALLURE_PROJECT_ID: ${{ secrets.ALLURE_PROJECT_ID }}
  ALLURE_RESULTS: allure-results

jobs:
  all-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Set up Python 3.x
        uses: actions/setup-python@v2
        with:
          python-version: '3.x'

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt

      - name: install and configure allurectl
        uses: allure-framework/setup-allurectl@v1
        with:
          allure-endpoint: ${{ secrets.ALLURE_ENDPOINT }}
          allure-token: ${{ secrets.ALLURE_TOKEN }}
          allure-project-id: ${{ secrets.ALLURE_PROJECT_ID }}

      - name: Run tests
        run: |
          chmod +x run.sh
          allurectl watch -- pytest ./test --alluredir=${ALLURE_RESULTS} --capture=no
        env:
          TEST_BROWSER: ${{ github.event.inputs.TEST_BROWSER }}
          ALLURE_TESTPLAN_PATH: "./testplan.json"
          ALLURE_RESULTS: "allure-results"
          ALLURE_JOB_RUN_ID: ${{ github.event.inputs.ALLURE_JOB_RUN_ID }}
{$co}
Это необходимо сохранить файлом .yml в папке .github/workflows, с несколькими изменениями:
  • Для поля ALLURE_ENDPOINT нужно указать URL-адрес вашего инстанса ТестОпс
  • Для поля ALLURE_PROJECT_ID нужно указать ID вашего проекта в ТестОпс. Его можно найти, например, на главной странице ТестОпс:
Если всё настроено правильно, то, как только коммит с этим yml-файлом появится на GitHub, в GitHub Actions выполнится запуск тестов, и его результаты будут переданы в ТестОпс.

Создание и выполнение простого ручного теста

Напишем примитивный ручной тест для pytest:
import allure


@allure.title("Демонстрационный ручной тест")
@allure.manual(True)
def test_demo_manual():
    with allure.step("Демонстрационный шаг"):
        pass
Аннотация @allure.manual(True) — это всё, что необходимо системе, чтобы опознать тест как ручной. Ей уже не важно, какой дальше код — она тест выполнять не будет и оставит его ручным тестировщикам.
Закоммитим этот тест и отправим на GitHub. Это автоматически создаст новый запуск — но давайте на этот раз попробуем инициировать выполнение теста со стороны ТестОпс.
Откроем наш проект в ТестОпс и перейдём на вкладку Джобы. Если настройка интеграции с гитхабом была выполнена правильно, здесь должна быть джоба, связывающая проект с нашим репозиторием. Щелкнем кнопку запуска джобы:
Следующее окно предложит выбрать нужные тесты. Ручные тесты могут здесь не отображаться, это не страшно, они всё равно подгрузятся из репозитория. Нажмём Отправить.
Перейдём на вкладку Запуски. Здесь должна появиться плашка запуска с указанием количества выполняемых тестов; ручные тесты здесь будут учтены со статусом В процессе (поскольку мы их ещё не прошли):
Поначалу количество тестов на плашке может не совпадать с общим числом запущенных тестов, потому что результаты здесь отображаются в реальном времени по мере выполнения.
Щёлкнем по числу тестов на плашке запуска, и перейдём к результатам тестов. Здесь можно пройти наш тест вручную:
У нас заработала базовая инфраструктура. Теперь посмотрим, как связать с кодом более продвинутый инструментарий ТестОпс

Метаданные

Метаданные тест-кейсов лучше хранить прямо в коде, вместе с самими тест-кейсами. Вот как это может выглядеть:
@allure.title("Демонстрационный ручной тест")
@allure.story("Story")
@allure.feature("Feature")
@allure.tag("example")
@allure.label("owner", "mlankin")
@allure.manual(True)
def test_demo_manual():
    with allure.step("Демонстрационный шаг"):
        pass
    custom_step()
Все эти метаданные автоматически загружаются в тест-кейсы в ТестОпс и отображаются там:

Вложения

Тест-кейсы правильно распознают вложения в коде. Вот как это выглядит в коде:
@allure.title("Тест с вложением")
@allure.manual(True)
def test_demo_custom_steps():
    with allure.step("Шаг с вложением"):
        allure.attach("Текстовое вложение")
И вот что получается в Тестопс:

Общие шаги

Шаги, используемые в нескольких тестах, можно вынести в отдельный класс:
class Steps:


    @allure.step("Пользовательский шаг")
    def custom_step(self):
        pass
И вызывать методы этого класса в тестах:
@allure.title("Тест с пользовательскими шагами")
@allure.manual(True)
def test_demo_custom_steps():
    steps.custom_step()
    steps.second_step()
В ТестОпс получаем:

Выводы

Подытожим. У нас получился проект, в котором все тесты — и ручные, и автоматизированные:
  • хранятся в одном пространстве;
  • запускаются одинаково;
  • удобны в отладкеl
  • позволяют быстро получить представление о покрытии.
Правда, для этого нужно освоиться с инструментом, который обычно не используется в ручном тестировании. Но для многих это является ещё одним преимуществом подхода, поскольку позволяет глубже познакомиться со стеком технологий.