37
Test in Action – Week 3 Stub / Mock Hubert Chan

Test in action week 3

Embed Size (px)

Citation preview

Page 1: Test in action   week 3

Test in Action – Week 3Stub / Mock

Hubert Chan

Page 2: Test in action   week 3

Back to Basics

• Wikipedia said unit testing is– A software verification and validation method in

which a programmer tests if individual units of source code are fit for use.

Page 3: Test in action   week 3

Dependency in the Test

• System Under Test (SUT) Dependency– SUT need another module to perform its task• Handler need a $dbmodel to perform query• $handler = new Handler($dbmodel);

Page 4: Test in action   week 3

Real Object is Hard to Test

• Real object– Supplies non-deterministic results • (e.g. the current temperature)

– Difficult to create or reproduce (e.g. network fail)– Slow (e.g. database)– Does not yet exist or may change behavior

Page 5: Test in action   week 3

Substitute Dependency

• Using Fake– Fake is anything not real

• Fake techniques– Mock object– Stub object– Fake object

Page 6: Test in action   week 3

Stub Objects

• Stub Objects– Stubs provide canned answers to calls made

during the test– Usually not responding at all to anything outside

what's programmed in for the test

Page 7: Test in action   week 3

Fake Objects

• Fake Objects– Fake objects have working implementations– Usually take some shortcut, which makes them

not suitable for production. – Example• An in-memory file system• An in-memory registry manager

Page 8: Test in action   week 3

Example: AlertSystem

Page 9: Test in action   week 3

Example: AlertSystem

• Component Overview– AlertSystem• Send notification to the targets, such as e-mail or SMS

notifier.

– NotifierInterface instance• Send actually notification to the target

– NotifierTargetProviderInterface instance• Provide a target array to send notification

Page 10: Test in action   week 3

AlertSystem Test

• Dependency Analysis– Collaboration Classes

• NotifierInterface• NotifierTargetProviderInterface

• Tests should– Only focus on AlertSystem– Fake the dependency

Page 11: Test in action   week 3

Make a Fake?

• FileNotifyTargetProviderclass FileNotifyTargetProvider implements NotifyTargetProviderInterface {

function __construct($filename) { $this->_filename = $filename; }

public function get_targets() { $targets = array(); $handle = @fopen($this->_filename, "r");

while (($target = fgets($handle)) !== FALSE) { $targets[] = $target; } return $targets; }}

Page 12: Test in action   week 3

Make a Stub?

• StubNotifierclass StubNotifier implements NotifierInterface { public function notify($target, $content) { }}

Page 13: Test in action   week 3

Example: Test Code

• Test Codeclass AlertSystemTest extends PHPUnit_Framework_TestCase { public function test_sendNotify_FakeNStub_NoException() { $target_file = __DIR__ . '/data/targets.txt';

$notifier = new StubNotifier(); $provider = new FileNotifyTargetProvider($target_file);

$alert_system = new AlertSystem( $notifier, $provider ); $alert_system->send_notify('Alert!!'); }}

Page 14: Test in action   week 3

Manual Fake or Stub

• Pros– Fake or stub can be used as library– Shared implementation

• Cons– Different scenarios need different implementation– Testing class explosion

Page 15: Test in action   week 3

Manual Stub or Fake

• Cons– Need extra efforts/logics for behaviors• Setting return value• Setting thrown exception

– Hard to validate• Calling sequence of functions• Passing arguments for functions• The method should be called

Page 16: Test in action   week 3

Test Doubles

• Using manual stub and mock– Is $notifier->notify() be called?– Does the target of $notifier->notify equal

to expect target?– Does the content of $notifier->notify

equal to expect target?

Page 17: Test in action   week 3

Mock objects

• Mock Objects– Mocks are objects pre-programmed with expectations, which form a specification of the calls they are expected to receive.

Page 18: Test in action   week 3

Mock Object Example

• Mock Object Example public function test_sendNotify_Mock_NoException() {

$notify_content = 'fake_content'; $mock_notifier = $this->getMock('NotifierInterface'); $mock_notifier->expects($this->once()) ->method('notify') ->with($this->anything(), $this->equalTo($notify_content));

$alert_system = new AlertSystem( $mock_notifier, $stub_provider ); $alert_system->send_notify('Alert!!'); }

Page 19: Test in action   week 3

Mock Object Example

• Mock Object Verification– $notifier->notify is called only once– $notifier->notify 1st parameter can be

anything– $notifier->notify 2nd parameter should be

equal to $notify_content

Page 20: Test in action   week 3

Using PHPUnit Stub

• Return Value public function test_sendNotify_Mock_NoException() {

$stub_provider = $this->getMock('NotifyTargetProviderInterface'); $targets = array('hubert'); $stub_provider->expects($this->any()) ->method('get_targets') ->will($this->returnValue($targets));

$alert_system = new AlertSystem( $mock_notifier, $stub_provider ); $alert_system->send_notify('Alert!!'); }

Page 21: Test in action   week 3

Using PHPUnit Stub

