智慧屏安装APP的最佳实践与跨平台小程序开发的结合
710
2022-10-27
Pho:一个行为驱动BDD开发测试框架
BDD test framework for PHP, inspired by Jasmine and RSpec. Features a familiar syntax, and a watch command to automatically re-run specs during development. It can also be extended with custom matchers and reporters.
InstallationUsageWriting SpecsRunning SpecsExpectations/MatchersCustom MatchersReportersMockingNamespace
Installation
The following instructions outline installation using Composer. If you don't have Composer, you can download it from http://getcomposer.org/ If you're new to composer, make sure to add the vendor bin to your PATH:
# Append the following to your profile file, for example in ~/.profileexport PATH=$HOME/.composer/vendor/bin:$PATH
To install pho, run:
composer global require danielstjules/pho
Usage
Usage: pho [options] [files]Options -a --ascii Show ASCII art on completion -b --bootstrap
Writing Specs
Pho exposes a DSL for organizing and writing your tests, which includes the following functions: describe, context, it, before, after, beforeEach, afterEach and expect. Equivalent functions for disabling specs and suites also exist via xdescribe, xcontext and xit.
To create a suite, describe and context can be used by passing them a string and function. Both are interchangeable, though context is more often nested in a describe to group some set of behaviour. it is then used to create a spec, or test.
A spec may contain multiple expectations or assertions, and will pass so long as all assertions pass and no exception is uncaught. For asserting values in pho, expect can be used. The function accepts the value to be tested, and may be chained with a handful of matchers.
toBe(true); }); it('can have specs that fail', function() { expect(false)->not()->toBe(false); }); it('can have incomplete specs');});
Objects may be passed between suites and specs with php's use keyword. Here's an example:
describe('Example', function() { $object = new stdClass(); $object->name = 'pho'; context('name', function() use ($object) { it('is set to pho', function() use ($object) { expect($object->name)->toBe('pho'); }); });});
Things can get a bit verbose when dealing with multiple objects that need to be passed into closures with use. To avoid such long lists of arguments, $this can be used to set and retrieve values between suites and specs.
describe('SomeClass', function() { $this->key1 = 'initialValue'; $this->key2 = 'initialValue'; context('methodOne()', function() { $this->key1 = 'changedValue'; it('contains a spec', function() { expect($this->key1)->toBe('changedValue'); expect($this->key2)->toBe('initialValue'); }); }); context('methodTwo()', function() { it('contains another spec', function() { expect($this->key1)->toBe('initialValue'); expect($this->key2)->toBe('initialValue'); }); });});
Hooks are available for running functions as setups and teardowns. before is ran prior to any specs in a suite, and after, once all in the suite have been ran. beforeEach and afterEach both run their closures once per spec. Note that beforeEach and afterEach are both stackable, and will apply to specs within nested suites. Furthermore, Global hooks may be defined in your bootstrap file. For example, an afterEach hook in a bootstrap file will run after every test in your suite.
describe('Suite with Hooks', function() { $this->count = 0; beforeEach(function() { $this->count = $this->count + 1; }); it('has a count equal to 1', function() { expect($this->count)->toEqual(1); // A single beforeEach ran }); context('nested suite', function() { beforeEach(function() { $this->count = $this->count + 1; }); it('has a count equal to 3', function() { expect($this->count)->toEqual(3); // Both beforeEach closures incremented the value }); });});
Running Specs
By default, pho looks for specs in either a test or spec folder under the working directory. It will recurse through all subfolders and run any files ending with Spec.php, ie: userSpec.php. Furthermore, continuous testing is as easy as using the --watch option, which will monitor all files in the path for changes, and rerun specs on save.
Expectations/Matchers
Type Matching
expect('pho')->toBeA('string');expect(1)->notToBeA('string');expect(1)->not()->toBeA('string');expect(1)->toBeAn('integer'); // Alias for toBeAexpect('pho')->notToBeAn('integer');expect('pho')->not()->toBeA('integer');
Instance Matching
expect(new User())->toBeAnInstanceOf('User');expect(new User())->not()->toBeAnInstanceOf('Post');expect(new User())->notToBeAnInstanceOf('Post');
Strict Equality Matching
expect(true)->toBe(true);expect(true)->not()->toBe(false);expect(true)->notToBe(false);expect(['foo'])->toEqual(['foo']); // Alias for toBeexpect(['foo'])->not()->toEqual(true);expect(['foo'])->notToEqual(true);
Loose Equality Matching
expect(1)->toEql(true);expect(new User('Bob'))->not()->ToEql(new User('Alice'))expect(new User('Bob'))->notToEql(new User('Alice'))
Length Matching
expect(['tdd', 'bdd'])->toHaveLength(2);expect('pho')->not()->toHaveLength(2);expect('pho')->notToHaveLength(2);expect([])->toBeEmpty();expect('pho')->not()->toBeEmpty();expect('pho')->notToBeEmpty();
Inclusion Matching
expect('Spectacular!')->toContain('Spec');expect(['a', 'b'])->not()->toContain('c');expect(['a', 'b'])->notToContain('c');expect('testing')->toContain('test', 'ing'); // Accepts multiple argsexpect(['tdd', 'test'])->not()->toContain('bdd', 'spec');expect(['tdd', 'test'])->notToContain('bdd', 'spec');expect(['name' => 'pho'])->toHaveKey('name');expect(['name' => 'pho'])->not()->toHaveKey('id');expect(['name' => 'pho'])->notToHaveKey('id');
Pattern Matching
expect('tdd')->toMatch('/\w[D]{2}/i');expect('pho')->not()->toMatch('/\d+/');expect('pho')->notToMatch('/\d+/');expect('username')->toStartWith('user');expect('spec')->not()->toStartWith('test');expect('spec')->notToStartWith('test');expect('username')->toEndWith('name');expect('spec')->not()->toEndWith('s');expect('spec')->notToEndtWith('s');
Numeric Matching
expect(2)->toBeGreaterThan(1);expect(2)->not()->toBeGreaterThan(2);expect(1)->notToBeGreaterThan(2);expect(2)->toBeAbove(1); // Alias for toBeGreaterThanexpect(2)->not()->toBeAbove(2);expect(1)->notToBeAbove(2);expect(1)->toBeLessThan(2);expect(1)->not()->toBeLessThan(1);expect(2)->notToBeLessThan(1);expect(1)->toBeBelow(2); // Alias for toBeLessThanexpect(1)->not()->toBeBelow(1);expect(2)->notToBeBelow(1);expect(1)->toBeWithin(1, 10); // Inclusiveexpect(-2)->not()->toBeWithin(-1, 0);expect(-2)->notToBeWithin(-1, 0);
Print Matching
$callable = function() { echo 'test'};expect($callable)->toPrint('test');expect($callable)->not()->toPrint('testing');expect($callable)->notToPrint('testing');
Exception Matching
$callable = function() { throw new Custom\Exception('error!');};expect($callable)->toThrow('Custom\Exception');expect($callable)->not()->toThrow('\ErrorException');expect($callable)->notToThrow('\ErrorException');
Custom Matchers
Custom matchers can be added by creating a class that implements pho\Expectation\Matcher\MatcherInterface and registering the matcher with pho\Expectation\Expectation::addMatcher(). Below is an example of a basic matcher:
namespace example;use pho\Expectation\Matcher\MatcherInterface;class ExampleMatcher implements MatcherInterface{ protected $expectedValue; public function __construct($expectedValue) { $this->expectedValue = $expectedValue; } public function match($actualValue) { return ($actualValue === $this->expectedValue); } public function getFailureMessage($negated = false) { if (!$negated) { return "Expected value to be {$this->expectedValue}"; } else { return "Expected value not to be {$this->expectedValue}"; } }}
Registering it:
use pho\Expectation\Expectation;// Register the matcherExpectation::addMatcher('toHaveValue', '\example\ExampleMatcher');
And that's it! You would now have access to the following:
expect($actual)->toHaveValue($expected);expect($actual)->not()->toHaveValue($expected);expect($actual)->notToHaveValue($expected);
Reporters
dot (default)
$ pho --reporter dot exampleSpec.php.FIFailures:"A suite can have specs that fail" FAILED/Users/danielstjules/Desktop/exampleSpec.php:9Expected false not to be falseFinished in 0.00125 seconds3 specs, 1 failure, 1 incomplete
spec
$ pho --reporter spec exampleSpec.phpA suite contains specs with expectations can have specs that fail can have incomplete specsFailures:"A suite can have specs that fail" FAILED/Users/danielstjules/Desktop/exampleSpec.php:9Expected false not to be falseFinished in 0.0012 seconds3 specs, 1 failure, 1 incomplete
list
$ pho --reporter list exampleSpec.phpA suite contains specs with expectationsA suite can have specs that failA suite can have incomplete specsFailures:"A suite can have specs that fail" FAILED/Users/danielstjules/Desktop/exampleSpec.php:9Expected false not to be falseFinished in 0.0012 seconds3 specs, 1 failure, 1 incomplete
Mocking
Pho doesn't currently provide mocks/stubs out of the box. Instead, it's suggested that a mocking framework such as prophecy or mockery be used.
Note: Tests can be failed from a test hook. If you need to check mock object expectations after running a spec, you can do so from an afterEach hook.
describe('A suite', function() { afterEach(function() { Mockery::close(); }); it('should check mock object expectations', function() { $mock = Mockery::mock('simplemock'); $mock->shouldReceive('foo')->with(5)->once()->andReturn(10); expect($mock->foo(5))->toBe(10); });});
Namespace
If you'd rather not have pho use the global namespace for its functions, you can set the --namespace flag to force it to only use the pho namespace. This will be a nicer alternative in PHP 5.6 with https://wiki.php-/rfc/use_function
pho\describe('A suite', function() { pho\it('contains specs with expectations', function() { pho\expect(true)->toBe(true); }); pho\it('can have specs that fail', function() { pho\expect(false)->not()->toBe(false); });});
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~