sirbrillig / corretto
一个简单且表达性强的PHP测试运行器
Requires
- php: >=5.3.0
Requires (Dev)
- sirbrillig/spies: ^1.4
This package is auto-updated.
Last update: 2024-09-20 10:17:32 UTC
README
Corretto 是一个简单且表达性强的PHP测试运行器。
模仿 mocha 设计,Corretto 提供了一种领域特定语言 (DSL),使得编写测试变得简单。无需类和长的函数名;只需用普通词语描述你的代码即可。毕竟,你更愿意花大部分时间在写代码而不是写测试上吗?
<?php use function Corretto\describe, Corretto\it, Corretto\expect; describe( 'isFive()', function() { it( 'returns true if its argument is five', function() { expect( isFive( 5 ) )->toBeTrue(); } ); it( 'returns false if its argument is not five', function() { expect( isFive( 6 ) )->toBeFalse(); } ); } );
安装
首先,你需要 Composer。然后在你的项目中
composer require --dev sirbrillig/corretto
或者,要全局安装
composer global require sirbrillig/corretto
只需确保你的全局供应商二进制目录在 $PATH
中。有关更多信息,请参阅 全局安装文档。
断言
Corretto 支持基本的断言
assertTrue()
assertFalse()
assertEquals()
assertNotEquals()
它还支持期望语法,这被推荐使用
expect( $actual )->toBeTrue()
expect( $actual )->toBeFalse()
expect( $actual )->toEqual( $expected )
expect( $actual )->toNotEqual( $expected )
expect( $actual )->toBeGreaterThan( $expected )
expect( $actual )->toBeLessThan( $expected )
expect( $actual )->toContain( $expected )
expect( $actual )->toNotContain( $expected )
自定义断言
编写自定义断言很简单。任何抛出 Exception
的函数都被视为测试失败!
(你也可以抛出 \Corretto\AssertionFailure
,这将提供略微不那么嘈杂的失败信息。)
要向 expect()
添加新方法,你需要创建一个继承自 Corretto\Expectation
的类。在类中,添加测试 $this->actual
值的方法,这个值是传递给 expect()
的。然后,将类传递给函数 Corretto\extendExpectation()
。
例如,要添加 toBeFoo()
方法,你可以编写以下内容
class FooExpectation extends Corretto\Expectation { public function toBeFoo() { if ( ! $this->actual === 'foo' ) { throw new \Exception( 'not foo' ); } } } Corretto\extendExpectation( 'FooExpectation' );
然后,你可以在测试中使用这个方法,例如
test( 'string is "foo"', function() { $string = 'foo'; expect( $string )->toBeFoo(); } );
测试
测试是通过函数 it( string $name, callable $callable )
定义的。此函数还别名为 test( string $name, callable $callable )
和 specify( string $name, callable $callable )
。
可调用参数应该是一个匿名函数,其中至少包含一个断言。
it( 'does something', function() { ... } );
你可以通过省略其可调用参数来跳过测试,例如
it( 'does something not yet defined' );
你还可以通过添加字符串 SKIP
作为其第一个参数来跳过测试
it( 'SKIP', 'does something we should not run', function() { ... } );
套件
可以使用与测试类似的语法将测试组织成套件:describe( string $name, callable $callable )
。这还别名为 suite( string $name, callable $callable )
和 context( string $name, callable $callable )
。
describe( 'some tests to run', function() { test( '...' ); test( '...' ); test( '...' ); } );
默认情况下,总是定义了一个“根”套件,因此测试可以独立存在。
套件可以嵌套到任何深度。
describe( 'MyObject', function() { describe( 'getName()', function() { describe( 'when the name is missing', function() { it( 'returns a default name', function() { $obj = new MyObject(); expect( $obj->getName() )->toEqual( 'default' ); } ); } ); describe( 'when the name is set', function() { it( 'returns the name', function() { $obj = new MyObject( 'name' ); expect( $obj->getName() )->toEqual( 'name' ); } ); } ); } ); } );
你可以在套件的第一参数添加字符串 SKIP
来跳过套件中的所有测试
describe( 'SKIP', 'some tests not to run', function() { ... } );
Before,After
每个套件都可以有一个 before( callable $callable )
,它将在套件中的所有测试运行之前被调用。同样,after( callable $callable )
将在所有测试完成后运行。
还有 beforeEach( callable $callable )
和 afterEach( callable $callable )
,它们在套件(或任何嵌套套件)中的每个测试之前/之后运行其可调用参数。这些可以用来设置和恢复每个测试之间共享的数据。
describe( 'MyObject', function() { $ctx = new \StdClass(); beforeEach( function() use ( &$ctx ) { $ctx->color = 'blue'; } ); describe( 'getName()', function() use ( &$ctx ) { beforeEach( function() use ( &$ctx ) { $ctx->obj = new MyObject(); } ); it( 'returns a default name when the name is missing', function() use ( &$ctx ) { expect( $ctx->obj->getName() )->toEqual( 'default' ); } ); it( 'returns the name', function() use ( &$ctx ) { $ctx->obj->name = 'name'; expect( $ctx->obj->getName() )->toEqual( 'name' ); } ); it( 'returns a name matching the color', function() use ( &$ctx ) { $ctx->obj->name = $ctx->color; expect( $ctx->obj->getName() )->toEqual( $ctx->color ); } ); } ); } );
在PHP中使用use
表达式向闭包传递变量是冗长的,并且这种模式很可能在测试中频繁使用。为了使这更容易,每个测试以及每个beforeEach
/afterEach
/before
/after
函数都会接收一个共享对象,该对象可以用于在函数之间传递数据。以下是使用对象而不是闭包的相同测试序列。
describe( 'MyObject', function() { beforeEach( function( $ctx ) { $ctx->color = 'blue'; } ); describe( 'getName()', function() { beforeEach( function( $ctx ) { $ctx->obj = new MyObject(); } ); it( 'returns a default name when the name is missing', function( $ctx ) { expect( $ctx->obj->getName() )->toEqual( 'default' ); } ); it( 'returns the name', function( $ctx ) { $ctx->obj->name = 'name'; expect( $ctx->obj->getName() )->toEqual( 'name' ); } ); it( 'returns a name matching the color', function( $ctx ) { $ctx->obj->name = $ctx->color; expect( $ctx->obj->getName() )->toEqual( $ctx->color ); } ); } ); } );
运行器
使用corretto
命令行工具来执行测试。它可以接受一个测试文件或测试文件的目录。如果没有提供文件,它将默认在当前目录中查找名为tests
的目录。
该工具有几个名为报告器的输出选项,可以使用-R
或--reporter
选项进行更改。默认报告器是spec
,但还有一个base
,它更简单,还有一个dots
,它更类似于PHPUnit的默认输出。
通过扩展Corretto\Reporters\Base
可以非常容易地编写自定义报告器。运行器会在发生任何事情时发出事件,报告器可以使用这些事件按自己的方式使用。
示例
<?php use function Corretto\describe, Corretto\it; use function Corretto\assertTrue, Corretto\assertFalse, Corretto\assertEquals, Corretto\assertNotEquals; use function Corretto\test, Corretto\suite; use function Corretto\specify, Corretto\context; use function Corretto\beforeEach, Corretto\afterEach, Corretto\before, Corretto\after; use function Corretto\expect; it( 'allows tests outside a suite', function() { assertTrue( true ); } ); test( 'tests can use "test" as well as "it"', function() { assertTrue( true ); } ); specify( 'tests can use "specify" as well as "it"', function() { assertTrue( true ); } ); describe( 'describe()', function() { describe( 'when nested', function() { describe( 'more than once', function() { it( 'passes if its argument is true', function() { assertTrue( true ); } ); } ); it( 'skips tests with no function' ); it( 'SKIP', 'skips tests with the SKIP string as the first argument', function() { assertTrue( false ); } ); it( 'passes if its argument is true', function() { assertTrue( true ); } ); } ); it( 'supports non-nested tests along with nested ones', function() { assertTrue( true ); } ); describe( 'when multiple tests are nested at the same level', function() { it( 'passes if its argument is true', function() { assertTrue( true ); } ); } ); describe( 'SKIP', 'allows skipping whole suites', function() { it( 'passes if its argument is true', function() { assertTrue( false ); } ); } ); } ); context( 'a bunch of tests', function() { specify( 'suites can use "context" as well as "describe"', function() { assertTrue( true ); } ); } ); suite( 'my tests', function() { test( 'suites can use "suite" as well as "describe"', function() { assertTrue( true ); } ); suite( 'there are many assertions', function() { test( 'assertEquals()', function() { $actual = 'expected'; assertEquals( 'expected', $actual ); } ); test( 'assertNotEquals()', function() { $actual = 'actual'; assertNotEquals( 'expected', $actual ); } ); test( 'assertTrue()', function() { assertTrue( true ); } ); test( 'assertFalse()', function() { assertFalse( false ); } ); } ); suite( 'expectation syntax also works for assertions', function() { suite( 'expect()', function() { test( '->toBeTrue()', function() { expect( true )->toBeTrue(); } ); test( '->toBeFalse()', function() { expect( false )->toBeFalse(); } ); test( '->toEqual()', function() { expect( 'hi' )->toEqual( 'hi' ); } ); test( '->toNotEqual()', function() { expect( 'hi' )->toNotEqual( 'bye' ); } ); } ); } ); } ); describe( 'set up and tear down', function() { $ctx = new \StdClass(); describe( 'beforeEach()', function() use ( &$ctx ) { beforeEach( function() use ( &$ctx ) { $ctx->name = 'hello'; } ); it( 'sets up the test context', function() use ( &$ctx ) { expect( $ctx->name )->toEqual( 'hello' ); $ctx->name = 'bye'; } ); it( 'runs again before each test', function() use ( &$ctx ) { expect( $ctx->name )->toNotEqual( 'bye' ); } ); } ); describe( 'before()', function() use ( &$ctx ) { before( function() use ( &$ctx ) { $ctx->name = 'hello'; } ); it( 'sets up the test context', function() use ( &$ctx ) { expect( $ctx->name )->toEqual( 'hello' ); $ctx->name = 'bye'; } ); it( 'runs only once before the suite runs', function() use ( &$ctx ) { expect( $ctx->name )->toEqual( 'bye' ); } ); } ); describe( 'afterEach()', function() { $name = 'hello'; afterEach( function() use ( &$name ) { $name = 'bye'; } ); it( 'is run after each test', function() use( &$name ) { expect( $name )->toEqual( 'hello' ); } ); it( 'runs again after each test', function() use ( &$name ) { expect( $name )->toEqual( 'bye' ); } ); } ); $name = 'hello'; describe( 'after()', function() use( &$name ) { after( function() use ( &$name ) { $name = 'bye'; } ); it( 'is run after all tests in a suite', function() use( &$name ) { expect( $name )->toEqual( 'hello' ); } ); } ); describe( 'after() (continued)', function() use( &$name ) { it( 'is run at the end of a suite', function() use ( &$name ) { expect( $name )->toEqual( 'bye' ); } ); } ); } );