Spec-Zone.ru › Codeception

Параллельное выполнение

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

С чего начать

Codeception не предоставляет команду, подобную run-parallel. Нет единого решения, подходящего всем. Вот вопросы, на которые вам нужно ответить:

  • Как будут выполняться параллельные процессы?
  • Как параллельные процессы не будут влиять друг на друга?
  • Будут ли они использовать разные базы данных?
  • Будут ли они использовать разные хосты?
  • Как мне разделить мои тесты между параллельными процессами?

Существует два подхода к реализации паралеллизации. Мы можем использовать Docker и запускать каждый процесс внутри изолированных контейнеров, которые будут выполняться одновременно.

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

Docker

Пожалуйста, убедитесь, что у вас установлен docker. Также необходим опыт работы с Docker.

Использование образа Codeception Docker

Запустите официальный образ Codeception с DockerHub:

docker run codeception/codeception

Запуск тестов из проекта, путем монтирования текущего пути в качестве хост-тома в контейнер. По умолчанию рабочая директория в контейнере — /project.

docker run -v ${PWD}:/project codeception/codeception run

Для подготовки приложения и тестов к выполнению внутри контейнеров вам нужно будет использовать Docker Compose для запуска нескольких контейнеров и их соединения.

Определите все необходимые сервисы в docker-compose.yml файле. Убедитесь, что вы следуете философии Docker: 1 сервис = 1 контейнер. Таким образом, каждый процесс должен быть определен как отдельный сервис. Эти сервисы могут использовать официальные образы Docker, загруженные из DockerHub. Директории с кодом и тестами должны быть смонтированы с помощью volume директивы. А экспонированные порты должны быть явно установлены с помощью ports директивы.

Мы подготовили пример конфигурации с codeception, веб-сервером, базой данных и selenium с Chrome для совместного выполнения.

version: '3'
services:
  codecept:
    image: codeception/codeception
    depends_on:
      - chrome
      - web
    volumes:
      - .:/project
  web:
    image: php:7-apache
    depends_on:
      - db
    volumes:
      - .:/var/www/html
  db:
    image: percona:5.6
  chrome:
    image: selenium/standalone-chrome

Сервис Codeception выполнит команду codecept run, но только после запуска всех сервисов. Это определено с помощью параметра depends_on.

Добавить дополнительные пользовательские сервисы легко. Например, для использования Redis вам просто нужно добавить эти строки:

  redis:
    image: redis:3

По умолчанию образ имеет codecept в качестве точки входа. Для запуска тестов просто укажите команду run.

docker-compose run --rm codecept help

Запуск набора тестов

docker-compose run --rm codecept run acceptance
docker-compose run --rm codecept run acceptance LoginCest

Разработчик bash

docker-compose run --rm --entrypoint bash codecept

И, наконец, для параллельного выполнения тестирования, вы должны определить, как разбить ваши тесты и запустить параллельные процессы для docker-compose. Здесь мы разбиваем тесты по наборам, но вы можете использовать различные группы для разделения тестов. В следующем разделе вы узнаете, как это сделать с Robo.

docker-compose --project-name test-web run -d --rm codecept run --html report-web.html web & \
docker-compose --project-name test-unit run -d --rm codecept run --html report-unit.html unit & \
docker-compose --project-name test-functional run -d --rm codecept run --html report-functional.html functional

В заключение, стоит отметить, что настройка Docker может быть сложной, и убедитесь, что вы понимаете Docker и Docker Compose, прежде чем продолжать. Мы подготовили несколько ссылок, которые могут вам помочь:

  • Демо репозиторий для приёмочного тестирования
  • Внутренние тесты Codeception, реализованные в Docker
  • Приложение Phundament с Codeception

Если вы хотите автоматизировать разделение тестов по параллельным процессам и их выполнение с помощью скрипта PHP, вы должны использовать инструмент для создания задач Robo.

Robo

Что делать

Параллельное выполнение тестов состоит из 3 этапов:

  • разделение тестов
  • параллельный запуск тестов
  • объединение результатов

