В 1-й части мы рассмотрели основы использования фреймворка для тестирования PHPUnit. Теперь пора углубиться в детали.

Поставщики (провайдеры) данных.

Поставщики данных (data providers) используются для подстановки аргументов в тестируемый метод, для вызова одного и того же метода с разными значениями. Это позволяет не дублировать один и тот же метод с разными данными.

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

Провайдер данных это обычный метод, который возвращает массив с набором параметров следующем формате:
public function providerName(){
    return [
        [12, 12],
        [0, 0],
        [true, 'str']
    ];
}

Чтобы использовать провайдер, нужно перед методом, в который будем вставлять данные, разместить комментарий с директивой @dataProvider:
/**
 * @dataProvider nameMethodProvider
 */
После первого слэша обязательно две звездочки.
Где nameMethodProvider – название метода провайдера.

Например:
/**
 * @dataProvider providerSum
 */
public function testParseAndSum($a, $b)
{
    $result = SimpleParser::ParseAndSum($b);
    $this->assertEquals($a, $result); 
}
public function providerSum(){
    return [
        [12, 12],
        [0, 0],
        [true, 'str']
    ];
}
где в метод testParseAndSum передаем из провайдера:
$a – ожидаемое значение;
$b – значение полученное при выполнении тестируемого метода

В примере метод assertEquals() сравнивает два значения. Таким образом имеем 3 теста, где сравниваются 12 с 12, 0 с 0 и true с 'str'. Последний тест не пройдет, т.к.
true - это логический тип данных, а 'str' - строка.

Рассмотрим пример, который в т.ч. использует провайдер данных.
Файл app\Connection.php
<?php
namespace app;
/*
 * Класс устанавливающий соединение с заданным сайтом используя библиотеку curl
 * В случае успеха возвращает true, иначе false.
 */
class Connection
{
    public $curl;
    public function __construct()
    {
        $this->curl = curl_init();
    }
    public function siteVerification($host)
    {
        curl_setopt($this->curl, CURLOPT_URL, $host);
        curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true);
        $response = curl_exec($this->curl);
        if ($response) return true;
        else return false;
    }
    public function curlClose(){
        curl_close($this->curl);
    }
}

Можете запустить в WEB данный код, проверить его работу:
$con = new Connection();
$host = 'http://klisl.com';
$con->siteVerification($host);
$con->curlClose();
Файл tests\ConnectionTest.php
<?php
namespace tests;
use PHPUnit\Framework\TestCase;
use app\Connection;
class ConnectionTest extends TestCase
{
    protected $obj; //объект тестируемого класса
    /*
     * Метод вызывается в начале тестирования
     * Тут создаем объект класса который будем тестировать
     * при его создании выполняется метод-конструктор прописанный в данном классе
     */
    protected function setUp() {
        $this->obj = new Connection();
    }
    /**
     * @dataProvider hostsProvider
     * Тест проходит успешно если возвращаемое значение равно true
     * Проверяем на заведомо существующих доменах.
     * Используется метод провайдер данных (hostsProvider) для получения аргумента $a
     */
    public function testSiteVerificationTrue($a){
        $this->assertTrue($this->obj->siteVerification($a));
    }
    /*
     * Метод предоставляющий данные для тестирования.
     * Для каждого массива будет произведен отдельный тест
     */
    public function hostsProvider(){
        return [
            ['ukr.net'],
            ['klisl.com'],
        ];
    }
    /*
     * Тест проходит успешно если возвращаемое значение равно false
     * Тестируем с несуществующим доменом
     */
    public function testSiteVerificationFalse(){
        $this->assertFalse($this->obj->siteVerification('cocojambo'));
    }
}

Запускаем тестирование в консоли:
phpunit tests
или другой командой в зависимости от настроек которые мы рассматривали в предыдущей части статьи.
Результат:






Зависимости между методами тестирующего класса.

