+ All Categories
Home > Technology > (PHPers Wrocław #5) How to write valuable unit test?

(PHPers Wrocław #5) How to write valuable unit test?

Date post: 12-Apr-2017
Category:
Upload: rst-software-masters
View: 302 times
Download: 2 times
Share this document with a friend
52
How to write valuable unit tests? Łukasz Wróbel Michał Kopacz
Transcript
Page 1: (PHPers Wrocław #5) How to write valuable unit test?

How to write valuable unit

tests?

Łukasz Wróbel Michał Kopacz

Page 2: (PHPers Wrocław #5) How to write valuable unit test?

Please save your questions until the end of presentation

Page 3: (PHPers Wrocław #5) How to write valuable unit test?
Page 4: (PHPers Wrocław #5) How to write valuable unit test?

75%CODE COVERAGE FOR UNIT TESTS

Page 5: (PHPers Wrocław #5) How to write valuable unit test?

Tests without assertions

Page 6: (PHPers Wrocław #5) How to write valuable unit test?

How we can measure quality of tests?

Page 7: (PHPers Wrocław #5) How to write valuable unit test?

Workshops

Value = Benefits - Costs

Page 8: (PHPers Wrocław #5) How to write valuable unit test?

05 06 07 08 0904

10

TEAM CODE

Page 9: (PHPers Wrocław #5) How to write valuable unit test?

Readable tests

Page 10: (PHPers Wrocław #5) How to write valuable unit test?

Data Provider /**

* @dataProvider dataCaseProvider

*/

public function testGetStatus(array $data, $expectedIsOk, $expectedIsWarning)

{

$statusProviderMock = $this->getMockBuilder(QueueStatusProvider::class)

->setConstructorArgs([$this->repositoryMock])

->setMethods(['getData'])

->getMock();

$statusProviderMock->expects($this->once())

->method('getData')

->willReturn($data);

$result = $statusProviderMock->getStatus();

$this->assertInstanceOf(StatusInterface::class, $result);

$this->assertSame($expectedIsOk, $result->isOk());

$this->assertSame($expectedIsWarning, $result->isWarning());

$this->assertSame($data, $result->getDetails());

}

Page 11: (PHPers Wrocław #5) How to write valuable unit test?

Data Provider public function dataCaseProvider() { return [ [ [ QueueStatusProvider::SECTION_WARNING => [ QueueStatusProvider::STATUS_CATEGORY_DELAYED => [1], ], QueueStatusProvider::SECTION_FAILURE => [ QueueStatusProvider::STATUS_CATEGORY_DELAYED => [1, 2, 3], QueueStatusProvider::STATUS_CATEGORY_FAILED => [], ], ], false, //expectedIsOk false //expectedIsWarning ], [ [ QueueStatusProvider::SECTION_WARNING => [ QueueStatusProvider::STATUS_CATEGORY_DELAYED => [1], ], QueueStatusProvider::SECTION_FAILURE => [ QueueStatusProvider::STATUS_CATEGORY_DELAYED => [], QueueStatusProvider::STATUS_CATEGORY_FAILED => [], ], ], false, //expectedIsOk true //expectedIsWarning ],

Page 12: (PHPers Wrocław #5) How to write valuable unit test?

Separate each case

/** * @test */public function returns_not_ok_status_when_any_delayed_or_failed_report_exists()

/** * @test */public function returns_not_ok_with_warning_status_when_only_delayed_reports_exist()

Page 13: (PHPers Wrocław #5) How to write valuable unit test?

Name should include scenario under which it’s being tested.

returns_not_ok_status_when_any_delayed_or_failed_report_exists

Page 14: (PHPers Wrocław #5) How to write valuable unit test?

Name should say something about the expected behaviour.

returns_not_ok_status_when_any_delayed_or_failed_report_exists

Page 15: (PHPers Wrocław #5) How to write valuable unit test?

Expected behaviour

state-based testing interaction testing

Page 16: (PHPers Wrocław #5) How to write valuable unit test?

Examples /** * @test */ public function talk_contain_message_after_it_has_been_added() { //Given $message = new Message("test message");

$talkId = 1;

$talkStorage = $this->getMock(TalkStorage::class); $talkManager = new TalkManager($talkStorage);

$talkStorage->method('getMessages') ->with($talkId)

->willReturn([$message]);

//When $talkManager->add($talkId, $message);

//Then $this->assertTrue($talkManager->hasMessage($talkId, $message)); }

/** * @test */ public function saved_message_in_storage_when_it_is_adding_to_talk() { //Given $message = new Message("test message");

$talkId = 1;

$talkStorage = $this->getMock(TalkStorage::class); $talkManager = new TalkManager($talkStorage);

//Expect $talkStorage->expects($this->once()) ->method('save') ->with($talkId, $message);

//When $talkManager->add($talkId, $message); }

Page 17: (PHPers Wrocław #5) How to write valuable unit test?

Structure

Given

When

Then

Given

Expect

When

Page 18: (PHPers Wrocław #5) How to write valuable unit test?

Stub vs Mock

$talkStorage->method('getMessages') ->with($talkId) ->willReturn([$message]);

$talkStorage->expects($this->once()) ->method('save') ->with($talkId, $message);

Stub Mock

given expect

Page 19: (PHPers Wrocław #5) How to write valuable unit test?

Testable code

Page 20: (PHPers Wrocław #5) How to write valuable unit test?

public function handle($id, $isForced = false)

{

$logger = $this->debugLogger;

$item = $this->debtReportTable->getById($id);

if (!$item) {

throw new DebtReportNotFoundException('Report does not exist: ' . $id);

}

$content = $item->getReportContent();

$this->updateContentWithEntity($content, $item)

->renameKeys($content)

->organizeCurrency($content)

->addFilesToReport($content, $isForced);

$this->apiClient->setRoute('api/rest/v1/debt');

$response = $this->apiClient->dispatch(Request::METHOD_POST, $content)->getResponse();

if (in_array($this->apiClient->getResponseStatusCode(), $this->AdaUnrecoverableFailureCodes)) {

$responseRawBody = $this->apiClient->getResponseRawBody();

if ($logger) {

$logger->critical(sprintf('Debt reporting failed at AdaSoftware request with status: [%s:

%s]',

$this->apiClient->getResponseStatusCode(),

$this->apiClient->getResponseStatus()));

$logger->debug(sprintf('ADA API internal server error [%s: %s]. Reason: %s',

$this->apiClient->getResponseStatusCode(),

$this->apiClient->getResponseStatus(), var_export($responseRawBody, true)));

}

$this->sendMailWithLog($item, $responseRawBody);

$item->setStatus(DebtReport::STATUS_FAILED);

$this->updateReportStats($item);

$this->debtReportTable->save($item);

throw new InternalSwApiException($responseRawBody);

} elseif (!$this->apiClient->hasValidData() || !isset($response['_embedded']['debts']) ||

!is_array($response['_embedded']['debts'])) {

$responseRawBody = $this->apiClient->getResponseRawBody();

if ($logger) {

$logger->critical('Debt reporting failed at AdaSoftware request.');

$logger->debug(sprintf('ADA API failure [%s: %s]. Response body: %s',

$this->apiClient->getResponseStatusCode(),

$this->apiClient->getResponseStatus(), var_export($responseRawBody, true)));

}

$this->sendMailWithLog($item, $responseRawBody);

$item->setStatus(DebtReport::STATUS_FAILED);

$this->debtReportTable->save($item);

} else {

if ($logger) {

$logger->debug(sprintf('ADA API success [%s: %s]. Response body: %s',

$this->apiClient->getResponseStatusCode(),

$this->apiClient->getResponseStatus(), var_export($this->apiClient->getResponseRawBody(),

true)));

}

foreach ($response['_embedded']['debts'] as $debt) {

$link = new DebtToDebtReport();

$link->setId($debt['id']);

$item->addDebtToDebtReport($link);

}

$item->setStatus(DebtReport::STATUS_COMPLETED);

$this->debtReportTable->save($item);

$this->updateReportStats($item);

return true;

}

return false;

}

Testable code?

Page 21: (PHPers Wrocław #5) How to write valuable unit test?

BUILD REQUEST CONTENT

SEND REQUEST

HANDLE RESPONSE

WRONG STATUS CODE

INVALID RESPONSE BODY

PROCESS VALID RESPONSE

Page 22: (PHPers Wrocław #5) How to write valuable unit test?

Let’s test request content building

$content = $item->getReportContent(); $this->updateContentWithEntity($content, $item) ->renameKeys($content) ->organizeCurrency($content) ->addFilesToReport($content, $isForced);

$this->apiClient->setRoute('api/rest/v1/debt'); $response = $this->apiClient->dispatch(Request::METHOD_POST, $content)->getResponse();

Page 23: (PHPers Wrocław #5) How to write valuable unit test?

Verify request content in mock

$this->apiClientMock->expects($this->once()) ->method('dispatch') ->with(Request::METHOD_POST, $expectedContent);

Page 24: (PHPers Wrocław #5) How to write valuable unit test?

But it’s not the end of preparing test ... $response = $this->apiClient->dispatch(Request::METHOD_POST, $content)->getResponse();

} else { if ($logger) { $logger->debug(sprintf('ADA API success [%s: %s]. Response body: %s', $this->apiClient->getResponseStatusCode(), $this->apiClient->getResponseStatus(), var_export($this->apiClient->getResponseRawBody(), true))); }

foreach ($response['_embedded']['debts'] as $debt) { $link = new DebtToDebtReport(); $link->setId($debt['id']); $item->addDebtToDebtReport($link); }

$item->setStatus(DebtReport::STATUS_COMPLETED); $this->debtReportTable->save($item); $this->updateReportStats($item);

return true; }

return false;}

...

Page 25: (PHPers Wrocław #5) How to write valuable unit test?

Double other collaborators

protected function setUp()

{

$this->statsServiceMock = $this->getMockBuilder(\Inkasso\Statistics\DebtReportStatisticsService::class)

->disableOriginalConstructor()->getMock();

$this->apiClientMock = $this->getMockBuilder(\ApiClient\Client\AdaSoftware::class)

->disableOriginalConstructor()->getMock();

$this->debtReportTable = $this->getMockBuilder(\DebtReport\Entity\Table\DebtReportTable::class)

->disableOriginalConstructor()->getMock();

$this->storageAdapterMock = $this->getMockBuilder(\FileStorage\Storage\StorageAdapterInterface::class)

->disableOriginalConstructor()->getMock();

$this->consumer = new ReportHandler($this->statsServiceMock, $this->storageAdapterMock, $this->debtReportTable,

$this->apiClientMock);

}

Page 26: (PHPers Wrocław #5) How to write valuable unit test?

Does our object fulfill Single Responsibility Principle?

Page 27: (PHPers Wrocław #5) How to write valuable unit test?

Separate logic

● RequestBuilder● Strategies for handling response

Page 28: (PHPers Wrocław #5) How to write valuable unit test?

Decouple logic from infrastructure

● DebtResponseToItemsMapper● Document Object Model (DOM)● Asynchrony (android.app.Service)

foreach ($response['_embedded']['debts'] as $debt) { $link = new DebtToDebtReport(); $link->setId($debt['id']); $item->addDebtToDebtReport($link);}

$item->setStatus(DebtReport::STATUS_COMPLETED);

$this->debtReportTable->save($item); infra

logic

Page 29: (PHPers Wrocław #5) How to write valuable unit test?

Tests

Page 30: (PHPers Wrocław #5) How to write valuable unit test?
Page 31: (PHPers Wrocław #5) How to write valuable unit test?
Page 32: (PHPers Wrocław #5) How to write valuable unit test?

public function test_fetchAll_emptyLanguage(){ $paramsMock = $this->getMock('Zend\Stdlib\Parameters'); $paramsMock->expects($this->at(0)) ->method('get') ->with($this->equalTo('labels')) ->will($this->returnValue('')); $paramsMock->expects($this->at(1)) ->method('get') ->with($this->equalTo('module')) ->will($this->returnValue('')); $paramsMock->expects($this->at(2)) ->method('get') ->with($this->equalTo('lang')) ->will($this->returnValue(''));}

Call order

Is the call order relevant?

How is it connected to the result?

Are empty strings important?

Page 33: (PHPers Wrocław #5) How to write valuable unit test?

$mockObjects = [ '11' => Mockery::mock(ArticleObjectInterface::class) ->shouldReceive('getId') ->times(4) ->andReturn('11') ->getMock(), '22' => Mockery::mock(ArticleObjectInterface::class) ->shouldReceive('getId') ->times(5) ->andReturn('22') ->getMock(), '33' => Mockery::mock(ArticleObjectInterface::class) ->shouldReceive('getId') ->times(4) ->andReturn('33') ->getMock(),];

Call count

Are these performance tests?

Page 34: (PHPers Wrocław #5) How to write valuable unit test?

Call count and parameters verification

Query Command

stub mock

low maintenance high value

Page 35: (PHPers Wrocław #5) How to write valuable unit test?

Testing privacy public function testSetFilters()

{

$filters = ['foo' => 'bar'];

/** @var Client|\PHPUnit_Framework_MockObject_MockObject $clientMock */

$clientMock = $this->getMockBuilder(Client::class)

->disableOriginalConstructor()

->setMethods(['addParameter'])

->getMock();

$clientMock

->expects($this->once())

->method('addParameter')

->with(

$this->equalTo(HalStorageAdapter::FILTER_PARAM),

$this->equalTo(json_encode($filters))

);

$halStorageAdapter = new HalStorageAdapter('/foo/bar', $clientMock);

ReflectionHelper::executeMethod($halStorageAdapter, 'setFilters', [[]]);

ReflectionHelper::executeMethod($halStorageAdapter, 'setFilters', [$filters]);

}

Cannot we test this by calling public methods?

Page 36: (PHPers Wrocław #5) How to write valuable unit test?

Are you tempted to:● Call a private method via reflection?● Mock a private method when testing a public method?

Public

Private

SRPPublic Public+

Page 37: (PHPers Wrocław #5) How to write valuable unit test?

Unpredictability$timestamp = time();$this->assertEquals( date('c', $timestamp), Date::isoFormat(date(\DateTime::RFC1123, $timestamp)));

random()

What about Daylight Saving Time?

Page 38: (PHPers Wrocław #5) How to write valuable unit test?

If I was about to give you…

Page 39: (PHPers Wrocław #5) How to write valuable unit test?

Just one, the most precious piece of advice

Page 40: (PHPers Wrocław #5) How to write valuable unit test?

Remember some of the previous examples?

Page 41: (PHPers Wrocław #5) How to write valuable unit test?

Tests without assertions

Page 42: (PHPers Wrocław #5) How to write valuable unit test?

$mockObjects = [ '11' => Mockery::mock(ArticleObjectInterface::class) ->shouldReceive('getId') ->times(4) ->andReturn('11') ->getMock(), '22' => Mockery::mock(ArticleObjectInterface::class) ->shouldReceive('getId') ->times(5) ->andReturn('22') ->getMock(), '33' => Mockery::mock(ArticleObjectInterface::class) ->shouldReceive('getId') ->times(4) ->andReturn('33') ->getMock(),];

Testing the implementation

Page 43: (PHPers Wrocław #5) How to write valuable unit test?

public function handle($id, $isForced = false)

{

$logger = $this->debugLogger;

$item = $this->debtReportTable->getById($id);

if (!$item) {

throw new DebtReportNotFoundException('Report does not exist: ' . $id);

}

$content = $item->getReportContent();

$this->updateContentWithEntity($content, $item)

->renameKeys($content)

->organizeCurrency($content)

->addFilesToReport($content, $isForced);

$this->apiClient->setRoute('api/rest/v1/debt');

$response = $this->apiClient->dispatch(Request::METHOD_POST, $content)->getResponse();

if (in_array($this->apiClient->getResponseStatusCode(), $this->AdaUnrecoverableFailureCodes)) {

$responseRawBody = $this->apiClient->getResponseRawBody();

if ($logger) {

$logger->critical(sprintf('Debt reporting failed at AdaSoftware request with status: [%s:

%s]',

$this->apiClient->getResponseStatusCode(),

$this->apiClient->getResponseStatus()));

$logger->debug(sprintf('ADA API internal server error [%s: %s]. Reason: %s',

$this->apiClient->getResponseStatusCode(),

$this->apiClient->getResponseStatus(), var_export($responseRawBody, true)));

}

$this->sendMailWithLog($item, $responseRawBody);

$item->setStatus(DebtReport::STATUS_FAILED);

$this->updateReportStats($item);

$this->debtReportTable->save($item);

throw new InternalSwApiException($responseRawBody);

} elseif (!$this->apiClient->hasValidData() || !isset($response['_embedded']['debts']) ||

!is_array($response['_embedded']['debts'])) {

$responseRawBody = $this->apiClient->getResponseRawBody();

if ($logger) {

$logger->critical('Debt reporting failed at AdaSoftware request.');

$logger->debug(sprintf('ADA API failure [%s: %s]. Response body: %s',

$this->apiClient->getResponseStatusCode(),

$this->apiClient->getResponseStatus(), var_export($responseRawBody, true)));

}

$this->sendMailWithLog($item, $responseRawBody);

$item->setStatus(DebtReport::STATUS_FAILED);

$this->debtReportTable->save($item);

} else {

if ($logger) {

$logger->debug(sprintf('ADA API success [%s: %s]. Response body: %s',

$this->apiClient->getResponseStatusCode(),

$this->apiClient->getResponseStatus(), var_export($this->apiClient->getResponseRawBody(),

true)));

}

foreach ($response['_embedded']['debts'] as $debt) {

$link = new DebtToDebtReport();

$link->setId($debt['id']);

$item->addDebtToDebtReport($link);

}

$item->setStatus(DebtReport::STATUS_COMPLETED);

$this->debtReportTable->save($item);

$this->updateReportStats($item);

return true;

}

return false;

}

Big chunk of code

Page 44: (PHPers Wrocław #5) How to write valuable unit test?

Would it all happen if…

Page 45: (PHPers Wrocław #5) How to write valuable unit test?

Red

Green

Refactor*TDD

Page 46: (PHPers Wrocław #5) How to write valuable unit test?
Page 47: (PHPers Wrocław #5) How to write valuable unit test?

Invitation for workshops

Page 48: (PHPers Wrocław #5) How to write valuable unit test?

3 XII 2016 (Saturday)

9:30-17:00RST (Racławicka 2)

http://rst.com.pl/unittestsworkshop/

Page 49: (PHPers Wrocław #5) How to write valuable unit test?
Page 50: (PHPers Wrocław #5) How to write valuable unit test?

Assignment● No time spent on the basics.● Equal level.● Engagement.

Page 51: (PHPers Wrocław #5) How to write valuable unit test?

Questions?

Michał Kopacz Łukasz Wróbel

Page 52: (PHPers Wrocław #5) How to write valuable unit test?

Thank you!


Recommended