Дополнительно/Тестирование

Тестирование

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

Способ тестирования зависит от вида использования сервисов, это может быть одноимённый класс со статическими методами вида Hleb\Static\Service::method() для встроенных сервисов фреймворка или DI, последнее как внедрение сервисов(и других объектов) в методы и конструктор класса.

Внедрение подходящих объектов согласно Dependency Injection распространяется во фреймворке только на создаваемые им объекты классов, таких как контроллеры, middleware, команды, события, а также объекты созданные сервисом под названием DI.


#Тестирование для Dependency Injection

Простой пример демонстрационного контроллера с DI:

<?php

namespace App\Controllers;

use 
Hleb\Base\Controller;
use 
Hleb\Reference\Interface\Log;

class 
ExampleController extends Controller
{
    public function 
index(Log $logger): string
    
{
        
$logger->info('Request to demo controller');

        return 
'OK';
    }
}

Допустим, необходимо убедиться, что контроллер возвращает текст 'OK', но при этом не отправлять сообщение в логи.

use App\Controllers\ExampleController;
use 
Hleb\Main\Logger\NullLogger;

$controller = new ExampleController();
$logger = new NullLogger();
$result $controller->index($logger);

if (
$result === 'OK') {
    
// Successful test.
}

Здесь класс логирования заменён классом с таким-же интерфейсом, но его методы ничего не отправляют в лог.

Подразумевается, что для тестирования используется одна из специальных библиотек (например github.com/phhleb/test-o) и проверка будет реализована её средствами.

Теперь вызовем метод произвольного класса через сервис DI (именно сервис фреймворка, не название архитектурного метода):

use Hleb\Reference\Interface\Log;

class 
Example
{
    public function 
run(Log $logger): string
    
{
        
$logger->info('Demo class method executed');

        return 
'OK';
    }
}

use 
Hleb\Static\DI;

$result DI::method(new Example(), 'run');

В данном случае сервис логирования будет подставлен из контейнера и сообщение попадёт в лог. Изменим обращение к методу для его тестирования:

use Hleb\Main\Logger\NullLogger;
use 
Hleb\Static\DI;

$result DI::method(new Example(), 'run', ['logger' => new NullLogger()]);

if (
$result === 'OK') {
    
// Successful test.
}

Теперь класс протестирован и логирование не произошло. Вы можете подменить таким образом любой объект для DI на специально созданный для этого собственный класс с нужным поведением, который будет удобно тестировать.


#Тестирование стандартных сервисов

Встроенные сервисы фреймворка HLEB2 имеют способ обращения со статическими методами вида Hleb\Static\Service::method(). При использовании этого способа проще обращаться к сервисам, но затрудняется тестирование содержащих их модулей, хотя всё же возможно. На примере логирования:

use Hleb\Static\Log;

class 
Example
{
    public function 
run(): string
    
{
        
Log::info('Demo class method executed');

        return 
'OK';
    }
}

use 
Hleb\Main\Logger\NullLogger;
use 
Hleb\Init\ShootOneselfInTheFoot\LogForTest;

$logger = new NullLogger();

LogForTest::set($logger);

$result = (new Example())->run();

LogForTest::cancel();

if (
$result === 'OK') {
    
// Successful test.
}

В примере показано, как состояние сервиса было заменено на тестовый объект, а затем произведён откат к начальному значению. Чтобы такой способ нельзя было использовать вне тестов, в рабочем проекте, параметру конфигурации 'container.mock.allowed' файла /config/common.php выставлено значение false.


#Функциональное тестирование

Для запуска тестов инициирующих ядро фреймворка, вам может понадобиться заменить частично или все сервисы контейнера на тестовые объекты. Для этого достаточно сделать свою реализацию и назначить её по условию (в примере это глобальная константа APP_TEST_ON):

<?php
// File /app/Bootstrap/BaseContainer.php

namespace App\Bootstrap;

use 
Hleb\Constructor\Containers\CoreContainer;

final class 
BaseContainer extends CoreContainer implements ContainerInterface
{
    private ?
ContainerInterface $testContainer null;

    #[\Override]
    final public function 
get(string $id): mixed
    
{
        if (
get_constant('APP_TEST_ON')) {
            if (
$this->testContainer === null) {
                
$this->testContainer = new TestContainer();
            }
            return 
$this->testContainer->get($id);
        }

        return 
ContainerFactory::getSingleton($id) ?? match ($id) {

            
// ... //

            
default => parent::get($id),
        };
    }
}

#Тестирование встроенных функций

Несколько встроенных функций фреймворка, упрощающих обращение к сервисам, таких как функция logger(), реализованы через тестируемые обращения к сервисам, в данном случае это обёртка над Hleb\Static\Log.


#Тестирование для $this-container в классах

В контроллерах, middlewares, командах, событиях и других классах, унаследованных от Hleb\Base\Container, обращение к контейнеру может быть как $this-container. Если вы выбрали этот способ использования контейнера (смешивание различных способов в проекте выглядело бы странным), то для тестирования необходимо особым образом инициализировать конструктор объекта.

use Hleb\Base\Container;
use 
Hleb\Reference\LogInterface;

class 
Example extends Container
{
    public function 
run(): string
    
{
        
$this->container->get(LogInterface::class)->info('Demo class method executed');

        return 
'OK';
    }
}
// TestContainer has an interface App\Bootstrap\ContainerInterface.
$config = ['container' => new TestContainer()];

$result = (new Example($config))->run();

if (
$result === 'OK') {
    
// Successful test.
}
Страница создана: @fomiash
К началу страницы