PHPUnit 使用指南

一、介绍

PHPUnit 是 PHP 领域最流行的单元测试框架,它受到 JUnit 的启发,为 PHP 开发者提供了编写和运行单元测试的工具和方法。使用 PHPUnit 可以带来以下好处:

  • 验证代码的正确性,确保函数和方法按预期工作
  • 便于代码重构,修改后可以快速验证是否破坏了现有功能
  • 帮助理解代码功能,测试用例本身就是一种文档
  • 支持测试驱动开发 (TDD) 流程

二、安装 PHPUnit

1. 安装 PHPUnit(可以通过 composer require --dev phpunit/phpunit 安装)

2. 配置PhpStorm 运行 PHPUnit(Docker环境为例)

2.1. 配置 Docker 远程解释器

  1. 打开 PhpStorm,进入 File > Settings > Build, Execution, Deployment > Docker
  2. 在右侧选择 Docker for Windows(通常会自动检测)
  3. 点击 Apply 确认配置,确保连接成功(底部会显示 “Connection successful”)
  4. 进入 File > Settings > Languages & Frameworks > PHP
  5. 点击 CLI Interpreter 右侧的 … 按钮,点击左上角 + 号,选择 From Docker, Vagrant, VM, WSL, Remote…
  6. 选择 Docker 并找到你的 php 容器
  7. 选择容器中的 PHP 可执行文件路径(如 /usr/local/bin/php)
  8. 点击 OK 完成配置,PhpStorm 会自动检测 PHP 版本和扩展

2.2 配置 PHPUnit

  1. 进入 File > Settings > Languages & Frameworks > PHP > Test Frameworks

  2. 点击 + 号,选择 PHPUnit by Remote Interpreter,选择之前配置的 Docker PHP 解释器

  3. 选择 PHPUnit 的配置方式:

    • 如果你有 phpunit.xml 文件:(正确修改修改xml文件)选择 Use configuration file 并指定路径

    • 如果你想使用 PhpStorm 的配置:选择 Use alternative configuration fileDefault configuration file

2.3 配置 Docker 容器中的路径映射

  1. 在 Docker 解释器配置中,点击 ... 按钮编辑

  2. Path mappings 部分,添加你的项目路径映射:

    • 本地路径:你的项目在主机上的路径(如 /Users/you/project

    • 远程路径:容器中的项目路径(如 /var/www/html

4. 运行 PHPUnit 测试

  1. 打开你的测试文件

  2. 右键点击测试类或方法

  3. 选择 Run 'TestName'Debug 'TestName'

  4. 或者使用 PhpStorm 的 Run 菜单运行所有测试

三、基础使用方法

1. 简单示例

  1. 创建被测试类 如

    // src/Calculator.php
    class Calculator
    {
      public function add($a, $b)
      {
          return $a + $b;
      }
    }
    
  2. 创建测试类

    测试类通常放在 tests 目录下,类名以 Test 结尾,继承 PHPUnit\Framework\TestCase。

    // tests/CalculatorTest.php
    use PHPUnit\Framework\TestCase;
    
    class CalculatorTest extends TestCase
    {
        public function testAdd()
        {
            $calculator = new Calculator();
            $result = $calculator->add(2, 3);
            $this->assertEquals(5, $result);
        }
    }
    
  3. 运行测试

    ./vendor/bin/phpunit tests/CalculatorTest.php
    

2. 常用断言方法

$this->assertEquals($expected, $actual);      // 值相等
$this->assertSame($expected, $actual);       // 类型和值都相等
$this->assertTrue($condition);               // 为 true
$this->assertFalse($condition);              // 为 false
$this->assertNull($variable);                // 为 null
$this->assertNotNull($variable);             // 不为 null
$this->assertEmpty($variable);               // 为空
$this->assertNotEmpty($variable);            // 不为空
$this->assertCount($expectedCount, $array);  // 数组元素数量
$this->assertContains($needle, $haystack);   // 包含
$this->assertInstanceOf($class, $object);    // 是某类的实例
$this->assertStringContainsString($needle, $haystack); // 字符串包含

3. 使用 phpunit.xml 配置测试套件

<!-- phpunit.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php">
    <testsuites>
        <testsuite name="Application Test Suite">
            <directory>tests</directory>
        </testsuite>
    </testsuites>
</phpunit>

运行整个测试套件:

./vendor/bin/phpunit

4. 数据提供器

数据提供器允许你用不同的数据集运行同一个测试方法。

/**
 * @dataProvider additionProvider
 */
public function testAdd($a, $b, $expected)
{
    $this->assertSame($expected, (new Calculator())->add($a, $b));
}

public function additionProvider()
{
    return [
        [0, 0, 0],
        [0, 1, 1],
        [1, 0, 1],
        [1, 1, 3] // 故意错误的数据
    ];
}

5. 测试依赖

测试方法可以依赖于其他测试方法的结果:

public function testEmpty()
{
    $stack = [];
    $this->assertEmpty($stack);
    return $stack;
}

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

6. 测试异常

测试代码是否抛出了预期的异常:

public function testException()
{
    $this->expectException(InvalidArgumentException::class);
    $this->expectExceptionMessage('Division by zero');
    
    $calculator = new Calculator();
    $calculator->divide(10, 0);
}

7. 测试输出

测试代码的输出:

public function testOutput()
{
    $this->expectOutputString('Hello World');
    echo 'Hello World';
}