Объединение зависимостей с поставщиками данных

У меня есть один метод тестирования, который зависит от другого метода, который сам использует поставщика данных в PHPUnit:

/** 
 * @dataProvider getFields
 */
public function testCanDoSomeStuff($parm1, $parm2) {
  $result = my_func($parm1, $parm2);
  $this->assertNotNull($result);

  return $result;
}

/**
 * @depends testCanDoSomeStuff
 */
public function testCanDoSomeMoreStuff($result) {
  $this->assertNotNull($result);
}

У меня также есть функция поставщика данных getFields(), нет необходимости показывать это здесь.

Первый тест, который опирается на проходы поставщика данных - $result не равен null.

Я ожидаю, что результат теста будет передан зависимому тесту в качестве параметра $result. Однако функция testCanDoSomeMoreStuff получает параметр NULL, и тест не выполняется.

Обновление

Этот простой тестовый пример демонстрирует проблему:

class MyTest extends PHPUnit_Framework_TestCase {

    /**
     * @dataProvider myFunc
     */
    public function testCanDoSomeStuff($value) {
        $this->assertNotNull($value);
        return $value;
    }

    /**
     * @depends testCanDoSomeStuff
     */
    public function testCanDoSomeMoreStuff($value) {
        $this->assertNotNull($value);
    }

    /**
     * Data provider function
     */
    public function myFunc() {
      $values = array('22');
      return array($values);
    }
}

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

Ответ 1

Проблема заключается в нескольких факторах:

  • Каждый результат теста сохраняется в массиве с использованием имени теста в качестве ключа.
  • Имя для теста, получающего данные, <name> with data set #<x>.
  • Аннотация @depends не принимает несколько слов.

Существует хакерское обходное решение: переопределить TestCase::getDataSetAsString, чтобы вернуть имя, которое примет аннотация. Это немного проблематично, поскольку требуемые поля TestCase являются частными, но с PHP 5.3.2+ вы можете обойти это.

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

Вот код с образцом теста. Обратите внимание, что вам не нужно указывать строку данных. Если вы оставите клавишу 'foo', измените значение @depends на testOne-0.

class DependencyTest extends PHPUnit_Framework_TestCase
{
    /**
     * @dataProvider data
     */
    public function testOne($x, $y) {
        return $x + $y;
    }

    public function data() {
        return array(
            'foo' => array(1, 2),
        );
    }

    /**
     * @depends testOne-foo
     */
    public function testTwo($z) {
        self::assertEquals(3, $z);
    }

    protected function getDataSetAsString($includeData = false) {
        if (!$includeData && $this->getPrivateField('data')) {
            return '-' . $this->getPrivateField('dataName');
        }
        return parent::getDataSetAsString($includeData);
    }

    private function getPrivateField($name) {
        $reflector = new ReflectionProperty('PHPUnit_Framework_TestCase', $name);
        $reflector->setAccessible(true);
        return $reflector->getValue($this);
    }
}

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

Ответ 2

Если ваш $result в testCanDoSomeStuff() действительно не null, тогда это должно работать.

Чтобы обойти это, сначала попробуйте упростить его без поставщика данных, примерно так:

class StackTest extends PHPUnit_Framework_TestCase {
    public function testCanDoSomeStuff() {
        $result = true;
        $this->assertTrue($result); 
        return $result;
    }

    /**
     * @depends testCanDoSomeStuff
     */
    public function testCanDoSomeMoreStuff($result) {
        $this->assertNotNull($result);
    }
}

Тестирование этого должно привести к чему-то вроде...

~>phpunit test.php
PHPUnit 3.6.11 by Sebastian Bergmann.
..
Time: 1 second, Memory: 3.25Mb
OK (2 tests, 2 assertions)

Теперь добавьте поставщика данных, замените мою простую переменную своей функцией и затем снова проверьте ее.

Если этот результат отличается, var_dump переменная $result, прежде чем вы вернете ее в testcase testCanDoSomeStuff(). Если это не null, отключить поведение.

Ответ 3

Я также ожидал, что описанная проблема будет работать, и после некоторых исследований я узнал, что это не ошибка, а ожидаемое, не документированное поведение. Зависимый тест не знает о наборах данных, возвращаемых поставщиком, и о том, почему тестовый параметр имеет значение null.

Источник: https://github.com/sebastianbergmann/phpunit/issues/183#issuecomment-816066

Аннотации @dataProvider вычисляются до выполнения теста. В принципе, этап предварительной проверки создает метод тестирования для каждого набора параметров, предоставляемых поставщиком данных. @depends зависит от того, что по существу является прототипом теста, управляемого данными, поэтому в качестве параметра @depends находится несуществующий (не выполненный тест).

Другим способом думать об этом является то, что если провайдер предоставлял более одного набора параметров. PHPUnit сделал бы это много методов testDataProvider, но не было бы так много методов testDataReceiver, потому что на этом тестовом методе не существует метода @dataProvider для фазы предварительного тестирования.

Однако вы можете иметь @depends и @dataProvider в том же методе тестирования. Просто будьте осторожны, чтобы получить порядок параметров справа, хотя в этом случае не может быть первого параметра.

В принципе, вы должны использовать поставщиков данных, когда набор данных имеет несколько строк. Тем не менее, вы всегда можете использовать @depend и @dataProvider в то же время для достижения примерно того же поведения.