(phpers wrocław #5) how to write valuable unit test?
TRANSCRIPT
![Page 1: (PHPers Wrocław #5) How to write valuable unit test?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/1.jpg)
How to write valuable unit
tests?
Łukasz Wróbel Michał Kopacz
![Page 2: (PHPers Wrocław #5) How to write valuable unit test?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/2.jpg)
Please save your questions until the end of presentation
![Page 3: (PHPers Wrocław #5) How to write valuable unit test?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/3.jpg)
![Page 4: (PHPers Wrocław #5) How to write valuable unit test?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/4.jpg)
75%CODE COVERAGE FOR UNIT TESTS
![Page 5: (PHPers Wrocław #5) How to write valuable unit test?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/5.jpg)
Tests without assertions
![Page 6: (PHPers Wrocław #5) How to write valuable unit test?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/6.jpg)
How we can measure quality of tests?
![Page 7: (PHPers Wrocław #5) How to write valuable unit test?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/7.jpg)
Workshops
Value = Benefits - Costs
![Page 8: (PHPers Wrocław #5) How to write valuable unit test?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/8.jpg)
05 06 07 08 0904
10
TEAM CODE
![Page 9: (PHPers Wrocław #5) How to write valuable unit test?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/9.jpg)
Readable tests
![Page 10: (PHPers Wrocław #5) How to write valuable unit test?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/10.jpg)
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?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/11.jpg)
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?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/12.jpg)
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?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/13.jpg)
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?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/14.jpg)
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?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/15.jpg)
Expected behaviour
state-based testing interaction testing
![Page 16: (PHPers Wrocław #5) How to write valuable unit test?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/16.jpg)
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?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/17.jpg)
Structure
Given
When
Then
Given
Expect
When
![Page 18: (PHPers Wrocław #5) How to write valuable unit test?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/18.jpg)
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?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/19.jpg)
Testable code
![Page 20: (PHPers Wrocław #5) How to write valuable unit test?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/20.jpg)
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?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/21.jpg)
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?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/22.jpg)
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?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/23.jpg)
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?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/24.jpg)
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?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/25.jpg)
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?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/26.jpg)
Does our object fulfill Single Responsibility Principle?
![Page 27: (PHPers Wrocław #5) How to write valuable unit test?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/27.jpg)
Separate logic
● RequestBuilder● Strategies for handling response
![Page 28: (PHPers Wrocław #5) How to write valuable unit test?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/28.jpg)
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?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/29.jpg)
Tests
![Page 30: (PHPers Wrocław #5) How to write valuable unit test?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/30.jpg)
![Page 31: (PHPers Wrocław #5) How to write valuable unit test?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/31.jpg)
![Page 32: (PHPers Wrocław #5) How to write valuable unit test?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/32.jpg)
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?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/33.jpg)
$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?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/34.jpg)
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?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/35.jpg)
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?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/36.jpg)
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?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/37.jpg)
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?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/38.jpg)
If I was about to give you…
![Page 39: (PHPers Wrocław #5) How to write valuable unit test?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/39.jpg)
Just one, the most precious piece of advice
![Page 40: (PHPers Wrocław #5) How to write valuable unit test?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/40.jpg)
Remember some of the previous examples?
![Page 41: (PHPers Wrocław #5) How to write valuable unit test?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/41.jpg)
Tests without assertions
![Page 42: (PHPers Wrocław #5) How to write valuable unit test?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/42.jpg)
$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?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/43.jpg)
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?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/44.jpg)
Would it all happen if…
![Page 45: (PHPers Wrocław #5) How to write valuable unit test?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/45.jpg)
Red
Green
Refactor*TDD
![Page 46: (PHPers Wrocław #5) How to write valuable unit test?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/46.jpg)
![Page 47: (PHPers Wrocław #5) How to write valuable unit test?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/47.jpg)
Invitation for workshops
![Page 48: (PHPers Wrocław #5) How to write valuable unit test?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/48.jpg)
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?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/49.jpg)
![Page 50: (PHPers Wrocław #5) How to write valuable unit test?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/50.jpg)
Assignment● No time spent on the basics.● Equal level.● Engagement.
![Page 51: (PHPers Wrocław #5) How to write valuable unit test?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/51.jpg)
Questions?
Michał Kopacz Łukasz Wróbel
![Page 52: (PHPers Wrocław #5) How to write valuable unit test?](https://reader034.vdocument.in/reader034/viewer/2022051520/58ee131b1a28ab60238b456f/html5/thumbnails/52.jpg)
Thank you!