atoum
Discovering the unknown
Brussels php meetup
July 2016
hosted in mvillage / BePark
MVillage
- ICT Business Center
- host 60 startup & small companies
- Friendly
- www.mvillage.be
BePark
- Scaleup
- Link between parking spot and car driver
- Remotely open gate of parking
- www.bepark.eu
atoum
Discovering the unknown
...
and
get some news
Slides are available online ;)
http://slides.com/grummfy/atoum-brussels-php-meetup-july-2016
https://joind.in/talk/55c0bb
- @Grummfy
- Open source contributor
- Doc master of atoum
atoum?
name of an Egyptian god
but we will speak about unit test ;)
atoum
A simple, modern and intuitive unit testing framework
- Tests isolation and parallelization
- better performances,
- avoid side-effects on tests,
- Full-featured mock engine,
- Adapters to fake PHP functions,
- Virtual streams to mock filesystem,
- And many more!
Unit test ?
Unit test
- See internet ;)
- start small, but start to do it
- focus on business criticity
- invoice total compute
- gate opening signal
- ...
- Debug
- can be faster than xdebug
atoum
Now focus on it
Installation & init
- Phar
- Composer
- git
- ...
- atoum -init
- vendor/bin/atoum -init
- bin/atoum -init
- ...
.atoum.php
.bootstrap.atoum.php
Config file
$ vendor/bin/atoum
...
load file /.../.atoum.php
load file /.../test/.atoum.php
- Config file = .atoum.php
- use -c fileName to load it
- can be overwritten from parent directory
- used for
- atoum's config
- extension loading
- reports
- desktop notification
- ...
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
->given($this->newTestedInstance())
->assert('Will return false')
->boolean($this->testedInstance->iMReturningABool())
->isFalse;
}
}
}
Let's explain it
- namespace A\B
- A\test\unit\B
- Test\unit\A\B
- A\B\test\units
- autodetect tested class => newTestedInstance()
- extends \atoum
- given, assert
- do nothing, but create a story
- boolean() => asserter
- isFalse => assertion
Dives into atoum
- unit testing
- installation
- config
- example
- some explanation
✓
✓
✓
✓
✓
Now, let see what's available!
Structural keywords
- $this->given(/*...*/);
- $this->let(/*...*/);
- $this->define(/*...*/);
- $this->if(/*...*/);
- $this->and(/*...*/);
- $this->when(/*...*/);
- $this->then(/*...*/);
=> Storytelling!
test is pleasant, readable, understandable
Asserters
- $this->array(/*...*/);
- $this->boolean(/*...*/);
- $this->class(/*...*/);
- $this->float(/*...*/);
- $this->integer(/*...*/);
- $this->string(/*...*/);
- $this->variable(/*...*/);
- $this->castToString(/*...*/);
- $this->castToArray(/*...*/);
- $this->dateInterval(/*...*/);
- $this->dateTime(/*...*/);
- $this->iterator(/*...*/);
- $this->resource(/*...*/);
- $this->sizeOf(/*...*/);
- $this->utf8String(/*...*/);
- $this->error(function () {/*...*/});
- $this->exception(function () {/*...*/});
- $this->output(function () {/*...*/});
- $this->mock(/*...*/);
- $this->stream(/*...*/);
Assertions
->isEqualTo(/*...*/);
->isNotEqualTo(/*...*/);
->isIdenticalTo(/*...*/);
->isNotIdenticalTo(/*...*/);
->isGreaterThan(/*...*/);
->isLessThan(/*...*/);
->isGreaterThanOrEqualTo(/*...*/);
->isLessThanOrEqualTo(/*...*/);
->{‘==’}(/*...*/);
->{‘!=’}(/*...*/);
->{‘===‘}(/*...*/);
->{‘!==‘}(/*...*/);
->{‘>’}(/*...*/);
->{‘<‘}(/*...*/);
->{‘>=‘}(/*...*/);
->{‘<=‘}(/*...*/);
Assertion chain the asserter
Assertions ...
- ->isNull;
- ->isNotNull;
- ->isTrue;
- ->isNotTrue;
- ->isFalse;
- ->isNotFalse;
- ->isCallable;
- ->isNotCallable;
- ->isReferenceTo(/*...*/);
- ->isNearlyEqualTo(/*...*/);
can be use with or without "()"
some rely on specific asserter
extension give some more
Assertions aliases
-
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 or bootstrap
Tested class helpers
- $this->newTestedInstance
- $this->newTestedInstance(/**/)
- $this->testedInstance
- $this->isTestedInstance
Mock
-
not empty by default!
-
keep the feature in the mock
-
-
mock everything : interface, class, abstract, ...
-
Several ways to obtain a mock
-
new \mock\foo\Bar($at, $oum)
-
$this->newMockInstance('\foo\Bar', null, null, [$at, oum])
-
...
-
Mock - code is speaking
<?php
class klass {
public function __construct(at $at, oum $oum) {/*...*/}
public function foo($arg) {/*...*/}
}
$mock = new \mock\klass($at, $oum);
// the created mock will look like if we write this
namespace mock {
class klass extends \klass {
public function __construct(at $at, oum $oum) {
parent::__construct($at, $oum);
}
public function foo($arg) {
parent::foo($arg);
}
}
normal mock
Mock - code is speaking
<?php
class klass {
public function __construct(at $at, oum $oum) {/*...*/}
public function foo($arg) {/*...*/}
}
$this->mockGenerator->shuntParentClassCalls();
$mock = new \mock\klass($at, $oum);
// the created mock will look like if we write this
namespace mock {
class klass extends \klass {
public function __construct(at $at, oum $oum) {} // no code to parent call
public function foo($arg) {}
}
}
shuntParent mock
Mock - code is speaking
<?php
class klass {
public function __construct(at $at, oum $oum) {/*...*/}
public function foo($arg) {/*...*/}
}
$this->mockGenerator->orphanize('__construct'); // can be any method we want empty
$mock = new \mock\klass($at, $oum);
// the created mock will look like if we write this
namespace mock {
class klass extends \klass {
public function __construct() {} // no args and no code
public function foo($arg) {
parent::foo($arg);
}
} }
orphanize mock
Want a PHPUnit mock?
$this->mockGenerator->allIsInterface();
Mock definition
-
$this->calling($mock)->foo = ‘value’; // or a callable
-
$this->calling($mock)->foo[3] = 'value on third call'
-
$this->mock($mock)->call(‘foo’)->once;
-
->twice
-
->exactly(3) <=> ->{3}
-
->atLeast(2)
-
-
Read the doc, it will have example and many more
Mock assertion
-
$this->mock($mock)->call(‘foo’)
-
->withArguments(/*...*/);
-
->withIdenticalArguments(/*...*/);
-
->withAtLeastArguments(/*...*/);
-
->withAtLeastIdenticalArguments(/*...*/);
-
->withAnyArguments;
-
->withoutAnyArgument;
-
->before, ->after
-
$this->mock($mock)->call(‘foo’)->before(
$this->mock($mock)->call(‘bar’)->after(
$this->mock($mock)->call(‘baz’)->once
)—>once
)->once;
Mock native function
<?php
// ...
$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();
Inject mock in test method
<?php
// ...
public function testFoo(\Foo\MyInterface $fooInterfaceMock) {
$this->mock($fooInterfaceMock);
}
If you need a mock inside your test method...
... but if it required args, use a data provider!
Data provider
use a method to feed the test
<?php
// ...
/**
* @dataProvider feedFoo
*/
public function testFoo($a, $b) {
$this->integer($a)->isEqualTo($b);
}
public function feedFoo() {
return [42, 42];
}
Data provider
<?php
// ...
public function testFoo($a, $b) {
$this->integer($a)->isEqualTo($b);
}
// or simply, no annotation with [nameOfTestMethod]DataProvider
public function testFooDataProvider() {
// we can also return an array => 3 elements = 3 test call
return [
[42, 42],
[1, 1],
[1, '1'], // beecause isEquatlTo, not isIdenticalTo
];
}
Running the test ;)
-
atoum -d [directory of test]
-
atoum -f [file with the test]
-
-ns A\B
-
-m A\B\C::testFoo
-
-t myTag
-
--loop
-
--debug
-
...
<?php
// in your workspace/.atoum.php
// loop mode enabled by default for your test \o/
$script->enableLoopMode();
Execution test engine
- how the test are executed
- in the config => class level => test level
- inline ~ PHPUnit
- a, then b, then c
- 1 process
- isolate
- a, then b, then c
- 3 processes
-
concurrent -> atoum
- a, b, c
- 3 processes
Reports
-
from config file
-
required xdebug for coverage
<?php
// .atoum.php
$script->addDefaultReport();
$xunitWriter = new atoum\writers\file('atoum.xunit.xml');
$xunitReport = new atoum\reports\asynchronous\xunit(); // real xunit report
$xunitReport->addWriter($xunitWriter);
$runner->addReport($xunitReport);
$cloverWriter = new atoum\writers\file(‘atoum.clover.xml’);
$cloverReport = new atoum\reports\asynchronous\clover();
$cloverReport->addWriter($cloverWriter);
$runner->addReport($cloverReport);
cli realtime & light,
cover, tap, coveralls, sonar, html, nyancat, logo, santa, treemap, ...
Reports - Coverage
<?php
// .atoum.php
use mageekguy\atoum\reports;
use mageekguy\atoum\reports\coverage;
use mageekguy\atoum\writers\std;
$extension = new reports\extension($script);
$extension->addToRunner($runner);
$script->addDefaultReport();
$coverage = new coverage\html();
$coverage->addWriter(new std\out());
$coverage->setOutPutDirectory(__DIR__ . '/test/reports/coverage');
$script->enableBranchAndPathCoverage(); // <- lower your coverage score ;)
$runner->addReport($coverage);
Reports - Coverage
Without path coverage
With path coverage
Reports - telemetry
<?php
// .atoum.php
use mageekguy\atoum\reports;
use mageekguy\atoum\reports\telemetry;
use mageekguy\atoum\writers\std;
$script->addDefaultReport();
$telemetry = new telemetry();
$telemetry->addWriter(new std\out());
$runner->addReport($telemetry);
// $telemetry->readProjectNameFromComposerJson(__DIR__ . '/composer.json');
// $telemetry->setProjectName('my/project');
$telemetry->sendAnonymousProjectName();
composer require --dev atoum/reports-extension
Report - Telemetry
IDE integration
-
vim
-
netbeans
-
phpstorm
-
atom
-
sublime text
Framework integration
-
Symfony 1.x, 2.x (3.x ?)
-
ezPublish
-
Zend Framework 2.x
-
honestly you don't really need it
Extra integration
-
phing
-
robo
-
jenkins / hudson, sonar (standard xUnit)
-
codacy, travis
-
...
Some extra
Can't speak about everything
Test hook
- setUp
- tearDown
- beforeTestMethod
- afterTestMethod
- /!\ change with execution engine /!\
Annotation
- @engine
- @tag
- @php
- @...
Extensions
- deprecated: list deprecated error
- report: create report (tap, html, telemetry, ...)
- ruler: filter executed test with \Hoa\Ruler
- blackfire: use blackfire to make performance test
- visibility: easier test protected & private
- autoloop: detect file change and relaunch the test
- ...
Compare with PHPUnit ?
Start with this workshop on TDD:
Some extra
but not totally ready...
Praspel
- From a PHD Thesis of Ivan Enderlin (Hywan)
- Automatic test data generation
- basic
- domain model
- ~ faker but with a bit more
- Automatic test suite generation
- code
- annotation
- ~ contract
<?php
/**
* @requires t : array([to integer()],boundinteger(5,10)) and
* i : integer();
* @ensures \result : boolean();
*/
public function find($t, $i)
{
return false;
}
PHPUnit bridge
- Execute PHPUnit test with atoum ;)
- Works but need more test
- Contact us to find more
Want to go further?
- Check the documentation
- Check the website
- Speak with us on IRC or Gitter
- ##atoum on freenode
- https://gitter.im/atoum/atoum
- Youtube "atoum" channel
Project informations
- 2 release managers
- clear release documentation
- 1 doc master
- external contributor from everywhere
- over last year: 15 contributors
- for the doc: 9 contributors
- new features or simplifications
- lead have already change without issue
"Roadmap"
- documentation
- mock & configuration
- more example
- inside atoum
- improvements for 7.1
- infrastructure (phar builder, ...)
- 3.X
- split component
- lighter core
- remove PHP 5.3 adaptation
- package (core + extension)
Questions?
atoum, discovering the unknown and get some news
By Jonathan Van Belle
atoum, discovering the unknown and get some news
- 2,535