atoum, introduction and discovering
PHPLimburg meetup
2018-11-21
First
slides will be available after the talk ;)
Who am I?
Grummfy
Jonathan Van Belle
@Grummfy
@Grummfy@mamot.fr
me@grummfy.be
github.com/grummfy
Monizze
Open source contributor (dev & doc, idea): atoum, hoa, ...
Unit test in PHP
PHPUnit
atoum
SimpleTest
(codeception)
Peridot
StoryPlayer
Lime
Kahlan
atoum
A simple, modern and intuitive unit testing framework
- Tests isolation and parallelization
- better performances,
- avoid side-effects on tests,
- Full-featured mock engine,
- Virtual streams to mock filesystem,
- And many more!
First test
<?php
namespace A\B {
class C {
public function iMReturningABool() {
return false;
}}}
namespace A\test\unit\B {
class C extends \atoum {
public function testIMReturningABool() {
$this->newTestedInstance();
$this->boolean($this->testedInstance->iMReturningABool())->isFalse;
// or
$this
->given($this->newTestedInstance())
->assert('Will return false')
->boolean($this->testedInstance->iMReturningABool())
->isFalse;
}}}
Structural keywords
- $this->given(/*...*/);
- $this->let(/*...*/);
- $this->define(/*...*/);
- $this->if(/*...*/);
- $this->and(/*...*/);
- $this->when(/*...*/);
- $this->then(/*...*/);
=> Storytelling style!
test is pleasant, readable, understandable
- .atoum.php
- run once
- .bootstrap.atoum.php
- run before each test run
- inherited from parent directory
- .autoloader.atoum.php
- useless if you use composer
- overwritten by cli args
Configuration
Configuration: debug
If you got some troubles, use ++verbose
vendor/bin/atoum ++verbose
Using ++verbose CLI argument…
Using '/var/www/src/.atoum.php' configuration file…
Using '/var/www/src/vendor/autoload.php' autoloader file…
Using '/tmp/6401815c2f69923a1d74eb4797409729.atoum.cache' autoloader cache file…
Using '/var/www/src/tests/units/App/Http/Api/Transformers/OpenGate.php' test file…
Using '/var/www/src/tests/units/App/Http/Middleware/Paginate.php' test file…
Using '/var/www/src/tests/units/App/Models/Database/Holiday.php' test file…
Using '/var/www/src/tests/units/App/Models/Database/Parking/Booking.php' test file…
Using '/var/www/src/tests/units/App/Models/Database/Parking/Parking.php' test file…
Using '/var/www/src/tests/units/App/Models/Projections/Profile/CreditRequest.php' test file…
> atoum path: /var/www/src/vendor/atoum/atoum/bin/atoum
> atoum version: 3.1.1
> PHP path: /usr/local/bin/php
> PHP version:
=> PHP 7.1.9 (cli) (built: Sep 8 2017 02:55:21) ( NTS )
=> Copyright (c) 1997-2017 The PHP Group
=> Zend Engine v3.1.0, Copyright (c) 1998-2017 Zend Technologies
=> with Xdebug v2.5.5, Copyright (c) 2002-2017, by Derick Rethans
Configuration: example
use mageekguy\atoum\reports;
use mageekguy\atoum\reports\coverage;
use mageekguy\atoum\writers\std;
use mageekguy\atoum\report\fields\runner\result\logo;
// path & branch coverage for better view on the coverage
$script->enableBranchAndPathCoverage();
// automatically run test in this directory
$runner->addTestsFromDirectory(__DIR__ . '/tests/units');
// add report extension
$report = $script->addDefaultReport();
$extension = new reports\extension($script); $extension->addToRunner($runner);
// html report & stdout report
$coverage = new coverage\html();
$coverage->addWriter(new std\out());
$coverage->setOutPutDirectory(__DIR__ . '/tests/reports/unit/');
$runner->addReport($coverage);
// telemetry platform report
$telemetry = new reports\telemetry();
$telemetry->addWriter(new std\out());
$telemetry->readProjectNameFromComposerJson(__DIR__ . '/composer.json');
$telemetry->sendAnonymousProjectName(); $runner->addReport($telemetry);
// for the fun, a nice cli logo
$report->addField(new logo());
Asserters
<?php
namespace A\B {
class C {
public function iMReturningABool() {
return false;
}
}
}
namespace A\test\unit\B {
class C extends \atoum {
public function testIMReturningABool() {
$this
->given($this->newTestedInstance())
->assert('Will return false')
->boolean($this->testedInstance->iMReturningABool())
->isFalse;
}
}
}
Asserters: tree
|-- error
|-- mock
|-- stream
`-- variable
|-- array
| `-- castToArray
|-- boolean
|-- class
| `-- testedClass
|-- integer
| |-- float
| `-- sizeOf
...
|-- object
| |-- dateInterval
| |-- dateTime
| | `-- mysqlDateTime
| |-- exception
| `-- iterator
| `-- generator
|-- resource
`-- string
|-- castToString
|-- hash
|-- output
`-- utf8String
Asserters: assertion
// () or no ?
$this->boolean(true)->isTrue;
$this->boolean(true)->isTrue();
$this->boolean(true)->isTrue('PHP is going crazy!');
// some sugar
$a = ['foo' => 42, 'bar' => '1337'];
$this
->array($a)
->integer['foo']->isEqualTo(42)
->string['bar']->isEqualTo('1337');
Asserters: Aliases
$a = 42;
$this->integer($a)->isIdenticalTo(42);
$this->integer($a)->{'==='}(42);
Asserters: Aliases, custom
- from([asserter])->use([assertion])->as([assertion alias])
- $this->[asserter]->[assertion alias]
- $this->from(‘string’)->use(‘isEqualTo’)->as(‘is’);
- $this->string($atoum)->is(‘atoum’);
- $this->from(‘float’)->use(‘isNearlyEqualTo’)->as(‘~=’);
- $this->float(1 / 3)->{‘~=‘}(0.3, 0.1);
- constructor, setUp, test method, test class, bootstrap or config
Exception handling
<?php
// ...
$this
->exception(
function() {
$this->testedInstance->doOneThing('wrongParameter');
}
)->hasMessage('My foo exception message')
->isInstanceOf(FooException::class)
;
anonymous function are also used for output
Mock
-
Mock & data set are the most important part of your tests
-
Autoloading
-
Natural language declaration
-
Mock everything
Mock
-
Warning
-
Not empty by default!
-
=> Keep the feature in the mock
-
-
Mock everything
-
interface,
-
class,
-
abstract,
-
function
-
....
-
Mock: creation
$mock = new \mock\Foo\Bar($baz, $args);
// same as, but without IDE complains
$mock = $this->newMockInstance(\Foo\Bar::class, null, null, [$baz, $arg]);
$this->mockGenerator->generate(
'\Vendor\Project\AbstractClass',
'\MyMock',
'MockClass');
$mock = new \myMock\MockClass;
// same as
$mock = $this->newMockInstance(
'\Vendor\Project\AbstractClass',
'\MyMock',
'MockClass');
Mock: class generated
<?php
class Bar {
public function __construct(at $a, oum $b) {/*...*/}
public function foo($arg) {/*...*/}
}
$mock = new \mock\Bar($a, $b);
// the created mock will look like if we write this
namespace mock {
class Bar extends \Bar {
public function __construct(at $a, oum $b) {
parent::__construct($a, $b);
}
public function foo($arg) {
parent::foo($arg);
}
}
Mock: empty behaviour
shuntParent mock
<?php
class Bar {
public function __construct(at $a, oum $b) {/*...*/}
public function foo($arg) {/*...*/}
}
$this->mockGenerator->shuntParentClassCalls();
$mock = new \mock\Bar($a, $b);
// the created mock will look like if we write this
namespace mock {
class Bar extends \Bar {
public function __construct(at $a, oum $b) {} // no code to parent call
public function foo($arg) {}
}
}
Mock: empty method
orphanize mock
<?php
class Bar {
public function __construct(at $a, oum $b) {/*...*/}
public function foo($arg) {/*...*/}
}
$this->mockGenerator->orphanize('__construct'); // can be any method we want empty
$mock = new \mock\Bar($a, $b);
// the created mock will look like if we write this
namespace mock {
class Bar extends \Bar {
public function __construct() {} // no args and no code
public function foo($arg) {
parent::foo($arg);
}
} }
Mock: change behaviour
-
$this->calling($mock)->foo = ‘value’; // or a callable
-
$this->calling($mock)->foo[3] = 'value on third call';
-
$this->calling($mock)->foo->throw = new Exception();
-
$this->mock($mock)->call(‘foo’)->once;
-
->twice
-
->exactly(3) <=> ->{3} <=> ->thrice
-
->atLeast(2)
-
-
Read the doc, it will have examples and many more
Mock: Interface
new \mock\Countable();
Mock: Constant
$this->constant->PHP_VERSION_ID = 60606;
$this->constant->PHP_VERSION = '6.6.6';
$this->string(PHP_VERSION)->isEqualTo('6.6.6');
Mock: native function
$this
->assert('the file exist')
->given($this->newTestedInstance())
->if($this->function->file_exists = true)
->then
->object($this->testedInstance->loadConfigFile())
->isTestedInstance()
->function('file_exists')->wasCalled()->once();
Mock: injected in test method
If you need a mock inside your test method...
... but if it required args, use a data provider!
<?php
// ...
public function testFoo(\Foo\MyInterface $fooInterfaceMock) {
$this->mock($fooInterfaceMock);
}
Reporting
- From configuration file
- Reports extension is nice
- fun (santa, nyan)
- ...
- /!\ xunit report is the standard one...
- coverage is using xdebug only (for now)
Reporting: coverage
Without path coverage
With path coverage
Reporting: telemetry
Some key difference with PHPUnit 1/2
- Testing variable types
- Mocking system
- Use closure to test outputs, exceptions, …
- Multiple execution engine
- Concurrent run of test cases
- Fully isolated test cases
- Speed
Some key difference with PHPUnit 2/2
- Fluent interface
- no @depends (injecting result of another test inside the following)
- forced namespace & classname
- far less permissive by default
- syntaxic sugar (array, given, if, then, newTestedInstance, ...)
- Want a PHPUnit like mock? $this->mockGenerator->allIsInterface();
- (a lot more assertion)
- extension PHPUnit bridge
Integration in tools
- IDE: netbeans, PHPStorm, sublime text, vim, atom, ...
- Task: Phing, Robo, GrumPHP, ...
- CI: jenkins, circleCI, ContinuousPHP, travis, gitlab, ...
- Frameworks: Symfony, Zend 2, ezPublish, ...
Extensions
- Extension mechanism
- Installation
- autoloaded with composer
- using configuration file
- http://extensions.atoum.org/
Extensions
- visibility
- json-schema
- ruler
- ide-helper
- autoloop
Documentation
- Try to be an help
- Use rusty for the validity of the example
- Should normally be up to date or have an issue in github
Tips & tricks
- -ncc: remove code coverage
- --loop: use native loop mode
- -ns A\B: take only class in namespace A\B
- --debug: $this->dump($data)
Still a lot more... but no more time ;)
- Data provider
- Annotation
- Test hook
- Differences between the execution engines
- ...
Questions?
Thanks
Introducing and discovering of atoum, PHPLimburg meetup
By Jonathan Van Belle
Introducing and discovering of atoum, PHPLimburg meetup
In the PHP world, when you speak about unit testing, everybody will know PHPUnit. But there is some alternative that gets a lot of attention lately: atoum. During this talk, we try to understand some of it's specificities and why you should use it. You will discover the simplicity and the accuracy inside unit testing.
- 1,996