• Return one of the argumentspublic function testReturnArgumentStub() { // Create a stub for the SomeClass class. $stub = $this->getMock('SomeClass');

// Configure the stub. $stub->expects($this->any()) ->method('doSomething') ->will($this->returnArgument(0));

// $stub->doSomething('foo') returns 'foo' $this->assertEquals('foo', $stub->doSomething('foo'));

// $stub->doSomething('bar') returns 'bar' $this->assertEquals('bar', $stub->doSomething('bar'));}

Page 22: Test in action   week 3

Using PHPUnit Stub

• Return a value from a callback– Useful for “out” parameter

public function testReturnCallbackStub() { // Create a stub for the SomeClass class. $stub = $this->getMock('SomeClass');

// Configure the stub. $stub->expects($this->any()) ->method('doSomething') ->will($this->returnCallback('str_rot13'));

// $stub->doSomething($argument) returns str_rot13($argument) $this->assertEquals('fbzrguvat', $stub->doSomething('something'));}

Page 23: Test in action   week 3

PHPUnit Stub

• Throw Exceptionpublic function testThrowExceptionStub() { // Create a stub for the SomeClass class. $stub = $this->getMock('SomeClass');

// Configure the stub. $stub->expects($this->any()) ->method('doSomething') ->will($this->throwException(new Exception));

// $stub->doSomething() throws Exception $stub->doSomething();}

Page 24: Test in action   week 3

Misconception About Mocks

• Mocks are just Stubs– Mock is behavior verification• Is the function called?• Is the parameter passed correctly?

– Stub is used for state verification– References• Mocks Aren’t Stubs

– http://martinfowler.com/articles/mocksArentStubs.html

Page 25: Test in action   week 3

Is it HARD to use stub/mock?

• Untestable code– Make Your Own Dependencies– Heavy Duty Constructors– Depend on Concrete Classes– Use Statics – Using Singleton Everywhere– Look for Everything You Need

Page 26: Test in action   week 3

Dependency Injection

• Without dependency injection– Use “new” operator inside your class– Make mock object injection difficult

• Dependency Injection– Inject dependencies through injectors– Injection method• Constructor• Setter• Dependency Injection Framework

Page 27: Test in action   week 3

AlertSystem again

• AlertSystem (Hard to Test)– Concrete classes– Make Your Own Dependenciesclass AlertSystem {

public function send_notify($content) { $target_provider = new FileNotifyTargetProvider('data.txt'); $notifier = new EmailNotifier('user', 'pass', $port);

$notify_targets = $target_provider->get_targets(); foreach ($notify_targets as $target) { $notifier->notify($target, $content); } }}

Page 28: Test in action   week 3

AlertSystem constructor injection

• Constructor Injectionclass AlertSystem {

protected $_notifier; protected $_target_provider;

function __construct ( NotifierInterface $notifier, NotifyTargetProviderInterface $provider ) { $this->_notifier = $notifier; $this->_target_provider = $provider; }

public function send_notify($content) { $notify_targets = $this->_target_provider->get_targets(); foreach ($notify_targets as $target) { $this->_notifier->notify($target, $content); } }}

Page 29: Test in action   week 3

Not only for testing

• Single Responsibility Principle– Should alert system holds notifier and data

provider logic?– Ex. Should the class read registry directly?

• Dependency Inversion Principle• Open Close Principle

Page 30: Test in action   week 3

The Law of Demeter

• Definition– Each unit should have only limited knowledge

about other units: only units "closely" related to the current unit.

– Each unit should only talk to its friends; don't talk to strangers.

– Only talk to your immediate friends.

Page 31: Test in action   week 3

Violation of the Law

• How do you test for?– Mock for mock object, for another mock object– Like Looking for a Needle in the Haystackclass Monitor { SparkPlug sparkPlug; Monitor(Context context) { this.sparkPlug = context. getCar().getEngine(). getPiston().getSparkPlug(); }}

Page 32: Test in action   week 3

Law of Demeter - Explicit

• Explicit– We should not need to know the details of

collaboratorsclass Mechanic { Engine engine; Mechanic(Engine engine) { this.engine = engine; } }

Page 33: Test in action   week 3

Guide – Write Testable Code

• Bad smell for non-testable Code– Constructor does real work– Digging into collaborators– Brittle Global State & Singletons– Class Does Too Much

• References– Guide – Write Testable Code– http://

misko.hevery.com/attachments/Guide-Writing%20Testable%20Code.pdf

Page 34: Test in action   week 3

Conclusion

• Writing good unit tests is hard• Good OO design brings good testability• Using stub/mock from mocking framework

Page 35: Test in action   week 3

Q & A

Page 36: Test in action   week 3

PHPUnit Log File

• Junit Format<testsuites> <testsuite name="AlertSystemTest" file="/usr/home/hubert/own/work/trend/process/CI/php/tests/AlertSystemTest.php" tests="2" assertions="0" failures="1" errors="0" time="0.031119"> <testcase name="test_sendNotify_FakeNStub_NoException" class="AlertSystemTest" file="/usr/home/hubert/own/work/trend/process/CI/php/tests/AlertSystemTest.php" line="8" assertions="0" time="0.006881"/> </testsuite></testsuites>

Page 37: Test in action   week 3

PHPUnit Coverage

• PHPUnit Coverage– Need Xdebug– HTML format