目录

PHPUnit 单测基本使用

在 PHP 单元测试中,Mock 是一种非常重要的工具,可以模拟对象的行为,帮助我们隔离依赖,专注于测试代码的逻辑。以下是关于 Mock 的多种创建方式、相关set方法的使用,以及常用设置模拟属性或方法的方式,附带相应的范例。

1. Mock 的创建方式

1.1 使用createMock方法

createMock是最简单的方式,用于创建一个完整的 Mock 对象。注:PHPUnit 6.0 及以上版本才支持这个方法。

use PHPUnit\Framework\TestCase;

class MyTest extends TestCase
{
    public function testMyMethod()
    {
        $mock = $this->createMock(MyClass::class);
        $mock->method('myMethod')->willReturn('mocked value');
        $this->assertEquals('mocked value', $mock->myMethod());
    }
}

1.2 使用getMockBuilder方法

getMockBuilder提供了更灵活的配置选项,例如禁用构造函数、指定构造参数等。

use PHPUnit\Framework\TestCase;

class MyTest extends TestCase
{
    public function testMyMethod()
    {
        $mock = $this->getMockBuilder(MyClass::class)
                     ->disableOriginalConstructor()
                     ->setMethods(['myMethod'])
                     ->getMock();

        $mock->expects($this->any())
             ->method('myMethod')
             ->willReturn('mocked value');

        $this->assertEquals('mocked value', $mock->myMethod());
    }
}

1.3 使用getMockForAbstractClass方法

如果需要为抽象类创建 Mock 对象,可以使用getMockForAbstractClass

use PHPUnit\Framework\TestCase;

abstract class AbstractClass
{
    public abstract function myMethod();
}

class MyTest extends TestCase
{
    public function testMyMethod()
    {
        $mock = $this->getMockForAbstractClass(AbstractClass::class);
        $mock->expects($this->any())
             ->method('myMethod')
             ->willReturn('mocked value');

        $this->assertEquals('mocked value', $mock->myMethod());
    }
}

2. Mock 的set方法及相关配置

2.1 设置方法的预期调用次数

可以使用expects方法来指定 Mock 方法的调用次数。

use PHPUnit\Framework\TestCase;

class MyTest extends TestCase
{
    public function testMyMethod()
    {
        $mock = $this->createMock(MyClass::class);
        $mock->expects($this->once()) // 只调用一次
             ->method('myMethod')
             ->willReturn('mocked value');

        $mock->myMethod();
        // 调用第二次会抛出异常,因为只允许调用一次
    }
}

2.2 设置方法的返回值

使用willReturn方法可以指定 Mock 方法的返回值。

use PHPUnit\Framework\TestCase;

class MyTest extends TestCase
{
    public function testMyMethod()
    {
        $mock = $this->createMock(MyClass::class);
        $mock->method('myMethod')->willReturn('mocked value');
        $this->assertEquals('mocked value', $mock->myMethod());
    }
}

2.3 设置方法的参数匹配

可以使用with方法来指定 Mock 方法的参数。

use PHPUnit\Framework\TestCase;

class MyTest extends TestCase
{
    public function testMyMethod()
    {
        $mock = $this->createMock(MyClass::class);
        $mock->expects($this->once())
             ->method('myMethod')
             ->with('expected param') // 指定参数
             ->willReturn('mocked value');

        $mock->myMethod('expected param');
        $this->assertEquals('mocked value', $mock->myMethod('expected param'));
    }
}

3. 常用设置模拟属性或方法的方式

3.1 模拟方法的返回值

通过willReturn方法可以模拟方法的返回值。

use PHPUnit\Framework\TestCase;

class MyTest extends TestCase
{
    public function testMyMethod()
    {
        $mock = $this->createMock(MyClass::class);
        $mock->method('myMethod')->willReturn('mocked value');
        $this->assertEquals('mocked value', $mock->myMethod());
    }
}

