PHPUnit 和数据提供者存在问题

25

我有以下测试用例:

include_once('../Logger.php');

class LoggerTest extends PHPUnit_Framework_TestCase {

    public function providerLogger() {
        return new Logger;
    }

    /**
     * @dataProvider providerLogger
     */
    public function testAddStream($logger) {
        $this->assertTrue(false);
    }

}

当我在PHPUnit中运行它时,我得到:

PHPUnit 3.4.14 by Sebastian Bergmann.

..........

Time: 0 seconds, Memory: 5.75Mb

OK (1 tests, 0 assertions)

这个测试应该失败,但它没有。我尝试了以下方法:

public function providerLogger() {
    return array(new Logger);
}

但是我得到了:

The data provider specified for LoggerTest::testAddStream is invalid.

我尝试按照手册的说法将它声明为 static,但仍然没有任何区别。

我记得以前以类似的方式使其工作过,但我可能错了。我错过了什么?

提前感谢您的帮助。

PHPUnit 3.4.14(来自 PEAR)运行于 PHP 5.3.3


1个测试,0个断言。你的测试甚至没有被调用。我敢打赌问题不在数据提供程序中。 - zerkms
事实是,如果我删除@DataProvider标记,那么会出现错误,因为testAddStream()需要一个参数。如果我完全删除参数,则测试将失败,正如它应该的那样。 - netcoder
我在Zend Studio中使用PHPUnit时遇到了问题,如果我使用Zend_Autoloader注册命名空间,并使用@DataProvider,则会尝试将测试名称作为类加载。我敢打赌这两个问题是相关的。 - Duncan
1
将您的代码发布为问题,我们来找出答案吧!;) - netcoder
6个回答

32

小更新:从3.2版本开始(或者大约在那个版本),可以使用实例方法作为提供程序。查看下面的评论。


提供程序必须像这样。

public static function providerLogger() {
    return array(
      array(new Logger)
    );
}

首先:如果您使用phpunit 3.3以下版本,则该方法必须是静态的。

数组非常重要。理解起来并不难。外部数组为每次迭代调用测试的值。在这里,测试仅被调用一次。内部数组是按顺序调用测试的参数。您的测试仅期望一个参数,因此内部数组始终需要恰好一个值。以下是另一个小例子:

public static function addTestProvider () {
    return array(
        /* First + Second = third? */
        array(1,4,5),
        array(3,3,6),
        array(5,5,6)
    );
}
public function testAdd ($a, $b, $result) {
    $this->assertEquals($result, $a + $b);
}

这里testAdd会执行3次,每次针对二级array之一,并且它将接收来自内部array的值。您可能会注意到,测试将失败并为您提供一条消息,在数据集的哪个迭代中(这里是#3,因为5 + 5不等于6)断言失败。


15
我有所遗漏吗?为什么它必须是静态的?PHPUnit手册也使用常规方法 - Gordon
1
嗯... 你说得对。所以,如果它不是静态的,那么可能没问题。看起来这在3.3中已经改变了。至少3.2中的示例不同 ;) - KingCrunch
谢谢您的示例,但我不明白为什么您坚持该方法必须是静态的?! - hatef
@Hatef 请阅读评论。这个情况已经改变了,现在可以使用实例成员作为提供者。 - KingCrunch
2
我们已经来了个整圆。从PHPUnit v10开始,数据提供程序必须是静态的 - https://docs.phpunit.de/en/10.0/writing-tests-for-phpunit.html#data-providers - Brad Kent
显示剩余3条评论

6

我曾经遇到过同样的问题,解决方法是删除自动生成的空构造函数。我不确定为什么这样可以解决问题。另外我的测试方法名不像类名那样命名。提供者方法不需要是静态的,至少在我的测试中没有使用静态方法也可以运行。但是当我将提供者方法设置为静态时也可以正常运行。


