编写PHPUnit测试

本文详细介绍了如何编写PHPUnit测试,包括类测试、测试依赖关系、数据供给器的使用、异常测试、PHP错误测试和输出测试。在类测试中,强调了测试方法的命名和断言方法的使用。测试依赖关系部分解释了生产者和消费者的概念,以及如何处理对象的传递。数据供给器部分展示了返回数组和迭代器对象的方式。同时,文章提醒在使用数据供给器时要注意测试的执行顺序。此外,还介绍了如何测试预期的异常和PHP错误,以及对程序输出的验证。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2 编写PHPUnit测试

类测试

  • 针对类的测试,命名习惯是$className+Test,即类名+Test
  • 该类通常继承PHPUnit\Framework\TestCase
  • 测试方法都是以test+$functionName命名的,配合phpDocumentor ,可以在注释中使用@test方法标注为测试方法
  • 继承的PHPUnit\Framework\TestCase中有很多判断方法,这些方法都是以assert开头的。

具体的函数名太多了,肯定记不住,就说他们的大致内容,当你需要的时候再去找对应的方法:

  • 数据类型判断,intfloatstring
  • 文件,路径等,该文件是否可读,路径是否存在,该文件是否存在等
  • 数组判断,该数组中是否有该值
  • 字符串判断,字符串是否以指定字符串开头,字符串是否包含指定字符
  • xml文件判断,两个xml文件是否相等等
  • 对象判断,该对象是否包含指定属性,两个对象是否相同,该对象是否是一个类的实例等
  • 大于,小于等判断

以上判断有是否相等,就会有不想等的对应函数。

<?php
use PHPUnit\Framework\TestCase;

class StackTest extends TestCase
{
    public function testPushAndPop()
    {
        $stack = [];
        // 判断是否相同
        $this->assertEquals(0, count($stack));

        array_push($stack, 'foo');
        $this->assertEquals('foo', $stack[count($stack)-1]);
        $this->assertEquals(1, count($stack));

        $this->assertEquals('foo', array_pop($stack));
        $this->assertEquals(0, count($stack));
    }
}
?>

判断都是正确时,系统执行结果:

在这里插入图片描述

判断错误,系统执行结果:

在这里插入图片描述

测试依赖关系:@depends

PHPUnit支持对测试方法之间的显式依赖关系进行声明。这种依赖关系并不是定义在测试方法的执行顺序中,而是允许生产者(producer)返回一个测试基境(fixture)的实例,并将此实例传递给依赖于它的消费者(consumer)们。

  • 生产者(producer),是能生成被测单元并将其作为返回值的测试方法。
  • 消费者(consumer),是依赖于一个或多个生产者及其返回值的测试方法。
class UserStoreTest extends TestCase{
    public function testDepends(){
        $this->testPop(
            $this->testPush(
                $this->testEmpty()
            )
        );
    }
    public function testEmpty()
    {
        $stack = [];
        $this->assertEmpty($stack);

        return $stack;
    }

    /**
     * @depends testEmpty
     */
    public function testPush(array $stack)
    {
        array_push($stack, 'foo');
        $this->assertEquals('foo', $stack[count($stack)-1]);
        $this->assertNotEmpty($stack);

        return $stack;
    }

    /**
     * @depends testPush
     */
    public function testPop(array $stack)
    {
        $this->assertEquals('foo', array_pop($stack));
        $this->assertEmpty($stack);
    }
}

我感觉没有特定的生成者与消费者,而是看调用关系,比如testPush依赖testEmpty,则testEmpty是生产者,而testPush是消费者。再来看testPoptestPushtestPush这时就是生产者,而testPop则是消费者。

默认情况下,生产者所产生的返回值将“原样”传递给相应的消费者。这意味着,如果生产者返回的是一个对象,那么传递给消费者的将是一个指向此对象的引用。如果需要传递对象的副本而非引用,则应当用 @depends clone 替代 @depends。

class UserStoreTest extends TestCase{
    public function testOne(){
        $this->assertTrue(false);
    }

    /**
     * @depends testOne
     */
    public function testTwo(){

    }
}

这里讲一些注意点,PHPUnit会把你的类中的所有public方法从上到下执行一遍,所以如果你要是有对应的依赖,则生产者要在消费者前面。

而且因为phpunit会自动创建类的实例,所以你不需要在代码下面创建对应的实例并显示调用其中的方法,但是你要使用@depends指定依赖:

class UserStoreTest extends TestCase{
    public function testProducerFirst()
    {
        $this->assertTrue(true);
        return 'first';
    }

    public function testProducerSecond()
    {
        $this->assertTrue(true);
        return 'second';
    }

    /**
     * @depends testProducerFirst
     * @depends testProducerSecond
     */
    public function testConsumer()
    {
        $arguments=func_get_args();
        print_r($arguments);
        $this->assertEquals(
            ['first', 'second'],
            $arguments
        );
    }
}

这里phpunit会自动将

class UserStoreTest extends TestCase{
    public function testProducerFirst()
    {
        $this->assertTrue(true);
        return 'first';
    }

    public function testProducerSecond()
    {
        $this->assertTrue(true);
        return 'second';
    }

    /**
     * @depends testProducerFirst
     * @depends testProducerSecond
     */
    public function testConsumer()
    {
        $arguments=func_get_args();
        print_r($arguments);
        $this->assertEquals(
            ['first', 'second'],
            $arguments
        );
    }
}

这里phpunit会自动将testProducerFirsttestProducerSecond的返回值作为参数传入testConsumer中。

数据供给器:@dataProvider

顾名思义,测试一些类的时候需要提供数据,这里phpunit可以设置一个方法,返回测试的数据,但是强制返回数组,或者可以循环的对象。

返回数组

class UserStoreTest extends TestCase{
    /**
     * @param $a
     * @param $b
     * @param $expected
     * @dataProvider additionProvider
     */
    public function testAdd($a,$b,$expected){
        $this->assertEquals($expected,$a+$b);
    }
    public function additionProvider(){
        return [
            [0, 0, 0],
            [0, 1, 1],
            [1, 0, 1],
            [1, 1, 2]
        ];
    }
}

重点是数组中的$value$key只是用来做定位具体哪一行的,比如:

/**
 * Class UserStoreTest
 */
class UserStoreTest extends TestCase{
    /**
     * @param $a
     * @param $b
     * @param $expected
     * @dataProvider additionProvider
     */
    public function testAdd($a,$b,$expected){
        $this->assertEquals($expected,$a+$b);
    }
    public function additionProvider(){
        return [
            'adding zeros'  => [0, 0, 0],
            'zero plus one' => [0, 1, 1],
            'one plus zero' => [1, 0, 1],
            'one plus one'  => [1, 1, 3]
        ];
    }
}

错误提示信息:s

1) UserStoreTest::testAdd with data set "one plus one" (1, 1, 3)

返回迭代器对象

创建迭代器类:implements \Iterator

这个其实是要让类继承一个接口:

class Row implements \Iterator{
    private $array;
    private $index=0;
    public function __construct()
    {
        $this->array=array(
            array(1,2,3),
            array(3,4,5),
            array(6,7,8)
        );
        $this->index=0;
    }

    /**
     * 将指针指向下一个
     */
    public function next()
    {
        print "next\r\n";
        $this->index++;
    }

    /**
     * 验证是否还需要继续循环
     * @return bool|void
     */
    public function valid()
    {
        print "valid\r\n";
        return isset($this->array[$this->index]);
    }

    /**
     * 获取当前的key
     * @return mixed|void
     */
    public function key()
    {
        print "key\r\n";
        return $this->index;
    }

    /**
     * 重制
     */
    public function rewind()
    {
        print "rewind\r\n";
        $this->index=0;
    }

    /**
     * 获取当前值
     * @return mixed
     */
    public function current()
    {
        print "current\r\n";
        return $this->array[$this->index];
    }

}

$row=new Row();

foreach ($row as $key=>$value){
    print $key." #################################################\r\n";
}

输出结果

rewind
valid
current
key
0 #################################################
next
valid
current
key
1 #################################################
next
valid
current
key
2 #################################################
next
valid
current
key
3 #################################################
next
valid
测试类
require_once __DIR__."/../../vendor/autoload.php";
require_once __DIR__."/../Row.php";

use PHPUnit\Framework\TestCase;

/**
 * Class UserStoreTest
 */
class UserStoreTest extends TestCase{
    /**
     * @param $a
     * @param $b
     * @param $expected
     * @dataProvider additionProvider
     */
    public function testAdd($a,$b,$expected){
      	// 参数的数量是随意的,按照顺序从数据提供器中获取
    }
    public function additionProvider(){
      	// 这里返回的值一定要是类似二维数组的值,不论就是二维数组,还是迭代器嵌套迭代器
        return new Row();
    }
}
@depends@dataProvider组合使用
class UserStoreTest extends TestCase{
    public function provider()
    {
        return [['provider1'], ['provider2']];
    }

    public function testProducerFirst()
    {
        $this->assertTrue(true);
        return 'first';
    }

    public function testProducerSecond()
    {
        $this->assertTrue(true);
        return 'second';
    }

    /**
     * @depends testProducerFirst
     * @depends testProducerSecond
     * @dataProvider provider
     */
    public function testConsumer()
    {
        $argument=func_get_args();
        print "输出数据\r\n";
        print_r($argument);
        print "\r\n";
        $this->assertEquals(
            ['provider1', 'first', 'second'],
            $argument
        );
    }
}