Использование зависимостей позволяет пропускать выполнение определенных методов тестирующего класса в случае если методы тестов от которых они зависимы не выполнены успешно, а так же передавать нужные данные из одного метода в другой. Для этого используется директива @depends
В первую очередь выполняется родительский метод от которого зависит выполняемый тест. Если родительский тест не будет пройден, то зависимый метод теста не будет выполнен.
Пример.
<?php
namespace tests;
use PHPUnit\Framework\TestCase;
class MyTest extends TestCase
{
    public function testOne(){
        $num = 3;
        $this->assertEquals(3, $num);
        return $num;
    }
    /**
     * @depends testOne
     */
    public function testTwo($num){
        $result = 10 + $num;
        $this->assertEquals(13,$result);
    }
}
Первый тест (метод testOne) будет пройден успешно, т.к. метод assertEquals сравнивает два переданных ему аргумента между собой, а в нашем случае 3 = 3.
Далее передаем значение переменной $num в метод testTwo, для этого дописываем
return $num;
Т.к. родительский тест пройден, будет выполнен второй, зависимый тест метода testTwo, который так же завершится успешно. А вот если в первом тесте вы поменяете значение переменной:
$num = 4;
то увидим, что второй тест был пропущен (S), т.к. родительский тест не пройден:





Тестирование защищенных и частных методов.


Стоит сразу отметить, что защищенные и частные методы далеко не всегда требуют тестирования.
Например есть такой класс и защищенный метод setPassword(), который нужно проверить:

<?php
namespace main;
class User
{
    private $password;
    public function getPassword(){
        if (isset($this->password)){
            return $this->password;
        } else {
            return null;
        }
    }
    public function setPassword($login)
    {
        if(is_string($login)){
            $this->password = $this->cryptPassword($login);
            return true;
        }
        return false;
    }
    protected function cryptPassword($login){
        return md5(uniqid($login));
    }
}

Попробуем создать и выполнить тест (проверка на тип возвращаемого значения) обычным образом:
<?php
namespace tests;
use main\User;
class UserTest extends TestCase
{ 
    public function testGetPassword()
    {
        $obj = new User();
        $login = 'login';
        $this->assertInternalType('string', $obj->cryptPassword($login));
    }
}

Если тестируемый метод является защищенным или частным, то при выполнения теста получим ошибку такого плана:
Fatal error: Call to protected method User::cryptPassword() from context 'UserTest'…
т.к. данный защищенный метод может использоваться только внутри данного или дочерних классов.

Есть несколько вариантов тестирования защищенных (частных) методов.

1. Использование публичного метода взаимодействующего с защищенным (частным).

Из данного примера видно, что защищенный метод cryptPassword() вызывается в публичном методе setPassword(). Соответственно протестировав данный публичный метод на возвращаемое значение true можно быть уверенным, что метод cryptPassword() отработал и вернул некий пароль:
<?php
namespace tests;
use main\User;
class UserTest extends TestCase
{ 
    public function testGetPassword()
    {
        $obj = new User();
        $login = 'login';
        $this->assertTrue($obj->setPassword($login));
    }
}

Если нас не устраивает определение «некий» пароль и нужно, например, уточнить, что это строка длинной 32 символа, то у нас есть так же другой публичный метод getPassword(), с помощью которого и получим результат отработки метода cryptPassword().
<?php
namespace unit;
use main\User;
class UserTest extends TestCase
{ 
    public function testGetPassword()
    {
        $obj = new User();
        $login = 'login';
        $obj->setPassword($login);
        $this->assertEquals(32, strlen($obj->getPassword())); 
    }
}
тут сначала выполняем метод setPassword() для формирования пароля и сохранения в свойстве $password, откуда метод getPassword() его и возьмет.


2. Использование интерфейса Reflection API.

В данном случае используем ReflectionClass - это специальный класс языка PHP, который содержит множество различных методов по получению информации о структуре произвольного класса. И что в данном случае особенно актуально – позволяет взаимодействовать с защищенными и частными методами.
<?php
namespace tests;
use main\User;
class UserTest extends \PHPUnit_Framework_TestCase{
    /**
     * Выполнение защищенного/частного метода класса.
     *
     * @param object &$object    Объект тестируемого класса
     * @param string $methodName Вызываемое имя метода
     * @param array  $parameters Массив параметров (аргументов) метода.
     *
     * @return mixed Возврат метода.
     */
    public function invokeMethod(&$object, $methodName, array $parameters = array())
    {
        $reflection = new \ReflectionClass(get_class($object));
        $method = $reflection->getMethod($methodName);
        $method->setAccessible(true);
        return $method->invokeArgs($object, $parameters);
    }
    public function testCryptPassword(){
        $obj = new User();
        $login = 'login';
        $password = $this->invokeMethod($obj, 'cryptPassword', [$login]);
        $this->assertEquals(32, strlen($password));
    }
}
В данном случае мы напрямую проверяем, что строка возвращаемая защищенным методом cryptPassword() содержит 32 символа.

Данная статья является одной из серии статей про фреймворк для тестирования PHPUnit. Читайте так же: