PHPUnit: Электронная таблица (spreadsheet) в качестве источника данных (data provider)

в 17:40, , рубрики: php, phpunit, spreadsheet, извращения, метки:

В документации PHPUnit есть небольшой раздел посвященный источникам данных (data provider), которые позволяют скормить тесту большой объем данных, а чуть ниже есть даже пример источника данных для CSV файла.

Конечно же, использовать полноценную электронную таблицу (spreadsheet)!

Договоримся что:

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

И сразу перейдем к делу (минимум текста, максимум кода), весь код также доступен по ссылке внизу заметки:

Шаг #1: Зависимости

Нам понадобится (composer.json):

{ 
    "name": "PHPUnitSpreadsheetDataProvider",
    "require": {
        "php":                ">=5.5.0",
        "phpunit/phpunit":    "4.5.*",
        "phpoffice/phpexcel": "1.8.*"
    },
    "autoload": {
        "classmap": [
            "src/"
            ]
    }
}

Шаг #2: Основная логика

Всё сводится к тому чтобы добавить собственный метод который будет получать данные из файла и превращать их в источник данных для тестов.

Получение:

<?php
// PHPUnitTestCase.php
trait PHPUnitSDP_PHPUnitTestCase {
    /**
     * @var PHPExcel_Reader_IReader|PHPExcel_Reader_Abstract
     */
    private $_reader;

    /**
     * Возвращает пусть к ресурсу для теста.
     *
     * @param string $resource
     *
     * @return string
     */
    protected function getTestResource($resource = null) {
        $path     = (new ReflectionClass($this))->getFileName();
        $dirname  = pathinfo($path, PATHINFO_DIRNAME);
        $filename = pathinfo($path, PATHINFO_FILENAME);
        $resource = $resource ?: 'xml';

        return "{$dirname}/{$filename}.{$resource}";
    }

    /**
     * Возвращает данные для теста.
     *
     * @param string $test
     *
     * @return array|Iterator
     */
    public function getTestDataProvider($test) {
        // Файл
        $position = 2;
        $resource = $this->getTestResource('data.ods');

        // Reader?
        if (is_null($this->_reader)) {
            $this->_reader = PHPExcel_IOFactory::createReaderForFile($resource);
        }

        // Настройки
        $this->_reader->setReadDataOnly(true);
        $this->_reader->setLoadSheetsOnly($test);

        // Данные
        return new PHPUnitSDP_PHPExcelWorksheetRowIterator(
            $this->_reader->load($resource)->getActiveSheet(), $position);
    }
}

Внимания заслуживают два момента (остальное, надеюсь, очевидно):

  1. $position = 2; — первая строка (нумерация начинается с 1) с данными, всё что до неё можно использовать для комментариев (см. пример ниже)
  2. $resource — определяет название файла с данными, в данном случае это "ИмяТеста.data.ods"

Превращение:

<?php
// PHPExcelWorksheetRowIterator.php
class PHPUnitSDP_PHPExcelWorksheetRowIterator extends PHPExcel_Worksheet_RowIterator {
    /**
     * @return array
     */
    public function current() {
        $current = array();

        foreach (parent::current()->getCellIterator() as $cell) {
            /* @var $cell PHPExcel_Cell */
            $current[] = $this->getValue($cell->getCalculatedValue());
        }

        return $current;
    }

    /**
     * @param mixed $value
     *
     * @return mixed
     */
    protected function getValue($value) {
        switch (mb_strtolower(trim($value))) {
            case 'null':
                $value = null;
                break;
            case 'true':
                $value = true;
                break;
            case 'false':
                $value = false;
                break;
            default:
                /* empty */
                break;
        }

        return $value;
    }
}

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

Шаг #3: Данные

image

Шаг #4: Тест

// SDPTest.php
class SDPTest extends PHPUnit_Framework_TestCase {
    use PHPUnitSDP_PHPUnitTestCase;

    /**
     * @dataProvider getTestDataProvider
     *
     * @param number $base
     * @param number $exp
     * @param number $expected
     *
     * @return void
     */
    public function testPow($base, $exp, $expected) {
        $this->assertEquals($expected, pow($base, $exp));
    }
    
    /**
     * @dataProvider getTestDataProvider
     *
     * @param number $arg
     * @param number $expected
     *
     * @return void
     */
    public function testSqrt($arg, $expected) {
        $this->assertEquals($expected, sqrt($arg));
    }
}

Вся магия заключена в аннотации @dataProvider getTestDataProvider — перед запуском теста PHPUnit вызовет определенный ранее метод PHPUnitSDP_PHPUnitTestCase::getTestDataProvider() с аргументом в котором содержится название теста и получит необходимый источник данных.

Шаг #5: Результат

PHPUnitSpreadsheetDataProvider> phpunit
PHPUnit 4.5.0 by Sebastian Bergmann and contributors.

Configuration read from PHPUnitSpreadsheetDataProvider/phpunit.xml

......F

Time: 158 ms, Memory: 8.75Mb

There was 1 failure:

1) SDPTest::testSqrt with data set #4 (4.0, 3.0)
Failed asserting that 2.0 matches expected 3.0.

PHPUnitSpreadsheetDataProvider/tests/SDPTest.php:28
phar://PHPUnitSpreadsheetDataProvider/phpunit.phar/phpunit/TextUI/Command.php:152
phar://PHPUnitSpreadsheetDataProvider/phpunit.phar/phpunit/TextUI/Command.php:104

FAILURES!
Tests: 7, Assertions: 7, Failures: 1.

Заключение

Надеюсь данный рецепт кому-нибудь пригодится :)

Проект: yadi.sk/d/AyegnPCqf7i9Y

Автор: LastDragon

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js