A talk by @thorstenfrommen
What you might be able to test:
Also component testing, or module testing.
The process of evaluating a system or component to determine whether the system of a given development phase satisfies the conditions imposed at the start of that phase. IEEE Std 610.12-1990
class Oracle {
public function get_answer() {
return '42';
}
}
class OracleTest extends PHPUnit_Framework_TestCase {
public function test_get_answer() {
$testee = new Oracle();
$this->assertSame( '42', $testee->get_answer() );
}
}
var test = require( 'tape' );
var Oracle = require( './path/to/oracle' );
test( function( assert ) {
var testee = new Oracle();
assert.equal( testee.getAnswer(), '42' );
assert.end();
} );
class FortuneTeller {
private $oracle;
public function __construct( Oracle $oracle ) {
$this->oracle = $oracle;
}
public function get_answer( $money = 0 ) {
if ( (int) $money < 5 ) {
return '';
}
return $this->oracle->get_answer();
}
}
class FortuneTellerTest extends PHPUnit_Framework_TestCase {
public function test_get_answer() {
$oracle = $this->getMockBuilder( 'Oracle' )
->getMock();
$oracle->method( 'get_answer' )
->willReturn( 'answer' );
$testee = new FortuneTeller( $oracle );
$this->assertSame( '', $testee->get_answer( 0 ) );
$this->assertSame( 'answer', $testee->get_answer( 5 ) );
// ...use a data provider for this test.
}
}
class FortuneTellerMockeryTest extends PHPUnit_Framework_TestCase {
public function test_get_answer() {
$oracle = Mockery::mock( 'Oracle', [
'get_answer' => 'answer',
] );
$testee = new FortuneTeller( $oracle );
$this->assertSame( '', $testee->get_answer( 0 ) );
$this->assertSame( 'answer', $testee->get_answer( 5 ) );
// ...use a data provider for this test.
}
}
var test = require( 'tape' );
var sinon = require( 'sinon' );
var FortuneTeller = require( './path/to/fortune-teller' );
test( function( assert ) {
var testee = new FortuneTeller( {
getAnswer: sinon.stub().returns( 'answer' )
} );
assert.equal( testee.getAnswer( 0 ), '' );
assert.equal( testee.getAnswer( 5 ), 'answer' );
// ...
assert.end();
} );
class HookOracle {
public function get_answer() {
do_action( 'give_answer' );
return (string) apply_filters( 'the_answer', '42' );
}
}
class HookOracleBrainMonkeyTest extends PHPUnit_Framework_TestCase {
// Set up and tear down Brain Monkey...
public function test_get_answer() {
$testee = new HookOracle();
$this->assertSame( '42', $testee->get_answer() );
$this->assertSame( 1, did_action( 'give_answer' ) );
$this->assertTrue( Brain\Monkey::filters()->applied( 'the_answer' ) > 0 );
}
}
class HookOracleWP_MockTest extends PHPUnit_Framework_TestCase {
// Set up and tear down WP_Mock...
public function test_get_answer() {
$testee = new HookOracle();
WP_Mock::expectAction( 'give_answer' );
WP_Mock::onFilter( 'the_answer' )
->with( '42' )
->reply( '4815162342' );
$this->assertSame( '4815162342', $testee->get_answer() );
}
}
function maybeDoSomething() {
if ( ! checkSomething() ) {
return;
}
doSomething();
}
function maybeDoSomething() {
if ( ! checkSomething() ) {
return false;
}
doSomething();
return true;
}
But consider potential side effects (e.g., JavaScript event listeners).
class Checker {
public function check_data( $data ) {
if ( ! $data ) {
exit();
}
// ...
}
}
class Checker {
public function check_data( $data ) {
if ( ! $data ) {
$this->call_exit();
}
// ...
}
protected function call_exit() {
exit();
}
}
function processData( data ) {
data = prepareData( data );
sendData( data );
window.location.href = 'http://example.com';
}
function setLocation( url ) {
window.location.href = url;
}
function processData( data ) {
data = prepareData( data );
sendData( data );
setLocation( 'http://example.com' );
}
class Renderer {
public function render_formatted_data( $data ) {
$formatter = new Formatter();
echo $formatter->format( $data );
}
}
Here, constructor injection:
class Renderer {
private $formatter;
public function __construct( Formatter $formatter ) {
$this->formatter = $formatter;
}
public function render_formatted_data( $data ) {
echo $this->formatter->format( $data );
}
}
class Renderer {
private $formatter_factory;
public function __construct( FormatterFactory $formatter_factory ) {
$this->formatter_factory = $formatter_factory;
}
public function render_formatted_data( $data, $options ) {
$formatter = $this->formatter_factory->create( $options );
echo $formatter->format( $data );
}
}
function someFunction() {
if ( 42 === globalFoo ) {
// ...
}
// ...
}
function someFunction( localFoo ) {
if ( 42 === localFoo ) {
// ...
}
// ...
}
class Singleton {
private static $instance;
// protected/private __construct() and __clone(), ...
// ...and other code, which maybe manipulates the object's state...
public static function get_instance() {
if ( ! self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
}
reset()
Method
class Singleton {
// ...
public static function get_instance() {
if ( ! self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
public static function reset() {
self::$instance = null;
}
}
function isIdEven( someObject ) {
var id = someObject.getOtherObject().getId();
return ! ( id % 2 );
}
A method m of an object o should invoke only the methods of:
- o itself;
- any objects passed to m;
- any objects created/instantiated in m;
- any properties/members of o.
function isIdEven( someObject ) {
var id = someObject.getOtherObject().getId();
return ! ( id % 2 );
}
function filterByEvenId( someObject ) {
if ( isIdEven( someObject ) ) {
return someObject;
}
return undefined;
}
function isIdEven( otherObject ) {
var id = otherObject().getId();
return ! ( id % 2 );
}
function filterByEvenId( someObject ) {
if ( isIdEven( someObject.getOtherObject() ) ) {
return someObject;
}
return undefined;
}
class Renderer {
public function render_if_okay( $data ) {
if ( ThirdPartyChecker::is_okay( $data ) ) {
echo $data;
}
}
}
class Checker {
public function is_okay( $data ) {
return ThirdPartyChecker::is_okay( $data );
}
}
class Renderer {
private $checker;
public function __construct( Checker $checker ) {
$this->checker = $checker;
}
public function render_if_okay( $data ) {
if ( $this->checker->is_okay( $data ) ) {
echo $data;
}
}
}
function Renderer() {
var formatter;
this.setFormatter = function( f ) {
formatter = f;
};
this.getFormattedData = function( data ) { // <-- SUT
return formatter.format( data );
};
}
function Renderer( formatter ) {
this.getFormattedData = function( data ) {
return formatter.format( data );
};
}
function render_post_title_with_id( $post_id ) {
$post = get_post( $post_id );
if ( $post ) {
echo $post->post_title . '(' . $post_id . ')';
}
}
function render_post_title_with_id( WP_Post $post ) {
echo $post->post_title . '(' . $post->ID . ')';
}
eval
Expressions
function badlyDesignedFunction( data ) {
return eval( '42 === data' );
}
function okayDesignedFunction( data ) {
return 42 === data;
}
class Processor {
public function process( $data ) {
// 16 lines full of different checks, a lot of if—else, ...
// 23 lines full of data preparing, switch—case all over, ...
// 42 lines full of data processing...
// 8 lines full of post-processing, if—elseif—else again...
}
}
class Processor {
public function process( $data ) {
if ( ! $this->checker->check( $data ) ) {
return false;
}
$data = $this->prepare( $data );
// 42 lines full of data processing...
$this->post_processor->process( $data );
return true;
}
}