Мы предлагаем выполнить эти шаги с помощью инструмента создания задач. В этом руководстве мы будем использовать Robo инструмент создания задач. Это современный инструмент для создания задач на PHP, который очень прост в использовании. Он использует Symfony Process для запуска фоновых и параллельных процессов. Именно то, что нам нужно для этапа 2! А как насчет шагов 1 и 3? Мы создали задачи robo для разделения тестов на группы и объединения полученных отчётов JUnit XML.

В заключение, нам нужно:

  • Robo, инструмент для создания задач.
  • robo-paracept - задачи Codeception для параллельного выполнения.

Подготовка Robo и Robo-paracept

Выполните эту команду в пустой папке, чтобы установить Robo и Robo-paracept:

$ composer require codeception/robo-paracept --dev

Вам нужно установить Codeception после этого. Если Codeception уже установлен, это не сработает.

$ composer require codeception/codeception

Подготовка Robo

Инициализирует базовый RoboFile в корне вашего проекта

$ robo init

Откройте RoboFile.php для редактирования

<?php

class RoboFile extends \Robo\Tasks
{
    // define public methods as commands
}

Каждый публичный метод в robofile может быть выполнен как команда из консоли. Давайте определим команды для 3 шагов и включим автозагрузку.

<?php
require_once 'vendor/autoload.php';

class Robofile extends \Robo\Tasks
{
    use \Codeception\Task\MergeReports;
    use \Codeception\Task\SplitTestsByGroups;

    public function parallelSplitTests()
    {

    }

    public function parallelRun()
    {

    }

    public function parallelMergeResults()
    {

    }
}

Если вы запустите robo, вы сможете увидеть соответствующие команды:

$ robo
Robo version 0.6.0

Usage:
  command [options] [arguments]

Options:
  -h, --help            Display this help message
  -q, --quiet           Do not output any message
  -V, --version         Display this application version
      --ansi            Force ANSI output
      --no-ansi         Disable ANSI output
  -n, --no-interaction  Do not ask any interactive question
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

Available commands:
  help                    Displays help for a command
  list                    Lists commands
 parallel
  parallel:merge-results
  parallel:run
  parallel:split-tests

Шаг 1: Разделение тестов

Codeception может организовать тесты в группы. Начиная с версии 2.0, он может загружать информацию о группе из файлов. Пример текстового файла со списком имён файлов может рассматриваться как динамически настраиваемая группа. Посмотрите на пример файла группы:

tests/functional/LoginCept.php
tests/functional/AdminCest.php:createUser
tests/functional/AdminCest.php:deleteUser

Задачи из \Codeception\Task\SplitTestsByGroups сгенерируют непересекающиеся файлы групп. Вы можете разделить свои тесты по файлам или по отдельным тестам:

<?php
    function parallelSplitTests()
    {
        // Split your tests by files
        $this->taskSplitTestFilesByGroups(5)
            ->projectRoot('.')
            ->testsFrom('tests/acceptance')
            ->groupsTo('tests/_data/paracept_')
            ->run();

        /*
        // Split your tests by single tests (alternatively)
        $this->taskSplitTestsByGroups(5)
            ->projectRoot('.')
            ->testsFrom('tests/acceptance')
            ->groupsTo('tests/_data/paracept_')
            ->run();
        */
    }

Давайте подготовим файлы групп:

$ robo parallel:split-tests

 [Codeception\Task\SplitTestFilesByGroupsTask] Processing 33 files
 [Codeception\Task\SplitTestFilesByGroupsTask] Writing tests/_data/paracept_1
 [Codeception\Task\SplitTestFilesByGroupsTask] Writing tests/_data/paracept_2
 [Codeception\Task\SplitTestFilesByGroupsTask] Writing tests/_data/paracept_3
 [Codeception\Task\SplitTestFilesByGroupsTask] Writing tests/_data/paracept_4
 [Codeception\Task\SplitTestFilesByGroupsTask] Writing tests/_data/paracept_5