3.2 模拟方法抛出异常

可以使用willThrowException方法来模拟方法抛出异常。

use PHPUnit\Framework\TestCase;

class MyTest extends TestCase
{
    public function testMyMethod()
    {
        $mock = $this->createMock(MyClass::class);
        $mock->expects($this->once())
             ->method('myMethod')
             ->willThrowException(new \Exception('Mocked Exception'));

        $this->expectException(\Exception::class);
        $mock->myMethod();
    }
}

注:PHPUnit 6.0 及以上版本:推荐使用 expectException 方法。PHPUnit 5.x 及更早版本:使用@expectedException 注解。

3.3 模拟方法返回调用参数

使用returnArgument方法可以让 Mock 方法返回调用时的参数。

use PHPUnit\Framework\TestCase;

class MyTest extends TestCase
{
    public function testMyMethod()
    {
        $mock = $this->createMock(MyClass::class);
        $mock->method('myMethod')->willReturnArgument(0);

        $this->assertEquals('param', $mock->myMethod('param'));
    }
}

Mock 对象的所有方法及返回 null

在 PHPUnit 中,如果你想让 Mock 对象的所有方法都可以被调用,并且默认返回null(或者任何指定的默认值),同时不需要保存这些方法的调用状态,可以通过setMethodswillReturn方法来实现。

通过setMethods(null)willReturn(null),你可以让 Mock 对象的所有方法都可以被调用,并且默认返回null。这种方式非常适合在测试中快速模拟对象行为,而不需要关心具体的实现细节。

以下是一个具体的实现方式:

使用setMethods(null)willReturn(null)

通过setMethods(null),你可以让 Mock 对象的所有方法都可以被调用,并且通过willReturn(null)设置默认返回值为null

示例代码:

use PHPUnit\Framework\TestCase;

class MyClass
{
    public function methodA()
    {
        return "Original methodA";
    }

    public function methodB($param)
    {
        return "Original methodB with param: $param";
    }

    public function methodC()
    {
        return "Original methodC";
    }
}

class MyTest extends TestCase
{
    public function testMockAllMethods()
    {
        // 创建 Mock 对象,允许所有方法被调用
        $mock = $this->getMockBuilder(MyClass::class)
                     ->setMethods(null) // 允许所有方法被调用
                     ->getMock();

        // 设置所有方法的默认返回值为 null
        $mock->method($this->anything())->willReturn(null);

        // 测试方法调用
        $this->assertNull($mock->methodA());
        $this->assertNull($mock->methodB("test"));
        $this->assertNull($mock->methodC());
    }
}

关键点说明

  1. setMethods(null):

    • 默认情况下,Mock 对象不会调用原始类的方法。通过setMethods(null),你可以允许 Mock 对象的所有方法被调用。
    • 如果不设置setMethods(null),Mock 对象只会调用你在setMethods中明确指定的方法,其他方法会抛出异常。
  2. willReturn(null):

    • 使用willReturn(null)可以设置 Mock 方法的默认返回值为null
    • 如果你希望返回其他默认值,比如false0,可以将willReturn(null)替换为willReturn(false)willReturn(0)
  3. method($this->anything()):

    • 这里使用$this->anything()来匹配任意方法名,确保所有方法调用都会返回指定的默认值。

注意事项

  • 如果你的类中有构造函数,并且构造函数中有一些初始化逻辑,可能需要使用disableOriginalConstructor()来禁用构造函数,以避免不必要的副作用。
  • 如果你的类中有__toString()方法,或者某些方法需要返回特定类型(比如字符串或数组),你可能需要为这些方法单独设置返回值。

禁用构造函数示例:

如果类MyClass有一个构造函数,你可以这样设置:

$mock = $this->getMockBuilder(MyClass::class)
             ->disableOriginalConstructor()
             ->setMethods(null)
             ->getMock();

$mock->method($this->anything())->willReturn(null);

