Статьи

PHPUnit 3.5: легче утверждать и дразнить

Официальный выпуск PHPUnit 3.5 теперь доступен для установки PEAR после длительного периода бета-тестирования. PHPUnit 3.5 предоставляет много новых функций, таких как куча новых методов утверждений и аннотаций, и небольшой, но очень полезный мой вклад: MockBuilder.

компонентизация

База кода PHPUnit была разделена на несколько компонентов, которые связаны друг с другом через зависимости пакета PEAR. Если при обновлении до PHPUnit 3.5 у вас возникли проблемы, новая установка должна исправить их и загрузить все необходимые пакеты:

sudo pear uninstall phpunit/PHPUnit
sudo pear install phpunit/PHPUnit

Я также натолкнулся на некоторые недостающие каналы, с которых PHPUnit захватывает пакеты:

sudo pear channel-discover pear.symfony-project.com
sudo pear channel-discover components.ez.no

Я всегда предпочитаю устанавливать pear (и, следовательно, pecl) через менеджер пакетов Ubuntu, а затем оставить двоичный пакет pear для управления всеми пакетами PHP, такими как PHPUnit и Phing. Таким образом, быстрые обновления обычно доступны, и вы не заканчиваете тем, что смешиваете дабы Ubuntu с загруженными пакетами, просто применяя единственное средство установки. То же самое можно сделать и для других дистрибутивов Linux.

Кстати, компонентизация не влияет на обычного пользователя (только участников, которые должны работать в разных репозиториях git), а также на ваши тесты. Как и следовало ожидать от вспомогательного выпуска, API PHPUnit 3.x расширяется только новыми функциями, но поддерживается обратной совместимостью.

Новые утверждения и аннотации

Введены некоторые удобные новые методы утверждений, и я приведу практический пример позже в этом посте. Вызов этих методов вместо создания пользовательских логических условий для передачи в assertTrue () означает удаление сообщений об ошибках без добавления дополнительного
аргумента утверждения
$ message и улучшенную читаемость тестов. Вы можете легко извлекать пользовательские утверждения в их собственные методы, но наличие их в базовом классе тестового примера делает его доступным везде и везде.

MockBuilder

Наконец, я хотел бы поговорить о моем небольшом дополнении к PHPUnit mocking Api, которое является реализацией шаблона Builder для фиктивных объектов (необъяснимо опущено из журнала изменений, опубликованного на Github).

Как вы, возможно, знаете, Api 3.4 содержит метод getMock () с 7 аргументами. Поскольку некоторые из этих аргументов являются логическими значениями, вызов getMock () может стать очень неясным, если вы не запомните значение всех 7 аргументов. К счастью, большинство из них являются необязательными, но для указания одного из последних аргументов, которые вы должны включить, и постарайтесь найти правильное значение по умолчанию, которое меняется в зависимости от типа параметра. Это приводит к классическому вызову для получения макета с переопределенным конструктором:

$this->getMock('My_Class', array(), array(), '', false);

Который может даже добраться до:

$this->getMock('My_Class', array(), array(), '', false, false, false);

Или, если вы действительно хотите указать дополнительное поведение, чтобы:

$this->getMock('My_Class', array('doSomething', 'doSomethingElse'), array('someParam', 42), '', true, false);

Шаблон создания Builder — это небольшой уровень абстракции над процессом создания экземпляра, который в данном случае дает вам четкий API-интерфейс:

$this->getMockBuilder('My_Class')
     ->disableOriginalConstructor()
     ->getMock();

Я надеюсь, что этот синтаксический сахар поможет вам очистить ваш набор тестов, который должен иметь те же права и внимание, что и ваш рабочий код.

Примеры

Я написал пример тестового примера, в котором используются новые функции PHPUnit 3.5, чтобы вы могли быстро понять API и сохранить его в качестве справочного материала для обновления своего набора тестов.

<?php
class NewFunctionalities extends PHPUnit_Framework_TestCase
{
    /**
     * Some quick assertions on variables.
     */
    public function testNewAssertions()
    {
        $this->assertEmpty(null);
        $this->assertEmpty(0);
        $this->assertNotEmpty(42);
    }

    /**
     * Some more sophisticated assertions on objects fields.
     */
    public function testNewAssertionsOnObjectsAttributes()
    {
        $car = new Car;
        $this->assertAttributeEmpty('wheels', $car);

        $car->wheels = 4;
        $this->assertAttributeNotEmpty('wheels', $car);
    }
    
    /**
     * If you have ever written $this->assertTrue($object instanceof My_Class),
     * you'll appreciate this.
     * I only wish PHP allowed classes and interfaces to be passed like in Java:
     * $this->assertInstanceOf(Countable.class, $car)
     * to catch undefined classes issues.
     */
    public function testNewAssertionsOnObjects()
    {
        $car = new Car();
        $this->assertInstanceOf('Car', $car);
        $this->assertNotInstanceOf('Countable', $car);
    }

    /**
     * Simplified type checks.
     */
    public function testNewAssertionsOnScalars()
    {
        $string = 'ElePHPants are cute';
        $this->assertInternalType('string', $string);
        $this->assertNotInternalType('int', $string);
    }

    /**
     * Builder pattern for mocks.
     * Other methods on MockBuilder: setConstructorArgs(), setMockClassName(),
     *                               disableOriginalClone(), disableAutoload()
     */
    public function testMockBuilder()
    {
        $busMock = $this->getMockBuilder('Bus')
                        ->setMethods(array('brake', 'steer'))
                        ->disableOriginalConstructor()
                        ->getMock();
        $this->assertInstanceOf('Bus', $busMock);
    }

    /**
     * Annotations to expand declarative exception checking.
     * Error conditions are usually a neglected part of test cases.
     *
     * @expectedException CustomException
     * @expectedExceptionMessage Trying out new features
     * @expectedExceptionCode 42
     */
    public function testExceptions()
    {
        throw new CustomException("Trying out new features", 42);
    }
}

class Car
{
    public $wheels;
    public function brake() {}
    public function steer($direction) {}
}

class Bus
{
    private $_driver; 

    public function __construct(Driver $driver)
    {
        $this->_driver = $driver;
    }

    public function brake() {}
    public function steer($direction) {}
}

class CustomException extends Exception {}