系统会先将@dataProvider作为参数,接着就是对应的@depends,比如这里输出的数据就是:

输出数据
Array(
	[0]=>"provider1",
	[1]=>"first",
	[2]=>"second"
)
输出数据
Array(
	[0]=>"provider2",
	[1]=>"first",
	[2]=>"second"
)
注意事项

如果一个测试依赖于另外一个使用了数据供给器的测试,仅当被依赖的测试至少能在一组数据上成功时,依赖于它的测试才会运行。

使用了数据供给器的测试,其运行结果是无法注入到依赖于此测试的其他测试中的。

直接上代码可能比较好理解一点:

class UserStoreTest extends TestCase{
    public function provider()
    {
        return [['provider3'], ['provider2']];
    }

    public function testProducerFirst()
    {
        $this->assertTrue(true);
        return 'first';
    }

    public function testProducerSecond()
    {
        $this->assertTrue(true);
        return 'second';
    }

    /**
     * @depends testProducerFirst
     * @depends testProducerSecond
     * @dataProvider provider
     */
    public function testConsumer()
    {
        $argument=func_get_args();
        // 这里的两条数据都是错误的,所以不会运行到 testAfterDepends 中
        $this->assertEquals(
            ['provider1', 'first', 'second'],
            $argument
        );
        return $argument;
    }

    /**
     * @depends testConsumer
     */
    public function testAfterDepends(){
        $arguments=func_get_args();
        print "\r\n";
        print "输出数据\r\n";
        print_r($arguments);
    }
}

再来

class UserStoreTest extends TestCase{
    public function provider()
    {
        return [['provider1'], ['provider2']];
    }

    public function testProducerFirst()
    {
        $this->assertTrue(true);
        return 'first';
    }

    public function testProducerSecond()
    {
        $this->assertTrue(true);
        return 'second';
    }

    /**
     * @depends testProducerFirst
     * @depends testProducerSecond
     * @dataProvider provider
     */
    public function testConsumer()
    {
        $argument=func_get_args();
        $this->assertEquals(
            ['provider1', 'first', 'second'],
            $argument
        );
        return $argument;
    }

    /**
     * @depends testConsumer
     */
    public function testAfterDepends(){
        // 第一条数据是正确的,但是 testConsumer 的返回值是没有办法传递到 testAfterDepends 中的
        $arguments=func_get_args();
        print "\r\n";
        print "输出数据\r\n";
        print_r($arguments);
    }
}

所有的数据供给器方法的执行都是在对 setUpBeforeClass 静态方法的调用和第一次对 setUp 方法的调用之前完成的。因此,无法在数据供给器中使用创建于这两个方法内的变量。这是必须的,这样 PHPUnit 才能计算测试的总数量。

暂时没有讲到这两个函数,所以暂时先不用管。

对异常进行测试

所谓对异常进行测试,就是测试在以下代码中会不会出现异常,比如在下面的代码中,只有抛出异常,系统才是正常执行的,否则就是异常的。这里的例子演示了2种异常测试的方式,方式1,$this->expectException,另一种就是@expectedException Exception,但是第二种不建议使用。

class UserStoreTest extends TestCase{

    public function testException(){
      	// 指定捕获的异常
        $this->expectException(\Exception::class);
        $this->result(true);
        $this->result(false);
    }

    /**
     * 不建议使用,因为已经快要被取消了
     * @expectedException Exception
     * @throws Exception
     */
    public function testExceptionUseDoc(){
        $this->result(true);
        $this->result(false);
    }
    protected function result($result){
        if(!$result){
            throw new \Exception("抛出异常");
        }else{
            return $result;
        }
    }
}

不止可以对异常的种类进行限制,还可以对异常的数字,抛出异常的文字,以及针对对应文字进行正则判断等:

  • expectExceptionCode
  • expectExceptionMessage
  • expectExceptionMessageRegExp

对PHP错误进行测试

默认情况下,PHPUnit 将测试在执行中触发的 PHP 错误、警告、通知都转换为异常。

所以你只需要抓捕异常。

class UserStoreTest extends TestCase{

    public function testFailingInclude()
    {
        $this->expectException("PHPUnit\Framework\Error\Error");
        include 'not_existing_file.php';
    }
}

对异常进行测试是越明确越好的。对太笼统的类进行测试有可能导致不良副作用。因此,不再允许用 @expectedExceptionsetExpectedException()Exception 类进行测试。

对输出进行测试

简单来说就是对printecho等值的输出进行判断

class UserStoreTest extends TestCase{
    public function testExpectFooActualFoo()
    {
        $this->expectOutputString('foo');
        print 'foo';
    }

    public function testExpectBarActualBaz()
    {
        $this->expectOutputString('bar');
        print 'baz';
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值