Mock 静态类与静态方法

在 Mockery 中,模拟静态类和静态方法的方式与模拟普通对象和方法有所不同。Mockery 通过mock方法和shouldReceive方法来实现对静态类和静态方法的模拟。以下是具体的实现方式和示例:

模拟静态类和静态方法

1. 使用mock方法模拟静态类

Mockery 允许通过mock方法创建一个静态类的模拟对象,并通过shouldReceive方法定义静态方法的行为。

示例代码:

use Mockery as m;

class StaticClass
{
    public static function staticMethod()
    {
        return "Original value";
    }
}

class MyTest extends \PHPUnit\Framework\TestCase
{
    public function testStaticMethod()
    {
        // 创建静态类的模拟对象
        $mock = m::mock('alias:StaticClass');

        // 定义静态方法的行为
        $mock->shouldReceive('staticMethod')
             ->andReturn('Mocked value');

        // 调用静态方法并验证返回值
        $this->assertEquals('Mocked value', StaticClass::staticMethod());
    }

    public function tearDown(): void
    {
        // 清理 Mockery 的模拟对象
        m::close();
    }
}

2. 使用shouldReceive方法模拟静态方法

shouldReceive方法用于定义模拟对象的行为,包括返回值、抛出异常等。

示例代码:

use Mockery as m;

class StaticClass
{
    public static function staticMethod($param)
    {
        return "Original value with param: $param";
    }
}

class MyTest extends \PHPUnit\Framework\TestCase
{
    public function testStaticMethodWithParam()
    {
        // 创建静态类的模拟对象
        $mock = m::mock('alias:StaticClass');

        // 定义静态方法的行为
        $mock->shouldReceive('staticMethod')
             ->with('testParam') // 指定参数
             ->andReturn('Mocked value with param');

        // 调用静态方法并验证返回值
        $this->assertEquals('Mocked value with param', StaticClass::staticMethod('testParam'));
    }

    public function tearDown(): void
    {
        // 清理 Mockery 的模拟对象
        m::close();
    }
}

3. 模拟静态方法抛出异常

Mockery 可以通过shouldReceive方法定义静态方法抛出异常。

示例代码:

use Mockery as m;

class StaticClass
{
    public static function staticMethod()
    {
        return "Original value";
    }
}

class MyTest extends \PHPUnit\Framework\TestCase
{
    public function testStaticMethodThrowsException()
    {
        // 创建静态类的模拟对象
        $mock = m::mock('alias:StaticClass');

        // 定义静态方法抛出异常
        $mock->shouldReceive('staticMethod')
             ->andThrow(new \Exception('Mocked Exception'));

        // 验证异常
        $this->expectException(\Exception::class);
        $this->expectExceptionMessage('Mocked Exception');

        // 调用静态方法,触发异常
        StaticClass::staticMethod();
    }

    public function tearDown(): void
    {
        // 清理 Mockery 的模拟对象
        m::close();
    }
}

注:PHPUnit 6.0 及以上版本:推荐使用 expectException 方法。PHPUnit 5.x 及更早版本:使用@expectedException 注解。

注意事项

  1. 使用alias:在模拟静态类时,需要使用alias关键字,例如m::mock('alias:StaticClass')
  2. 清理模拟对象:在测试完成后,需要调用m::close()来清理 Mockery 的模拟对象。
  3. 参数匹配:可以通过with方法指定静态方法的参数。

通过以上方式,Mockery 可以灵活地模拟静态类和静态方法的行为,满足单元测试中的各种需求。

phpunit 断言异常不同版本

在 PHPUnit 的旧版本中(如 PHPUnit 3.x 和 4.x),setExpectedExceptiongetExpectedException 是用于断言异常的方法。这些方法在 PHPUnit 5.x 中被引入的 @expectedException 注解所取代,并在 PHPUnit 6.x 中被 expectException 方法完全替代。

  • PHPUnit 3.x 和 4.x:使用 setExpectedExceptiongetExpectedException 方法。
  • PHPUnit 5.x:使用 @expectedException 注解。
  • PHPUnit 6.x 及更高版本:使用 expectException 方法。