1
非常感谢!删除 __construct 真的有帮助。 - barbushin
8
我发现你也可以使用__construct,但不要忘记像这样调用parent::_construct:public function __construct($name = NULL, array $data = array(), $dataName = '') { //... parent::__construct($name, $data, $dataName); } - barbushin

1
<?php

require_once 'calculator.php';

/**
 * Calculator test case.
 */
class CalculatorTest extends PHPUnit_Framework_TestCase {

    /**
     * @var Calculator
     */
    private $Calculator;

    /**
     * Prepares the environment before running a test.
     */
    protected function setUp() {
        parent::setUp ();
        // TODO Auto-generated CalculatorTest::setUp()
        $this->Calculator = new Calculator(/* parameters */);
    }

    /**
     * Cleans up the environment after running a test.
     */
    protected function tearDown() {
        // TODO Auto-generated CalculatorTest::tearDown()
        $this->Calculator = null;
        parent::tearDown ();
    }

    /**
     * Constructs the test case.
     */
    public function __construct() {
        // TODO Auto-generated constructor
    }

    /**
     * Tests Calculator->add()
     *
         * @dataProvider provider
         */
    public function testAdd($a, $b, $c) {
        // TODO Auto-generated CalculatorTest->testAdd()
        //$this->markTestIncomplete ( "add test not implemented" );

        //$this->Calculator->add(/* parameters */);
        $this->assertEquals($this->Calculator->add($a, $b), $c);
    }

    public static function provider()
    {
        return array(
          array(1, 1, 1),
          array(1, 1, -1),
          array(4, 2, 2),
          array(1, 1, 1)
        );
    }
}

是完整的代码集合


0

我也发现你不能直接链接数据提供程序:

class ProviderTest extends PHPUnit_Framework_TestCase {

    public function provider() {
        return array(array('test'));
    }

    /**
     * @dataProvider provider
     */
    public function providerTest1($test) {
        $this->assertTrue($test);
        return array(array($test));
    }

    /**
     * @dataProvider providerTest1
     */
    public function providerTest2($test) {
        $this->assertEquals('test', $test);
    }

}

显然,PHPUnit在运行任何测试之前调用所有提供程序函数,因此您甚至不能使用单独的提供程序函数将测试结果数据提供给其他测试。您能做的最好的事情就是模拟:

class ProviderTest extends PHPUnit_Framework_TestCase {

    private $provider_data = array();

    public function provider() {
        return array(array('test'));
    }

    /**
     * @dataProvider provider
     */
    public function testProvider1($test) {
        $this->assertFalse(empty($test));
        array_push($this->provider_data, array($test));
    }

    /**
     * @depends testProvider1
     */
    public function testProvider2($test = NULL) {
        if(is_null($test)) {
            // simulate a provider
            foreach($this->provider_data as $row) {
                call_user_func_array(array($this, __METHOD__), $row);
            }
        } else {
            $this->assertEquals('test', $test);
        }
    }

}

-2
从公共函数testAddStream($logger)中删除参数,然后再试一次。我不相信PHPUnit会调用需要无法传递的参数的测试。

1
如果我移除参数,测试将会被执行并且失败,就像它应该做的那样。@dataProvider块的目的是向测试传递参数,就像手册中所演示的那样。 - netcoder
啊,我明白了。你的dataprovider函数需要是公共且静态的。请参阅手册中的这段话:“数据提供方法必须是公共和静态的,并且可以返回一个数组的数组或实现迭代器接口并为每个迭代步骤产生一个数组的对象。对于集合中的每个数组,测试方法将使用该数组的内容作为其参数来调用。” - Macy Abbey
@ Macy:不幸的是,我也尝试过这个(静态、非静态、返回数组、返回对象...),但结果仍然相同... - netcoder
你能给我展示一下你尝试过返回单元素数组的公共静态函数的代码版本吗?你是否也尝试过使这个单元素数组只返回一个整数,而不是一个新的Logger对象?这可能是因为PHPUnit在Logger对象实例化时吞掉了运行时错误。 - Macy Abbey
我的解决方案怎么样?它已经放在那里大约20分钟了 ;) - KingCrunch
@ Macy:KingCrunch已经解决了。也感谢你的帮助! - netcoder

-2

瞧,我已经成功实现了一个模式,用于实现数据提供程序的测试依赖性!这样你就可以链式调用数据提供程序。

class ProviderDependencyTest extends PHPUnit_Framework_TestCase
{
    private static $dataSet;

    public function provideData()
    {
        self::$dataSet = array(
                    array(2,2,4),
                    array(1,0,2),
                    array(0,0,0)
                );

        //use static storage so you don't have to call the dataProvider again
        return self::$dataSet;
    }

    public function testProvideAdd()
    {
        $data = self::$dataSet;

        $this->assertEquals(3,count($data[0]));

        return $data[0];
    }

    /**
     * @depends testProvideAdd
     */
    public function testAdd($data)
    {
        $sum = $data[0] + $data[1];

        $this->assertEquals($data[2], $sum);

        return array($sum,$data[0],$data[1]);
    }

    /**
     * @depends testAdd
     */
    public function testSubtract($data)
    {
        $difference = $data[0] - $data[1];

        $this->assertEquals($data[2], $difference);

        return array($difference,$data[0],$data[1]);
    }

    /**
     * @depends testSubtract
     */
    public function testMultiply($data)
    {
        $product = $data[0] * $data[2];

        $this->assertEquals($data[1], $product);

        return $product;
    }

    /**
     * @depends testMultiply
     *
     * @dataProvider provideData
     */
    public function testMath($a,$b,$c)
    {
        //don't redo the first set of tests
        if(array($a,$b,$c) == self::$dataSet[0])
        {
            return;
        }

        $sum = $this->testAdd(array($a,$b,$c));
        $difference= $this->testSubtract($sum);
        $product = $this->testMultiply($difference);

        $this->assertInternalType('integer', $product);
    }
}

第二个数据集有1个测试失败,以此作为例子。


请不要以这种方式在您的答案中包含您的网站链接。该链接与问题没有直接关系,因此它在这里的出现看起来像是垃圾邮件。我会为您删除它。 - Andrew Barber
当然可以!只是想帮忙。 - Josh Woodcock

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接