PHP测试驱动开发与PHPUnit实践测试是保证代码质量的重要手段。PHPUnit是PHP最流行的测试框架今天从基础到高级用法都说一遍。先安装PHPUnit。用Composer安装很方便composer require --dev phpunit/phpunit。写一个最简单的测试类phpuse PHPUnit\Framework\TestCase;class MathTest extends TestCase{public function testAddition(): void{$result 1 1;$this-assertEquals(2, $result);}public function testStringConcatenation(): void{$result Hello . . World;$this-assertEquals(Hello World, $result);}public function testArrayPush(): void{$arr [];array_push($arr, item);$this-assertCount(1, $arr);$this-assertContains(item, $arr);}}?测试真实业务类的时候需要用到setUp方法初始化测试环境。php// 被测试的类class Calculator{public function add(float $a, float $b): float{return $a $b;}public function subtract(float $a, float $b): float{return $a - $b;}public function multiply(float $a, float $b): float{return $a * $b;}public function divide(float $a, float $b): float{if ($b 0.0) {throw new InvalidArgumentException(除数不能为0);}return $a / $b;}public function factorial(int $n): int{if ($n 0) {throw new InvalidArgumentException(负数没有阶乘);}if ($n 1) return 1;return $n * $this-factorial($n - 1);}public function isPrime(int $n): bool{if ($n 2) return false;for ($i 2; $i sqrt($n); $i) {if ($n % $i 0) return false;}return true;}}class CalculatorTest extends TestCase{private Calculator $calculator;protected function setUp(): void{$this-calculator new Calculator();}public function testAdd(): void{$this-assertEquals(5, $this-calculator-add(2, 3));$this-assertEquals(0, $this-calculator-add(-2, 2));$this-assertEquals(5.5, $this-calculator-add(2.5, 3.0));}public function testDivide(): void{$this-assertEquals(2, $this-calculator-divide(10, 5));$this-assertEquals(3.33, $this-calculator-divide(10, 3), , 0.01);}public function testDivideByZero(): void{$this-expectException(InvalidArgumentException::class);$this-expectExceptionMessage(除数不能为0);$this-calculator-divide(10, 0);}public function testFactorial(): void{$this-assertEquals(1, $this-calculator-factorial(0));$this-assertEquals(1, $this-calculator-factorial(1));$this-assertEquals(2, $this-calculator-factorial(2));$this-assertEquals(120, $this-calculator-factorial(5));}public function testFactorialNegative(): void{$this-expectException(InvalidArgumentException::class);$this-calculator-factorial(-1);}public function testIsPrime(): void{$this-assertTrue($this-calculator-isPrime(2));$this-assertTrue($this-calculator-isPrime(3));$this-assertFalse($this-calculator-isPrime(4));$this-assertTrue($this-calculator-isPrime(5));$this-assertFalse($this-calculator-isPrime(9));$this-assertTrue($this-calculator-isPrime(11));$this-assertFalse($this-calculator-isPrime(1));}}?数据提供器可以用不同的参数多次执行同一个测试phpclass DataProviderTest extends TestCase{/*** dataProvider additionProvider*/public function testAdd(int $a, int $b, int $expected): void{$result (new Calculator())-add($a, $b);$this-assertEquals($expected, $result);}public static function additionProvider(): array{return [正数相加 [1, 2, 3],负数相加 [-1, -2, -3],正负相加 [5, -3, 2],零相加 [0, 5, 5],大数相加 [1000000, 2000000, 3000000],];}/*** dataProvider primeProvider*/public function testIsPrime(int $number, bool $expected): void{$this-assertEquals($expected, (new Calculator())-isPrime($number));}public static function primeProvider(): array{return [[2, true],[3, true],[4, false],[5, true],[6, false],[7, true],[8, false],[9, false],[11, true],[13, true],];}}?测试依赖注入和模拟对象phpinterface MailerInterface{public function send(string $to, string $subject, string $body): bool;}class UserService2{private MailerInterface $mailer;private array $users [];public function __construct(MailerInterface $mailer){$this-mailer $mailer;}public function register(string $name, string $email): array{$user [id count($this-users) 1,name $name,email $email,created_at new DateTime(),];$this-users[] $user;$this-mailer-send($email, 欢迎注册, Hello $name!);return $user;}public function getUser(int $id): ?array{foreach ($this-users as $user) {if ($user[id] $id) return $user;}return null;}}class UserServiceTest extends TestCase{public function testRegister(): void{$mailer $this-createMock(MailerInterface::class);$mailer-expects($this-once())-method(send)-with($this-equalTo(usertest.com),$this-stringContains(欢迎),$this-anything())-willReturn(true);$service new UserService2($mailer);$user $service-register(张三, usertest.com);$this-assertArrayHasKey(id, $user);$this-assertEquals(张三, $user[name]);$this-assertEquals(usertest.com, $user[email]);}public function testGetUser(): void{$mailer $this-createMock(MailerInterface::class);$mailer-method(send)-willReturn(true);$service new UserService2($mailer);$service-register(张三, atest.com);$service-register(李四, btest.com);$user $service-getUser(1);$this-assertNotNull($user);$this-assertEquals(张三, $user[name]);$this-assertNull($service-getUser(999));}}?测试有副作用的代码需要模拟外部依赖phpclass DatabaseRepository{private PDO $pdo;public function __construct(PDO $pdo){$this-pdo $pdo;}public function findUser(int $id): ?array{$stmt $this-pdo-prepare(SELECT * FROM users WHERE id ?);$stmt-execute([$id]);$result $stmt-fetch(PDO::FETCH_ASSOC);return $result ?: null;}public function saveUser(string $name, string $email): int{$stmt $this-pdo-prepare(INSERT INTO users (name, email) VALUES (?, ?));$stmt-execute([$name, $email]);return (int)$this-pdo-lastInsertId();}}class DatabaseRepositoryTest extends TestCase{public function testFindUser(): void{$pdo $this-createMock(PDO::class);$stmt $this-createMock(PDOStatement::class);$pdo-method(prepare)-willReturn($stmt);$stmt-method(execute)-willReturn(true);$stmt-method(fetch)-willReturn([id 1,name 张三,email testtest.com,]);$repo new DatabaseRepository($pdo);$user $repo-findUser(1);$this-assertNotNull($user);$this-assertEquals(张三, $user[name]);}public function testFindUserNotFound(): void{$pdo $this-createMock(PDO::class);$stmt $this-createMock(PDOStatement::class);$pdo-method(prepare)-willReturn($stmt);$stmt-method(execute)-willReturn(true);$stmt-method(fetch)-willReturn(false);$repo new DatabaseRepository($pdo);$this-assertNull($repo-findUser(999));}}?PHPUnit的常用断言- assertEquals: 检查是否相等- assertSame: 检查是否全等- assertNull/assertNotNull: 检查null- assertTrue/assertFalse: 检查布尔值- assertCount: 检查数组长度- assertContains: 检查是否包含- assertEmpty: 检查是否为空- assertInstanceOf: 检查类型测试不只是为了找bug更重要的是让你敢重构。有测试覆盖的代码改起来底气足很多。
PHP测试驱动开发与PHPUnit实践
发布时间:2026/6/6 1:59:28
PHP测试驱动开发与PHPUnit实践测试是保证代码质量的重要手段。PHPUnit是PHP最流行的测试框架今天从基础到高级用法都说一遍。先安装PHPUnit。用Composer安装很方便composer require --dev phpunit/phpunit。写一个最简单的测试类phpuse PHPUnit\Framework\TestCase;class MathTest extends TestCase{public function testAddition(): void{$result 1 1;$this-assertEquals(2, $result);}public function testStringConcatenation(): void{$result Hello . . World;$this-assertEquals(Hello World, $result);}public function testArrayPush(): void{$arr [];array_push($arr, item);$this-assertCount(1, $arr);$this-assertContains(item, $arr);}}?测试真实业务类的时候需要用到setUp方法初始化测试环境。php// 被测试的类class Calculator{public function add(float $a, float $b): float{return $a $b;}public function subtract(float $a, float $b): float{return $a - $b;}public function multiply(float $a, float $b): float{return $a * $b;}public function divide(float $a, float $b): float{if ($b 0.0) {throw new InvalidArgumentException(除数不能为0);}return $a / $b;}public function factorial(int $n): int{if ($n 0) {throw new InvalidArgumentException(负数没有阶乘);}if ($n 1) return 1;return $n * $this-factorial($n - 1);}public function isPrime(int $n): bool{if ($n 2) return false;for ($i 2; $i sqrt($n); $i) {if ($n % $i 0) return false;}return true;}}class CalculatorTest extends TestCase{private Calculator $calculator;protected function setUp(): void{$this-calculator new Calculator();}public function testAdd(): void{$this-assertEquals(5, $this-calculator-add(2, 3));$this-assertEquals(0, $this-calculator-add(-2, 2));$this-assertEquals(5.5, $this-calculator-add(2.5, 3.0));}public function testDivide(): void{$this-assertEquals(2, $this-calculator-divide(10, 5));$this-assertEquals(3.33, $this-calculator-divide(10, 3), , 0.01);}public function testDivideByZero(): void{$this-expectException(InvalidArgumentException::class);$this-expectExceptionMessage(除数不能为0);$this-calculator-divide(10, 0);}public function testFactorial(): void{$this-assertEquals(1, $this-calculator-factorial(0));$this-assertEquals(1, $this-calculator-factorial(1));$this-assertEquals(2, $this-calculator-factorial(2));$this-assertEquals(120, $this-calculator-factorial(5));}public function testFactorialNegative(): void{$this-expectException(InvalidArgumentException::class);$this-calculator-factorial(-1);}public function testIsPrime(): void{$this-assertTrue($this-calculator-isPrime(2));$this-assertTrue($this-calculator-isPrime(3));$this-assertFalse($this-calculator-isPrime(4));$this-assertTrue($this-calculator-isPrime(5));$this-assertFalse($this-calculator-isPrime(9));$this-assertTrue($this-calculator-isPrime(11));$this-assertFalse($this-calculator-isPrime(1));}}?数据提供器可以用不同的参数多次执行同一个测试phpclass DataProviderTest extends TestCase{/*** dataProvider additionProvider*/public function testAdd(int $a, int $b, int $expected): void{$result (new Calculator())-add($a, $b);$this-assertEquals($expected, $result);}public static function additionProvider(): array{return [正数相加 [1, 2, 3],负数相加 [-1, -2, -3],正负相加 [5, -3, 2],零相加 [0, 5, 5],大数相加 [1000000, 2000000, 3000000],];}/*** dataProvider primeProvider*/public function testIsPrime(int $number, bool $expected): void{$this-assertEquals($expected, (new Calculator())-isPrime($number));}public static function primeProvider(): array{return [[2, true],[3, true],[4, false],[5, true],[6, false],[7, true],[8, false],[9, false],[11, true],[13, true],];}}?测试依赖注入和模拟对象phpinterface MailerInterface{public function send(string $to, string $subject, string $body): bool;}class UserService2{private MailerInterface $mailer;private array $users [];public function __construct(MailerInterface $mailer){$this-mailer $mailer;}public function register(string $name, string $email): array{$user [id count($this-users) 1,name $name,email $email,created_at new DateTime(),];$this-users[] $user;$this-mailer-send($email, 欢迎注册, Hello $name!);return $user;}public function getUser(int $id): ?array{foreach ($this-users as $user) {if ($user[id] $id) return $user;}return null;}}class UserServiceTest extends TestCase{public function testRegister(): void{$mailer $this-createMock(MailerInterface::class);$mailer-expects($this-once())-method(send)-with($this-equalTo(usertest.com),$this-stringContains(欢迎),$this-anything())-willReturn(true);$service new UserService2($mailer);$user $service-register(张三, usertest.com);$this-assertArrayHasKey(id, $user);$this-assertEquals(张三, $user[name]);$this-assertEquals(usertest.com, $user[email]);}public function testGetUser(): void{$mailer $this-createMock(MailerInterface::class);$mailer-method(send)-willReturn(true);$service new UserService2($mailer);$service-register(张三, atest.com);$service-register(李四, btest.com);$user $service-getUser(1);$this-assertNotNull($user);$this-assertEquals(张三, $user[name]);$this-assertNull($service-getUser(999));}}?测试有副作用的代码需要模拟外部依赖phpclass DatabaseRepository{private PDO $pdo;public function __construct(PDO $pdo){$this-pdo $pdo;}public function findUser(int $id): ?array{$stmt $this-pdo-prepare(SELECT * FROM users WHERE id ?);$stmt-execute([$id]);$result $stmt-fetch(PDO::FETCH_ASSOC);return $result ?: null;}public function saveUser(string $name, string $email): int{$stmt $this-pdo-prepare(INSERT INTO users (name, email) VALUES (?, ?));$stmt-execute([$name, $email]);return (int)$this-pdo-lastInsertId();}}class DatabaseRepositoryTest extends TestCase{public function testFindUser(): void{$pdo $this-createMock(PDO::class);$stmt $this-createMock(PDOStatement::class);$pdo-method(prepare)-willReturn($stmt);$stmt-method(execute)-willReturn(true);$stmt-method(fetch)-willReturn([id 1,name 张三,email testtest.com,]);$repo new DatabaseRepository($pdo);$user $repo-findUser(1);$this-assertNotNull($user);$this-assertEquals(张三, $user[name]);}public function testFindUserNotFound(): void{$pdo $this-createMock(PDO::class);$stmt $this-createMock(PDOStatement::class);$pdo-method(prepare)-willReturn($stmt);$stmt-method(execute)-willReturn(true);$stmt-method(fetch)-willReturn(false);$repo new DatabaseRepository($pdo);$this-assertNull($repo-findUser(999));}}?PHPUnit的常用断言- assertEquals: 检查是否相等- assertSame: 检查是否全等- assertNull/assertNotNull: 检查null- assertTrue/assertFalse: 检查布尔值- assertCount: 检查数组长度- assertContains: 检查是否包含- assertEmpty: 检查是否为空- assertInstanceOf: 检查类型测试不只是为了找bug更重要的是让你敢重构。有测试覆盖的代码改起来底气足很多。