setExpectedException 方法

setExpectedException 方法用于设置测试中期望抛出的异常类型。如果测试代码中没有抛出指定的异常,测试将失败。

使用方式:

public function testException()
{
    $this->setExpectedException('InvalidArgumentException'); // 设置期望的异常类型
    throw new InvalidArgumentException("Test exception");
}

getExpectedException 方法

getExpectedException 方法用于获取当前设置的期望异常类型。它通常用于调试或验证异常设置是否正确。

使用方式:

public function testException()
{
    $this->setExpectedException('InvalidArgumentException');
    $expectedException = $this->getExpectedException(); // 获取期望的异常类型
    $this->assertEquals('InvalidArgumentException', $expectedException);
    throw new InvalidArgumentException("Test exception");
}

替代方法

从 PHPUnit 5.x 开始,推荐使用 @expectedException 注解来替代 setExpectedException 方法。例如:

/**
 * @expectedException InvalidArgumentException
 */
public function testException()
{
    throw new InvalidArgumentException("Test exception");
}

从 PHPUnit 6.x 开始,进一步引入了 expectException 方法,提供了更灵活的异常断言:

public function testException()
{
    $this->expectException(InvalidArgumentException::class);
    throw new InvalidArgumentException("Test exception");
}

PHPBench 基准测试

PHPUnit 本身不提供基准测试功能,但可以通过 PHPBench 等工具来实现性能测试和基准比较。PHPBench 是一个强大的基准测试框架,支持多种功能和报告格式,适合与 PHPUnit 结合使用。

PHPUnit 本身并不直接提供基准测试(Benchmark)功能,但可以通过其他工具或扩展来实现性能测试和基准比较。以下是一些相关工具和方法:

1. PHPBench

PHPBench 是一个专门用于性能基准测试的工具,类似于 PHPUnit 用于单元测试。它提供了以下功能:

  • 重复执行:多次运行代码以确定平均执行时间。
  • 迭代采样:多次采样并提供聚合统计数据。
  • 进程隔离:每次迭代在独立进程中执行,避免相互干扰。
  • 报告生成:支持多种输出格式(如控制台、CSV、HTML)。
  • 内存使用监控:监控测试代码的内存使用情况。

PHPBench 可以通过 Composer 安装:

composer require phpbench/phpbench --dev

2. PHPUnit 与 PHPBench 的集成

虽然 PHPUnit 本身不支持基准测试,但可以通过 PHPBench 来扩展其功能。例如,可以将 PHPUnit 测试类与 PHPBench 集成,通过注解或配置文件来定义基准测试。

3. 使用 PHPUnit 的--repeat选项

PHPUnit 提供了--repeat命令行选项,可以重复运行测试多次。虽然这不是一个完整的基准测试工具,但可以通过重复运行测试来观察性能变化:

phpunit --repeat 100 tests/MyTest.php

4. 其他基准测试工具

除了 PHPBench,还可以使用其他工具(如Benchmark扩展)来测试代码的执行时间。例如,Benchmark扩展提供了TimerIterateProfiler类,用于测量代码的执行时间和性能。

安装Benchmark扩展:

pear install Benchmark

示例:使用 PHPBench 进行基准测试

以下是一个简单的 PHPBench 基准测试示例:

<?php
// 基准测试类
class MyBenchmark extends \PhpBench\Benchmark
{
    public function benchAddition()
    {
        $result = 0;
        for ($i = 0; $i < 100000; $i++) {
            $result += $i;
        }
    }
}

运行基准测试:

vendor/bin/phpbench run --report=default

9ong@TsingChan 文章内容由 AI 辅助生成。