Теперь у нас есть файлы групп. Мы должны обновить codeception.yml для загрузки сгенерированных файлов групп. В нашем случае у нас есть группы: paracept_1, paracept_2, paracept_3, paracept_4, paracept_5.

groups:
    paracept_*: tests/_data/paracept_*

Давайте попробуем выполнить тесты из второй группы:

$ codecept run acceptance -g paracept_2

Шаг 2: Запуск тестов

Robo имеет ParallelExec задачу для запуска фоновых процессов.

Внутри контейнера

Если вы используете контейнеры Docker, вы можете запустить несколько контейнеров Codeception для разных групп:

public function parallelRun()
{
    $parallel = $this->taskParallelExec();
    for ($i = 1; $i <= 5; $i++) {
        $parallel->process(
            $this->taskExec('docker-compose run --rm codecept run')
                ->option('group', "paracept_$i") // run for groups paracept_*
                ->option('xml', "tests/_log/result_$i.xml") // provide xml report
        );
    }
    return $parallel->run();
}
Локально

Если вы хотите запустить тесты локально, просто используйте встроенную taskCodecept задачу Robo для определения команд Codeception и размещения их внутри parallelExec.

<?php
public function parallelRun()
{
    $parallel = $this->taskParallelExec();
    for ($i = 1; $i <= 5; $i++) {
        $parallel->process(
            $this->taskCodecept() // use built-in Codecept task
            ->suite('acceptance') // run acceptance tests
            ->group("paracept_$i") // for all paracept_* groups
            ->xml("tests/_log/result_$i.xml") // save XML results
        );
    }
    return $parallel->run();
}

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

Мы можем определить разные базы данных для разных процессов. Это можно сделать, используя среды. Давайте определим 5 новых сред в acceptance.suite.yml:

actor: AcceptanceTester
modules:
    enabled:
        - Db:
            dsn: 'mysql:dbname=testdb;host=127.0.0.1'
            user: 'root'
            dump: 'tests/_data/dump.sql'
            populate: true
            cleanup: true
        - WebDriver:
            url: 'http://localhost/'
env:
    env1:
        modules:
            config:
                Db:
                    dsn: 'mysql:dbname=testdb_1;host=127.0.0.1'
                WebDriver:
                    url: 'http://test1.localhost/'
    env2:
        modules:
            config:
                Db:
                    dsn: 'mysql:dbname=testdb_2;host=127.0.0.1'
                WebDriver:
                    url: 'http://test2.localhost/'
    env3:
        modules:
            config:
                Db:
                    dsn: 'mysql:dbname=testdb_3;host=127.0.0.1'
                WebDriver:
                    url: 'http://test3.localhost/'
    env4:
        modules:
            config:
                Db:
                    dsn: 'mysql:dbname=testdb_4;host=127.0.0.1'
                WebDriver:
                    url: 'http://test4.localhost/'
    env5:
        modules:
            config:
                Db:
                    dsn: 'mysql:dbname=testdb_5;host=127.0.0.1'
                WebDriver:
                    url: 'http://test5.localhost/'

После определения метода parallelRun вы можете запустить тесты с помощью

$ robo parallel:run

Шаг 3: Объединение результатов

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

<?php
    function parallelMergeResults()
    {
        $merge = $this->taskMergeXmlReports();
        for ($i=1; $i<=5; $i++) {
            $merge->from("tests/_output/result_paracept_$i.xml");
        }
        $merge->into("tests/_output/result_paracept.xml")->run();
    }

Теперь, мы можем выполнить:

$ robo parallel:merge-results

Файл result_paracept.xml будет сгенерирован. Он может быть обработан и проанализирован.

Всё вместе

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

<?php
    function parallelAll()
    {
        $this->parallelSplitTests();
        $result = $this->parallelRun();
        $this->parallelMergeResults();
        return $result;
    }

Заключение

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

  • Предыдущая глава: < Непрерывная интеграция

© 2011 Michael Bodnarchuk and contributors
Licensed under the MIT License.
https://codeception.com/docs/12-ParallelExecution

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API