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

  • @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

  • behat

  • 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

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:

https://github.com/vonglasow/workshop-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?

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,356