codeception / specify
PHPUnit 和 Codeception 的 BDD 代码块
Requires
- php: >=7.4.0
- myclabs/deep-copy: ^1.10
- phpunit/phpunit: ^8.0|^9.0
README
为 PHPUnit 或 Codeception 提供的 BDD 风格代码块
Specify 允许您以更易读的 BDD 风格编写测试,就像您可能在使用 Jasmine 时所经历的那样。受 Ruby 的 MiniTest 启发,现在您可以将 BDD 和经典 TDD 风格结合在一个测试中。
安装
需要 PHP >= 7.4
- 使用 Composer 安装
composer require codeception/specify --dev
- 在您的测试中包含
Codeception\Specify
特性。
用法
将 $this->specify
方法指定为为您的 PHPUnit 测试添加隔离测试块!
public function testValidation() { $this->assertInstanceOf('Model', $this->user); $this->specify('username is required', function() { $this->user->username = null; $this->assertFalse($this->user->validate(['username'])); }); $this->specify('username is too long', function() { $this->user->username = 'toolooooongnaaaaaaameeee'; $this->assertFalse($this->user->validate(['username'])); }); }
BDD 示例
Specify 支持在 PHPUnit 中使用 describe-it
和 describe-should
BDD 语法
public function testValidation() { $this->describe('user', function () { $this->it('should have a name', function() { $this->user->username = null; $this->assertFalse($this->user->validate(['username'])); }); }); // you can use chained methods for better readability: $this->describe('user') ->should('be ok with valid name', function() { $this->user->username = 'davert'; $this->assertTrue($this->user->validate(['username'])); }) ->shouldNot('have long name', function() { $this->user->username = 'toolooooongnaaaaaaameeee'; $this->assertFalse($this->user->validate(['username'])); }) // empty codeblocks are marked as Incomplete tests ->it('should be ok with valid name') ; }
Specify + Verify 示例
使用 Codeception/Verify 进行更简单的断言
public function testValidation() { $this->specify('username is too long', function() { $this->user->username = 'toolooooongnaaaaaaameeee'; expect_not($this->user->validate(['username'])); }); $this->specify('username is ok', function() { $this->user->username = 'davert'; expect_that($this->user->validate(['username'])); }); }
用例
这个小巧的库通过组织嵌套代码块使您的测试易于阅读。这允许将相似的测试组合在一起,但将它们放在嵌套部分中。
这与 RSpec 或 Mocha 中的 BDD 语法非常相似,但可以在 PHPUnit 中使用
<?php class UserTest extends PHPUnit\Framework\TestCase { use Codeception\Specify; /** @specify */ protected $user; // is cloned inside specify blocks public function setUp(): void { $this->user = new User; } public function testValidation() { $this->user->name = 'davert'; $this->specify('i can change my name', function() { $this->user->name = 'jon'; $this->assertEquals('jon', $this->user->name); }); // user name is 'davert' again $this->assertEquals('davert', $this->user->name); } }
每个代码块都是隔离的。这意味着对 $this->specify
的调用不会改变测试类的属性值。应使用 @specify
注解标记隔离属性。
在 specify
块中的失败不会停止您的测试。
<?php $this->specify('failing but test goes on', function() { $this->fail('bye'); }); $this->assertTrue(true); // Assertions: 2, Failures: 1 ?>
如果测试失败,您将在结果中看到规范文本。
隔离
通过为每个 specify
块 克隆对象属性 来实现隔离。只有标记有 @specify
注解的属性才会被克隆。
/** @specify */ protected $user; // cloning /** * @specify **/ protected $user; // cloning protected $repository; // not cloning
使用深度克隆方法克隆对象。
如果对象克隆影响性能,请考虑关闭克隆.
默认情况下,Mock 是隔离的。
在 specify
块内定义的 Mock 不会在外部测试中执行,并且外部测试中的 Mock 也不会在代码块中触发。
<?php $config = $this->createMock(Config::class); $config->expects($this->once())->method('init'); $config->init(); // success: $config->init() was executed $this->specify('this should not fail', function () { $config = $this->createMock(Config::class); $config->expects($this->never())->method('init')->willReturn(null); // success: $config->init() is never executed });
示例:数据提供者替代方案
<?php $this->specify('should calculate square numbers', function($number, $square) { $this->assertEquals($square, $number*$number); }, ['examples' => [ [2,4], [3,9] ]]);
您还可以在 examples
参数中使用 DataProvider 函数。
<?php $this->specify('should calculate square numbers', function($number, $square) { $this->assertEquals($square, $number*$number); }, ['examples' => $this->provider()]);
也可以与真实数据提供者一起使用
<?php /** * @dataProvider someData */ public function testExamplesAndDataProvider($param) { $this->specify('should assert data provider', function ($example) use ($param) { $this->assertGreaterThanOrEqual(5, $param + $example); }, ['examples' => [[4], [7], [5]]]); } public function someData() { return [[1], [2]]; }
Before/After
还有 before 和 after 回调,它们作为 specify 的 setUp/tearDown。
<?php $this->beforeSpecify(function() { // prepare something; }); $this->afterSpecify(function() { // reset something }); $this->cleanSpecify(); // removes before/after callbacks ?>
API
可用方法
// Starts a specify code block: $this->specify(string $thing, callable $code = null, $examples = []) // Starts a describe code block. Same as 'specify' but expects chained 'it' or 'should' methods. $this->describe(string $feature, callable $code = null) // Starts a code block. If 'code' is null, marks test as incomplete. $this->it(string $spec, callable $code = null, $examples = []) $this->its(string $spec, callable $code = null, $examples = []) // Starts a code block. Same as 'it' but prepends 'should' or 'should not' into description. $this->should(string $behavior, callable $code = null, $examples = []) $this->shouldNot(string $behavior, callable $code = null, $examples = [])
打印选项
对于 PHPUnit,将 Codeception\Specify\ResultPrinter
打印机添加到 phpunit.xml
<phpunit colors="true" printerClass="Codeception\Specify\ResultPrinter"> </phpunit>
推荐
- 使用 Codeception/AssertThrows 进行异常断言
- 使用 Codeception/DomainAssert 进行详细的域逻辑断言
- 结合使用 Codeception/Verify 库,以获取 BDD 风格的断言。
许可证:MIT。