SlideShare a Scribd company logo
1 of 38
Clean tests - good tests
Petr Heinz
Time for an exercise
Time for an exercise
Who have ever written an automated test?
Time for an exercise
Who have ever written an automated test?
Who already dealt with tests failing for no apparent reason?
Time for an exercise
Who have ever written an automated test?
Who already dealt with tests failing for no apparent reason?
Who had ever feeling like the tests are just throwing
obstacles in your way?
How is testing done with ShopSys Framework
Unit tests - PHPUnit
Integration / database tests
Crawler tests
Acceptance tests - Codeception, Selenium
Performance tests
automated execution on CI server (Jenkins)
What can I expect from a good test?
It is testing one functionality and it fails when it doesn’t work properly.
It is robust enough not to fail when changing unrelated code.
Even after two months I know what, how and why it is testing.
When it fails, I know where the problem is.
It is easy to execute the test and it runs fast. Having unexecuted test is useless.
It is testing an important functionality. The aim isn’t 100% coverage.
Test phases
Arrange - initial requirements setting
Act - the execution of the test
Assert - expected result control
Each phase should be easily told apart in the code.
Don’t be afraid to extract bit of the code just to make it more readable.
Finally, source codes!
Let’s have a look at an acceptance test for searching
product in the administration using its catalogue
number
class AdminProductSearchCest {
public function testSearchByCatnum(AcceptanceTester $me) {
$me->wantTo('search for product by catnum');
$me->amOnPage('/admin/');
$me->fillFieldByName('admin_login_form[username]', 'admin');
$me->fillFieldByName('admin_login_form[password]', 'admin123');
$me->clickByText('Přihlásit');
$me->amOnPage('/admin/product/list/');
$me->clickByText('Advanced search');
$me->selectOptionByCssAndValue('.js-search-rule-subject', 'productCatnum');
$me->fillFieldByCss('.js-search-rule-value input', '9176544MG');
$me->clickByText('Search');
$me->seeInCss('Aquila Still Spring Water', '.js-grid-column-name');
$foundProductCount = $me->countVisibleByCss('tbody .table-grid__row');
assertEquals(1, $foundProductCount);
}
}
Acceptance test of filtering - the original code
class LoginPage extends AbstractPage {
const ADMIN_USERNAME = 'admin';
const ADMIN_PASSWORD = 'admin123';
/**
* @param string $username
* @param string $password
*/
public function login($username, $password) {
$this->tester->amOnPage('/admin/');
$this->tester->fillFieldByName('admin_login_form[username]', $username);
$this->tester->fillFieldByName('admin_login_form[password]', $password);
$this->tester->clickByText('Log in');
}
}
LoginPage object
class AdminProductSearchCest {
public function testSearchByCatnum(AcceptanceTester $me, LoginPage $loginPage) {
$me->wantTo('search for product by catnum');
$loginPage->login(LoginPage::ADMIN_USERNAME, LoginPage::ADMIN_PASSWORD);
$me->amOnPage('/admin/product/list/');
$me->clickByText('Advanced search');
$me->selectOptionByCssAndValue('.js-search-rule-subject', 'productCatnum');
$me->fillFieldByCss('.js-search-rule-value input', '9176544MG');
$me->clickByText('Search');
$me->seeInCss('Aquila Still Spring Water', '.js-grid-column-name');
$foundProductCount = $me->countVisibleByCss('tbody .table-grid__row');
assertEquals(1, $foundProductCount);
}
}
Acceptance test of filtering - using the LoginPage
class LoginPage extends AbstractPage {
const ADMIN_USERNAME = 'admin';
const ADMIN_PASSWORD = 'admin123';
/**
* @param string $username
* @param string $password
*/
public function login($username, $password) {
$this->tester->amOnPage('/admin/');
$this->tester->fillFieldByName('admin_login_form[username]', $username);
$this->tester->fillFieldByName('admin_login_form[password]', $password);
$this->tester->clickByText('Log in');
}
public function assertLoginFailed() {
$this->tester->see('Login failed.');
$this->tester->seeCurrentPageEquals('/admin/');
}
}
LoginPage object - assert extension
class AdministratorLoginCest {
public function testSuccessfulLogin(AcceptanceTester $me, LoginPage $loginPage) {
$me->wantTo('login on admin with valid data');
$loginPage->login(LoginPage::ADMIN_USERNAME, LoginPage::ADMIN_PASSWORD);
$me->see('Dashboard');
}
public function testLoginWithInvalidUsername(AcceptanceTester $me, LoginPage $loginPage)
{
$me->wantTo('login on admin with nonexistent username');
$loginPage->login('nonexistent username', LoginPage::ADMIN_PASSWORD);
$loginPage->assertLoginFailed();
}
public function testLoginWithInvalidPassword(AcceptanceTester $me, LoginPage $loginPage)
{
$me->wantTo('login on admin with invalid password');
$loginPage->login(LoginPage::ADMIN_USERNAME, 'invalid password');
$loginPage->assertLoginFailed();
}
}
Acceptance test of filtering - reusing the LoginPage
class AdminProductSearchCest {
public function testSearchByCatnum(AcceptanceTester $me, LoginPage $loginPage) {
$me->wantTo('search for product by catnum');
$loginPage->login(LoginPage::ADMIN_USERNAME, LoginPage::ADMIN_PASSWORD);
$me->amOnPage('/admin/product/list/');
$me->clickByText('Advanced search');
$me->selectOptionByCssAndValue('.js-search-rule-subject', 'productCatnum');
$me->fillFieldByCss('.js-search-rule-value input', '9176544MG');
$me->clickByText('Search');
$me->seeInCss('Aquila Still Spring Water', '.js-grid-column-name');
$foundProductCount = $me->countVisibleByCss('tbody .table-grid__row');
assertEquals(1, $foundProductCount);
}
}
Acceptance test of filtering - using LoginPage
class ProductSearchPage extends AbstractPage {
const SEARCH_SUBJECT_CATNUM = 'productCatnum';
/**
* @param string $searchSubject
* @param string $value
*/
public function search($searchSubject, $value) {
$this->tester->amOnPage('/admin/product/list/');
$this->tester->clickByText('Advanced search');
$this->tester->selectOptionByCssAndValue('.js-search-rule-subject',
$searchSubject);
$this->tester->fillFieldByCss('.js-search-rule-value input', $value);
$this->tester->clickByText('Search');
}
public function assertFoundProductByName($productName) {
$this->tester->seeInCss($productName, '.js-grid-column-name');
}
public function assertFoundProductCount($productCount) {
$foundProductCount = $me->countVisibleByCss('tbody .table-grid__row');
assertEquals($productCount, $foundProductCount);
}
}
ProductSearchPage object
class AdminProductSearchCest {
public function testSearchByCatnum(
AcceptanceTester $me,
LoginPage $loginPage,
ProductSearchPage $productSearchPage
) {
$me->wantTo('search for product by catnum');
$loginPage->login(LoginPage::ADMIN_USERNAME, LoginPage::ADMIN_PASSWORD);
$productSearchPage->search(ProductSearchPage::SEARCH_SUBJECT_CATNUM, '9176544MG');
$productSearchPage->assertFoundProductByName('Aquila Pramenitá voda neperlivá');
$productSearchPage->assertFoundProductCount(1);
}
}
Acceptance test of filtering - using the ProductSearchPage
Naming of the testing methods
Testing methods don’t have to be named exactly after the tested method.
It is suitable to name the methods after the tested scenario.
The intention and the expectations of the test should be clear.
If it’s not easy to name the testing method it might be the case you are testing too
many things at once.
Don’t be afraid of long names.
Back to code!
Let’s have a look at a unit test of method for adding
product to the cart
interface CartService {
// …
/**
* @param SS6ShopBundleModelCartCart $cart
* @param SS6ShopBundleModelProductProduct $product
* @param int $quantity
* @return SS6ShopBundleModelCartAddProductResult
* @throws SS6ShopBundleModelCartInvalidQuantityException
*/
public function addProductToCart(Cart $cart, Product $product, $quantity);
// …
}
Test class interface
interface AddProductResult {
/**
* @param SS6ShopBundleModelCartItemCartItem $cartItem
* @param bool $isNew
* @param int $addedQuantity
*/
public function __construct(CartItem $cartItem, $isNew, $addedQuantity);
/**
* @return SS6ShopBundleModelCartItemCartItem
*/
public function getCartItem();
/**
* @return bool
*/
public function getIsNew();
/**
* @return int
*/
public function getAddedQuantity();
}
Interface of the return value of the tested method
class CartServiceTest extends FunctionalTestCase {
// …
public function testAddProductToCartInvalidFloatQuantity() {
$cartService = $this->getCartService();
$product = $this->createProduct();
$cart = $this->createEmptyCart();
$addedQuantity = 1.1;
$this-
>setExpectedException('SS6ShopBundleModelCartInvalidQuantityException');
$cartService->addProductToCart($cart, $product, $addedQuantity);
}
// …
}
Adding to the cart unit test - original method name
class CartServiceTest extends FunctionalTestCase {
// …
public function testCannotAddProductWithFloatQuantityToCart() {
$cartService = $this->getCartService();
$product = $this->createProduct();
$cart = $this->createEmptyCart();
$addedQuantity = 1.1;
$this-
>setExpectedException('SS6ShopBundleModelCartInvalidQuantityException');
$cartService->addProductToCart($cart, $product, $addedQuantity);
}
// …
}
Adding to the cart unit test - new method name
class CartServiceTest extends FunctionalTestCase {
// …
public function testAddProductToCartInvalidZeroQuantity() {
$cartService = $this->getCartService();
$product = $this->createProduct();
$cart = $this->createEmptyCart();
$addedQuantity = 0;
$this-
>setExpectedException('SS6ShopBundleModelCartInvalidQuantityException');
$cartService->addProductToCart($cart, $product, $addedQuantity);
}
// …
}
Adding to the cart unit test - original method name
class CartServiceTest extends FunctionalTestCase {
// …
public function testCannotAddProductWithZeroQuantityToCart() {
$cartService = $this->getCartService();
$product = $this->createProduct();
$cart = $this->createEmptyCart();
$addedQuantity = 0;
$this-
>setExpectedException('SS6ShopBundleModelCartInvalidQuantityException');
$cartService->addProductToCart($cart, $product, $addedQuantity);
}
// …
}
Adding to the cart unit test - new method name
class CartServiceTest extends FunctionalTestCase {
// …
public function testAddProductToCartNewProduct() {
$cartService = $this->getCartService();
$product = $this->createProduct();
$cart = $this->createEmptyCart();
$addedQuantity = 2;
$result = $cartService->addProductToCart($cart, $product, $addedQuantity);
$this->assertTrue($result->getIsNew());
$this->assertSame($addedQuantity, $result->getAddedQuantity());
}
// …
}
Adding to the cart unit test - original method name
class CartServiceTest extends FunctionalTestCase {
// …
public function
testAddProductToCartMarksNewlyAddedProductAsNewAndContainsAddedQuantity() {
$cartService = $this->getCartService();
$product = $this->createProduct();
$cart = $this->createEmptyCart();
$addedQuantity = 2;
$result = $cartService->addProductToCart($cart, $product, $addedQuantity);
$this->assertTrue($result->getIsNew());
$this->assertSame($addedQuantity, $result->getAddedQuantity());
}
// …
}
Adding to the cart unit test - new method name?
class CartServiceTest extends FunctionalTestCase {
// …
public function testAddProductToCartMarksNewlyAddedProductAsNew() {
$cartService = $this->getCartService();
$product = $this->createProduct();
$cart = $this->createEmptyCart();
$addedQuantity = 2;
$result = $cartService->addProductToCart($cart, $product, $addedQuantity);
$this->assertTrue($result->getIsNew());
}
public function testAddProductResultContainsAddedProductQuantity() {
$cartService = $this->getCartService();
$product = $this->createProduct();
$cart = $this->createEmptyCart();
$addedQuantity = 2;
$result = $cartService->addProductToCart($cart, $product, $addedQuantity);
$this->assertSame($addedQuantity, $result->getAddedQuantity());
}
// …
} Adding to the cart unit test - separating the method
class CartServiceTest extends FunctionalTestCase {
// …
public function testAddProductToCartSameProduct() {
$cartService = $this->getCartService();
$product = $this->createProduct();
$cart = $this->createCartWithOneItem($product);
$addedQuantity = 2;
$result = $cartService->addProductToCart($cart, $product, $addedQuantity);
$this->assertFalse($result->getIsNew());
$this->assertSame($addedQuantity, $result->getAddedQuantity());
}
// …
}
Adding to the cart unit test - original method name
class CartServiceTest extends FunctionalTestCase {
// …
public function testAddProductToCartMarksRepeatedlyAddedProductAsNotNew() {
$cartService = $this->getCartService();
$product = $this->createProduct();
$cart = $this->createCartWithOneItem($product);
$addedQuantity = 2;
$result = $cartService->addProductToCart($cart, $product, $addedQuantity);
$this->assertFalse($result->getIsNew());
}
public function testAddProductResultDoesNotContainPreviouslyAddedProductQuantity() {
$cartService = $this->getCartService();
$product = $this->createProduct();
$cart = $this->createCartWithOneItem($product);
$addedQuantity = 2;
$result = $cartService->addProductToCart($cart, $product, $addedQuantity);
$this->assertSame($addedQuantity, $result->getAddedQuantity());
}
// …
} Adding to the cart unit test - separating the method
Mocking
Mocks are good when simulating too complex objects.
Its behavior can be controlled well directly in the test code.
It is possible to use it when verifying correct communication between classes.
It is good to extract its creation to a private method.
To the code!
Let’s have a look at a demonstration of a mocking in
database/integration test
interface WebService {
// …
/**
* @param SS6ShopBundleComponentWebServiceRequest $request
* @return resource
*/
public function getResponseStream(Request $request);
// …
}
Mocked class interface
class TransferProductTest extends DatabaseTestCase {
// …
/**
* @param string $fileName
* @return SS6ShopBundleComponentWebService|PHPUnit_Framework_MockObject_MockObject
*/
private function mockWebServiceReturningFileResource($fileName) {
$transferWebServiceMock = $this->getMockBuilder(WebService::class)
->disableOriginalConstructor()
->getMock();
$filePath = __DIR__ . '/Resources/' . $fileName;
$fileResource = fopen($filePath, 'r');
$transferWebServiceMock
->method('getResponseStream')
->willReturn($fileResource);
return $transferWebServiceMock;
}
// …
}
Creating the mock in a private class
class TransferProductTest extends DatabaseTestCase {
// …
/**
* @param string $fileName
* @return SS6ShopBundleModelTransferTransferFacade
*/
private function createTransferFacadeMockingWebServiceWithFile($fileName) {
return new TransferFacade(
$this->getContainer()->get(TransferRepository::class),
$this->mockWebServicReturningFileResource($fileName),
$this->getContainer()->get(ByteFormatter::class),
$this->getContainer()->get(SqlLoggerFacade::class),
$this->getContainer()->get(RepeatedTransferFacade::class),
$this->getContainer()->get(TransferLoggerFactory::class),
$this->getContainer()->get(EntityManager::class),
$this->getContainer()->get(EntityManagerFacade::class)
);
}
// …
}
Injecting the mock into the real tested class
class TransferProductTest extends DatabaseTestCase {
/**
* @var SS6ShopBundleModelTransferProductProductTransferProcessor
*/
private $productTransferProcessor;
/**
* @var SS6ShopBundleModelProductProductFacade
*/
private $productFacade;
// …
public function testCreateProductCreatesProduct() {
$transferFacade =
$this-
>createTransferFacadeMockingWebServiceWithFile(self::FILE_NAME);
$logger = $this->createLogger();
$transferFacade->process($this->productTransferProcessor, $logger);
$product = $this->productFacade-
>findOneByFloresId(self::PRODUCT_1_FLORES_ID);
$this->assertNotNull($product);
}
// …
}
Intergraton/database test
Some advice in conclusion
Tests are not here in order “to exist”, they are here for you.
Start with testing the most important scenarios.
Well-kept demonstration data which you are going to use in the tests will help.
Don’t be afraid to create special classes only for the test.
Some tests are worth deleting.
Having clean code in tests is equally important as having it in application code.
Thank you for your attention
Let’s get down to your questions!
petr.heinz@shopsys.com

More Related Content

What's hot

Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12Michelangelo van Dam
 
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012Michelangelo van Dam
 
Kiss PageObjects [01-2017]
Kiss PageObjects [01-2017]Kiss PageObjects [01-2017]
Kiss PageObjects [01-2017]Iakiv Kramarenko
 
QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...
QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...
QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...GeeksLab Odessa
 
Workshop quality assurance for php projects - phpdublin
Workshop quality assurance for php projects - phpdublinWorkshop quality assurance for php projects - phpdublin
Workshop quality assurance for php projects - phpdublinMichelangelo van Dam
 
Web ui tests examples with selenide, nselene, selene & capybara
Web ui tests examples with  selenide, nselene, selene & capybaraWeb ui tests examples with  selenide, nselene, selene & capybara
Web ui tests examples with selenide, nselene, selene & capybaraIakiv Kramarenko
 
Vejovis: Suggesting Fixes for JavaScript Faults
Vejovis: Suggesting Fixes for JavaScript FaultsVejovis: Suggesting Fixes for JavaScript Faults
Vejovis: Suggesting Fixes for JavaScript FaultsSALT Lab @ UBC
 
UA testing with Selenium and PHPUnit - PFCongres 2013
UA testing with Selenium and PHPUnit - PFCongres 2013UA testing with Selenium and PHPUnit - PFCongres 2013
UA testing with Selenium and PHPUnit - PFCongres 2013Michelangelo van Dam
 
Understanding JavaScript Testing
Understanding JavaScript TestingUnderstanding JavaScript Testing
Understanding JavaScript Testingjeresig
 
Why Your Test Suite Sucks - PHPCon PL 2015
Why Your Test Suite Sucks - PHPCon PL 2015Why Your Test Suite Sucks - PHPCon PL 2015
Why Your Test Suite Sucks - PHPCon PL 2015CiaranMcNulty
 
Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013Michelangelo van Dam
 
Unit testing PHP apps with PHPUnit
Unit testing PHP apps with PHPUnitUnit testing PHP apps with PHPUnit
Unit testing PHP apps with PHPUnitMichelangelo van Dam
 
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"GeeksLab Odessa
 
From Good to Great: Functional and Acceptance Testing in WordPress.
From Good to Great: Functional and Acceptance Testing in WordPress.From Good to Great: Functional and Acceptance Testing in WordPress.
From Good to Great: Functional and Acceptance Testing in WordPress.David Aguilera
 
Enrique Amodeo | Graphql + Microservices = Win! | Codemotion Madrid 2018
Enrique Amodeo | Graphql + Microservices = Win! | Codemotion Madrid 2018 Enrique Amodeo | Graphql + Microservices = Win! | Codemotion Madrid 2018
Enrique Amodeo | Graphql + Microservices = Win! | Codemotion Madrid 2018 Codemotion
 
Codeception presentation
Codeception presentationCodeception presentation
Codeception presentationAndrei Burian
 

What's hot (20)

Write readable tests
Write readable testsWrite readable tests
Write readable tests
 
Codeception
CodeceptionCodeception
Codeception
 
Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12
 
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012
 
Kiss PageObjects [01-2017]
Kiss PageObjects [01-2017]Kiss PageObjects [01-2017]
Kiss PageObjects [01-2017]
 
QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...
QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...
QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...
 
Workshop quality assurance for php projects - phpdublin
Workshop quality assurance for php projects - phpdublinWorkshop quality assurance for php projects - phpdublin
Workshop quality assurance for php projects - phpdublin
 
Web ui tests examples with selenide, nselene, selene & capybara
Web ui tests examples with  selenide, nselene, selene & capybaraWeb ui tests examples with  selenide, nselene, selene & capybara
Web ui tests examples with selenide, nselene, selene & capybara
 
Vejovis: Suggesting Fixes for JavaScript Faults
Vejovis: Suggesting Fixes for JavaScript FaultsVejovis: Suggesting Fixes for JavaScript Faults
Vejovis: Suggesting Fixes for JavaScript Faults
 
UA testing with Selenium and PHPUnit - PFCongres 2013
UA testing with Selenium and PHPUnit - PFCongres 2013UA testing with Selenium and PHPUnit - PFCongres 2013
UA testing with Selenium and PHPUnit - PFCongres 2013
 
Understanding JavaScript Testing
Understanding JavaScript TestingUnderstanding JavaScript Testing
Understanding JavaScript Testing
 
Why Your Test Suite Sucks - PHPCon PL 2015
Why Your Test Suite Sucks - PHPCon PL 2015Why Your Test Suite Sucks - PHPCon PL 2015
Why Your Test Suite Sucks - PHPCon PL 2015
 
Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013
 
Unit testing PHP apps with PHPUnit
Unit testing PHP apps with PHPUnitUnit testing PHP apps with PHPUnit
Unit testing PHP apps with PHPUnit
 
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"
 
TDD, BDD, RSpec
TDD, BDD, RSpecTDD, BDD, RSpec
TDD, BDD, RSpec
 
From Good to Great: Functional and Acceptance Testing in WordPress.
From Good to Great: Functional and Acceptance Testing in WordPress.From Good to Great: Functional and Acceptance Testing in WordPress.
From Good to Great: Functional and Acceptance Testing in WordPress.
 
Enrique Amodeo | Graphql + Microservices = Win! | Codemotion Madrid 2018
Enrique Amodeo | Graphql + Microservices = Win! | Codemotion Madrid 2018 Enrique Amodeo | Graphql + Microservices = Win! | Codemotion Madrid 2018
Enrique Amodeo | Graphql + Microservices = Win! | Codemotion Madrid 2018
 
Codeception presentation
Codeception presentationCodeception presentation
Codeception presentation
 
Qunit Java script Un
Qunit Java script UnQunit Java script Un
Qunit Java script Un
 

Similar to Clean tests good tests

Using of TDD practices for Magento
Using of TDD practices for MagentoUsing of TDD practices for Magento
Using of TDD practices for MagentoIvan Chepurnyi
 
Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8Michelangelo van Dam
 
Test in action week 4
Test in action   week 4Test in action   week 4
Test in action week 4Yi-Huan Chan
 
Bring the fun back to java
Bring the fun back to javaBring the fun back to java
Bring the fun back to javaciklum_ods
 
How To Test Everything
How To Test EverythingHow To Test Everything
How To Test Everythingnoelrap
 
Testing ASP.NET - Progressive.NET
Testing ASP.NET - Progressive.NETTesting ASP.NET - Progressive.NET
Testing ASP.NET - Progressive.NETBen Hall
 
Unit testing with zend framework tek11
Unit testing with zend framework tek11Unit testing with zend framework tek11
Unit testing with zend framework tek11Michelangelo van Dam
 
PHPUnit best practices presentation
PHPUnit best practices presentationPHPUnit best practices presentation
PHPUnit best practices presentationThanh Robi
 
Better Testing With PHP Unit
Better Testing With PHP UnitBetter Testing With PHP Unit
Better Testing With PHP Unitsitecrafting
 
Unit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBeneluxUnit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBeneluxMichelangelo van Dam
 
Automated Unit Testing
Automated Unit TestingAutomated Unit Testing
Automated Unit TestingMike Lively
 
Тестирование и Django
Тестирование и DjangoТестирование и Django
Тестирование и DjangoMoscowDjango
 
How to write clean tests
How to write clean testsHow to write clean tests
How to write clean testsDanylenko Max
 
Getting to Grips with SilverStripe Testing
Getting to Grips with SilverStripe TestingGetting to Grips with SilverStripe Testing
Getting to Grips with SilverStripe TestingMark Rickerby
 
EPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur PurnamaEPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur PurnamaEnterprise PHP Center
 

Similar to Clean tests good tests (20)

Php tests tips
Php tests tipsPhp tests tips
Php tests tips
 
Unit testing zend framework apps
Unit testing zend framework appsUnit testing zend framework apps
Unit testing zend framework apps
 
Using of TDD practices for Magento
Using of TDD practices for MagentoUsing of TDD practices for Magento
Using of TDD practices for Magento
 
PHP Unit Testing
PHP Unit TestingPHP Unit Testing
PHP Unit Testing
 
Testy integracyjne
Testy integracyjneTesty integracyjne
Testy integracyjne
 
Test driven development_for_php
Test driven development_for_phpTest driven development_for_php
Test driven development_for_php
 
Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8
 
Test in action week 4
Test in action   week 4Test in action   week 4
Test in action week 4
 
Bring the fun back to java
Bring the fun back to javaBring the fun back to java
Bring the fun back to java
 
How To Test Everything
How To Test EverythingHow To Test Everything
How To Test Everything
 
Testing ASP.NET - Progressive.NET
Testing ASP.NET - Progressive.NETTesting ASP.NET - Progressive.NET
Testing ASP.NET - Progressive.NET
 
Unit testing with zend framework tek11
Unit testing with zend framework tek11Unit testing with zend framework tek11
Unit testing with zend framework tek11
 
PHPUnit best practices presentation
PHPUnit best practices presentationPHPUnit best practices presentation
PHPUnit best practices presentation
 
Better Testing With PHP Unit
Better Testing With PHP UnitBetter Testing With PHP Unit
Better Testing With PHP Unit
 
Unit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBeneluxUnit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBenelux
 
Automated Unit Testing
Automated Unit TestingAutomated Unit Testing
Automated Unit Testing
 
Тестирование и Django
Тестирование и DjangoТестирование и Django
Тестирование и Django
 
How to write clean tests
How to write clean testsHow to write clean tests
How to write clean tests
 
Getting to Grips with SilverStripe Testing
Getting to Grips with SilverStripe TestingGetting to Grips with SilverStripe Testing
Getting to Grips with SilverStripe Testing
 
EPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur PurnamaEPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur Purnama
 

Recently uploaded

Story boards and shot lists for my a level piece
Story boards and shot lists for my a level pieceStory boards and shot lists for my a level piece
Story boards and shot lists for my a level piececharlottematthew16
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024Stephanie Beckett
 
Vector Databases 101 - An introduction to the world of Vector Databases
Vector Databases 101 - An introduction to the world of Vector DatabasesVector Databases 101 - An introduction to the world of Vector Databases
Vector Databases 101 - An introduction to the world of Vector DatabasesZilliz
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLScyllaDB
 
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
"Federated learning: out of reach no matter how close",Oleksandr LapshynFwdays
 
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostLeverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostZilliz
 
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)Bun (KitWorks Team Study 노별마루 발표 2024.4.22)
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)Wonjun Hwang
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationSlibray Presentation
 
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Patryk Bandurski
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 3652toLead Limited
 
Unraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfUnraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfAlex Barbosa Coqueiro
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii SoldatenkoFwdays
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
The Future of Software Development - Devin AI Innovative Approach.pdf
The Future of Software Development - Devin AI Innovative Approach.pdfThe Future of Software Development - Devin AI Innovative Approach.pdf
The Future of Software Development - Devin AI Innovative Approach.pdfSeasiaInfotech2
 
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks..."LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...Fwdays
 
Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfRankYa
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxNavinnSomaal
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsRizwan Syed
 

Recently uploaded (20)

Story boards and shot lists for my a level piece
Story boards and shot lists for my a level pieceStory boards and shot lists for my a level piece
Story boards and shot lists for my a level piece
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024
 
Vector Databases 101 - An introduction to the world of Vector Databases
Vector Databases 101 - An introduction to the world of Vector DatabasesVector Databases 101 - An introduction to the world of Vector Databases
Vector Databases 101 - An introduction to the world of Vector Databases
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQL
 
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
 
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostLeverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
 
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)Bun (KitWorks Team Study 노별마루 발표 2024.4.22)
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck Presentation
 
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365
 
Unraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfUnraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdf
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko
 
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptxE-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
DMCC Future of Trade Web3 - Special Edition
DMCC Future of Trade Web3 - Special EditionDMCC Future of Trade Web3 - Special Edition
DMCC Future of Trade Web3 - Special Edition
 
The Future of Software Development - Devin AI Innovative Approach.pdf
The Future of Software Development - Devin AI Innovative Approach.pdfThe Future of Software Development - Devin AI Innovative Approach.pdf
The Future of Software Development - Devin AI Innovative Approach.pdf
 
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks..."LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
 
Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdf
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptx
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL Certs
 

Clean tests good tests

  • 1. Clean tests - good tests Petr Heinz
  • 2. Time for an exercise
  • 3. Time for an exercise Who have ever written an automated test?
  • 4. Time for an exercise Who have ever written an automated test? Who already dealt with tests failing for no apparent reason?
  • 5. Time for an exercise Who have ever written an automated test? Who already dealt with tests failing for no apparent reason? Who had ever feeling like the tests are just throwing obstacles in your way?
  • 6. How is testing done with ShopSys Framework Unit tests - PHPUnit Integration / database tests Crawler tests Acceptance tests - Codeception, Selenium Performance tests automated execution on CI server (Jenkins)
  • 7. What can I expect from a good test? It is testing one functionality and it fails when it doesn’t work properly. It is robust enough not to fail when changing unrelated code. Even after two months I know what, how and why it is testing. When it fails, I know where the problem is. It is easy to execute the test and it runs fast. Having unexecuted test is useless. It is testing an important functionality. The aim isn’t 100% coverage.
  • 8. Test phases Arrange - initial requirements setting Act - the execution of the test Assert - expected result control Each phase should be easily told apart in the code. Don’t be afraid to extract bit of the code just to make it more readable.
  • 9. Finally, source codes! Let’s have a look at an acceptance test for searching product in the administration using its catalogue number
  • 10. class AdminProductSearchCest { public function testSearchByCatnum(AcceptanceTester $me) { $me->wantTo('search for product by catnum'); $me->amOnPage('/admin/'); $me->fillFieldByName('admin_login_form[username]', 'admin'); $me->fillFieldByName('admin_login_form[password]', 'admin123'); $me->clickByText('Přihlásit'); $me->amOnPage('/admin/product/list/'); $me->clickByText('Advanced search'); $me->selectOptionByCssAndValue('.js-search-rule-subject', 'productCatnum'); $me->fillFieldByCss('.js-search-rule-value input', '9176544MG'); $me->clickByText('Search'); $me->seeInCss('Aquila Still Spring Water', '.js-grid-column-name'); $foundProductCount = $me->countVisibleByCss('tbody .table-grid__row'); assertEquals(1, $foundProductCount); } } Acceptance test of filtering - the original code
  • 11. class LoginPage extends AbstractPage { const ADMIN_USERNAME = 'admin'; const ADMIN_PASSWORD = 'admin123'; /** * @param string $username * @param string $password */ public function login($username, $password) { $this->tester->amOnPage('/admin/'); $this->tester->fillFieldByName('admin_login_form[username]', $username); $this->tester->fillFieldByName('admin_login_form[password]', $password); $this->tester->clickByText('Log in'); } } LoginPage object
  • 12. class AdminProductSearchCest { public function testSearchByCatnum(AcceptanceTester $me, LoginPage $loginPage) { $me->wantTo('search for product by catnum'); $loginPage->login(LoginPage::ADMIN_USERNAME, LoginPage::ADMIN_PASSWORD); $me->amOnPage('/admin/product/list/'); $me->clickByText('Advanced search'); $me->selectOptionByCssAndValue('.js-search-rule-subject', 'productCatnum'); $me->fillFieldByCss('.js-search-rule-value input', '9176544MG'); $me->clickByText('Search'); $me->seeInCss('Aquila Still Spring Water', '.js-grid-column-name'); $foundProductCount = $me->countVisibleByCss('tbody .table-grid__row'); assertEquals(1, $foundProductCount); } } Acceptance test of filtering - using the LoginPage
  • 13. class LoginPage extends AbstractPage { const ADMIN_USERNAME = 'admin'; const ADMIN_PASSWORD = 'admin123'; /** * @param string $username * @param string $password */ public function login($username, $password) { $this->tester->amOnPage('/admin/'); $this->tester->fillFieldByName('admin_login_form[username]', $username); $this->tester->fillFieldByName('admin_login_form[password]', $password); $this->tester->clickByText('Log in'); } public function assertLoginFailed() { $this->tester->see('Login failed.'); $this->tester->seeCurrentPageEquals('/admin/'); } } LoginPage object - assert extension
  • 14. class AdministratorLoginCest { public function testSuccessfulLogin(AcceptanceTester $me, LoginPage $loginPage) { $me->wantTo('login on admin with valid data'); $loginPage->login(LoginPage::ADMIN_USERNAME, LoginPage::ADMIN_PASSWORD); $me->see('Dashboard'); } public function testLoginWithInvalidUsername(AcceptanceTester $me, LoginPage $loginPage) { $me->wantTo('login on admin with nonexistent username'); $loginPage->login('nonexistent username', LoginPage::ADMIN_PASSWORD); $loginPage->assertLoginFailed(); } public function testLoginWithInvalidPassword(AcceptanceTester $me, LoginPage $loginPage) { $me->wantTo('login on admin with invalid password'); $loginPage->login(LoginPage::ADMIN_USERNAME, 'invalid password'); $loginPage->assertLoginFailed(); } } Acceptance test of filtering - reusing the LoginPage
  • 15. class AdminProductSearchCest { public function testSearchByCatnum(AcceptanceTester $me, LoginPage $loginPage) { $me->wantTo('search for product by catnum'); $loginPage->login(LoginPage::ADMIN_USERNAME, LoginPage::ADMIN_PASSWORD); $me->amOnPage('/admin/product/list/'); $me->clickByText('Advanced search'); $me->selectOptionByCssAndValue('.js-search-rule-subject', 'productCatnum'); $me->fillFieldByCss('.js-search-rule-value input', '9176544MG'); $me->clickByText('Search'); $me->seeInCss('Aquila Still Spring Water', '.js-grid-column-name'); $foundProductCount = $me->countVisibleByCss('tbody .table-grid__row'); assertEquals(1, $foundProductCount); } } Acceptance test of filtering - using LoginPage
  • 16. class ProductSearchPage extends AbstractPage { const SEARCH_SUBJECT_CATNUM = 'productCatnum'; /** * @param string $searchSubject * @param string $value */ public function search($searchSubject, $value) { $this->tester->amOnPage('/admin/product/list/'); $this->tester->clickByText('Advanced search'); $this->tester->selectOptionByCssAndValue('.js-search-rule-subject', $searchSubject); $this->tester->fillFieldByCss('.js-search-rule-value input', $value); $this->tester->clickByText('Search'); } public function assertFoundProductByName($productName) { $this->tester->seeInCss($productName, '.js-grid-column-name'); } public function assertFoundProductCount($productCount) { $foundProductCount = $me->countVisibleByCss('tbody .table-grid__row'); assertEquals($productCount, $foundProductCount); } } ProductSearchPage object
  • 17. class AdminProductSearchCest { public function testSearchByCatnum( AcceptanceTester $me, LoginPage $loginPage, ProductSearchPage $productSearchPage ) { $me->wantTo('search for product by catnum'); $loginPage->login(LoginPage::ADMIN_USERNAME, LoginPage::ADMIN_PASSWORD); $productSearchPage->search(ProductSearchPage::SEARCH_SUBJECT_CATNUM, '9176544MG'); $productSearchPage->assertFoundProductByName('Aquila Pramenitá voda neperlivá'); $productSearchPage->assertFoundProductCount(1); } } Acceptance test of filtering - using the ProductSearchPage
  • 18. Naming of the testing methods Testing methods don’t have to be named exactly after the tested method. It is suitable to name the methods after the tested scenario. The intention and the expectations of the test should be clear. If it’s not easy to name the testing method it might be the case you are testing too many things at once. Don’t be afraid of long names.
  • 19. Back to code! Let’s have a look at a unit test of method for adding product to the cart
  • 20. interface CartService { // … /** * @param SS6ShopBundleModelCartCart $cart * @param SS6ShopBundleModelProductProduct $product * @param int $quantity * @return SS6ShopBundleModelCartAddProductResult * @throws SS6ShopBundleModelCartInvalidQuantityException */ public function addProductToCart(Cart $cart, Product $product, $quantity); // … } Test class interface
  • 21. interface AddProductResult { /** * @param SS6ShopBundleModelCartItemCartItem $cartItem * @param bool $isNew * @param int $addedQuantity */ public function __construct(CartItem $cartItem, $isNew, $addedQuantity); /** * @return SS6ShopBundleModelCartItemCartItem */ public function getCartItem(); /** * @return bool */ public function getIsNew(); /** * @return int */ public function getAddedQuantity(); } Interface of the return value of the tested method
  • 22. class CartServiceTest extends FunctionalTestCase { // … public function testAddProductToCartInvalidFloatQuantity() { $cartService = $this->getCartService(); $product = $this->createProduct(); $cart = $this->createEmptyCart(); $addedQuantity = 1.1; $this- >setExpectedException('SS6ShopBundleModelCartInvalidQuantityException'); $cartService->addProductToCart($cart, $product, $addedQuantity); } // … } Adding to the cart unit test - original method name
  • 23. class CartServiceTest extends FunctionalTestCase { // … public function testCannotAddProductWithFloatQuantityToCart() { $cartService = $this->getCartService(); $product = $this->createProduct(); $cart = $this->createEmptyCart(); $addedQuantity = 1.1; $this- >setExpectedException('SS6ShopBundleModelCartInvalidQuantityException'); $cartService->addProductToCart($cart, $product, $addedQuantity); } // … } Adding to the cart unit test - new method name
  • 24. class CartServiceTest extends FunctionalTestCase { // … public function testAddProductToCartInvalidZeroQuantity() { $cartService = $this->getCartService(); $product = $this->createProduct(); $cart = $this->createEmptyCart(); $addedQuantity = 0; $this- >setExpectedException('SS6ShopBundleModelCartInvalidQuantityException'); $cartService->addProductToCart($cart, $product, $addedQuantity); } // … } Adding to the cart unit test - original method name
  • 25. class CartServiceTest extends FunctionalTestCase { // … public function testCannotAddProductWithZeroQuantityToCart() { $cartService = $this->getCartService(); $product = $this->createProduct(); $cart = $this->createEmptyCart(); $addedQuantity = 0; $this- >setExpectedException('SS6ShopBundleModelCartInvalidQuantityException'); $cartService->addProductToCart($cart, $product, $addedQuantity); } // … } Adding to the cart unit test - new method name
  • 26. class CartServiceTest extends FunctionalTestCase { // … public function testAddProductToCartNewProduct() { $cartService = $this->getCartService(); $product = $this->createProduct(); $cart = $this->createEmptyCart(); $addedQuantity = 2; $result = $cartService->addProductToCart($cart, $product, $addedQuantity); $this->assertTrue($result->getIsNew()); $this->assertSame($addedQuantity, $result->getAddedQuantity()); } // … } Adding to the cart unit test - original method name
  • 27. class CartServiceTest extends FunctionalTestCase { // … public function testAddProductToCartMarksNewlyAddedProductAsNewAndContainsAddedQuantity() { $cartService = $this->getCartService(); $product = $this->createProduct(); $cart = $this->createEmptyCart(); $addedQuantity = 2; $result = $cartService->addProductToCart($cart, $product, $addedQuantity); $this->assertTrue($result->getIsNew()); $this->assertSame($addedQuantity, $result->getAddedQuantity()); } // … } Adding to the cart unit test - new method name?
  • 28. class CartServiceTest extends FunctionalTestCase { // … public function testAddProductToCartMarksNewlyAddedProductAsNew() { $cartService = $this->getCartService(); $product = $this->createProduct(); $cart = $this->createEmptyCart(); $addedQuantity = 2; $result = $cartService->addProductToCart($cart, $product, $addedQuantity); $this->assertTrue($result->getIsNew()); } public function testAddProductResultContainsAddedProductQuantity() { $cartService = $this->getCartService(); $product = $this->createProduct(); $cart = $this->createEmptyCart(); $addedQuantity = 2; $result = $cartService->addProductToCart($cart, $product, $addedQuantity); $this->assertSame($addedQuantity, $result->getAddedQuantity()); } // … } Adding to the cart unit test - separating the method
  • 29. class CartServiceTest extends FunctionalTestCase { // … public function testAddProductToCartSameProduct() { $cartService = $this->getCartService(); $product = $this->createProduct(); $cart = $this->createCartWithOneItem($product); $addedQuantity = 2; $result = $cartService->addProductToCart($cart, $product, $addedQuantity); $this->assertFalse($result->getIsNew()); $this->assertSame($addedQuantity, $result->getAddedQuantity()); } // … } Adding to the cart unit test - original method name
  • 30. class CartServiceTest extends FunctionalTestCase { // … public function testAddProductToCartMarksRepeatedlyAddedProductAsNotNew() { $cartService = $this->getCartService(); $product = $this->createProduct(); $cart = $this->createCartWithOneItem($product); $addedQuantity = 2; $result = $cartService->addProductToCart($cart, $product, $addedQuantity); $this->assertFalse($result->getIsNew()); } public function testAddProductResultDoesNotContainPreviouslyAddedProductQuantity() { $cartService = $this->getCartService(); $product = $this->createProduct(); $cart = $this->createCartWithOneItem($product); $addedQuantity = 2; $result = $cartService->addProductToCart($cart, $product, $addedQuantity); $this->assertSame($addedQuantity, $result->getAddedQuantity()); } // … } Adding to the cart unit test - separating the method
  • 31. Mocking Mocks are good when simulating too complex objects. Its behavior can be controlled well directly in the test code. It is possible to use it when verifying correct communication between classes. It is good to extract its creation to a private method.
  • 32. To the code! Let’s have a look at a demonstration of a mocking in database/integration test
  • 33. interface WebService { // … /** * @param SS6ShopBundleComponentWebServiceRequest $request * @return resource */ public function getResponseStream(Request $request); // … } Mocked class interface
  • 34. class TransferProductTest extends DatabaseTestCase { // … /** * @param string $fileName * @return SS6ShopBundleComponentWebService|PHPUnit_Framework_MockObject_MockObject */ private function mockWebServiceReturningFileResource($fileName) { $transferWebServiceMock = $this->getMockBuilder(WebService::class) ->disableOriginalConstructor() ->getMock(); $filePath = __DIR__ . '/Resources/' . $fileName; $fileResource = fopen($filePath, 'r'); $transferWebServiceMock ->method('getResponseStream') ->willReturn($fileResource); return $transferWebServiceMock; } // … } Creating the mock in a private class
  • 35. class TransferProductTest extends DatabaseTestCase { // … /** * @param string $fileName * @return SS6ShopBundleModelTransferTransferFacade */ private function createTransferFacadeMockingWebServiceWithFile($fileName) { return new TransferFacade( $this->getContainer()->get(TransferRepository::class), $this->mockWebServicReturningFileResource($fileName), $this->getContainer()->get(ByteFormatter::class), $this->getContainer()->get(SqlLoggerFacade::class), $this->getContainer()->get(RepeatedTransferFacade::class), $this->getContainer()->get(TransferLoggerFactory::class), $this->getContainer()->get(EntityManager::class), $this->getContainer()->get(EntityManagerFacade::class) ); } // … } Injecting the mock into the real tested class
  • 36. class TransferProductTest extends DatabaseTestCase { /** * @var SS6ShopBundleModelTransferProductProductTransferProcessor */ private $productTransferProcessor; /** * @var SS6ShopBundleModelProductProductFacade */ private $productFacade; // … public function testCreateProductCreatesProduct() { $transferFacade = $this- >createTransferFacadeMockingWebServiceWithFile(self::FILE_NAME); $logger = $this->createLogger(); $transferFacade->process($this->productTransferProcessor, $logger); $product = $this->productFacade- >findOneByFloresId(self::PRODUCT_1_FLORES_ID); $this->assertNotNull($product); } // … } Intergraton/database test
  • 37. Some advice in conclusion Tests are not here in order “to exist”, they are here for you. Start with testing the most important scenarios. Well-kept demonstration data which you are going to use in the tests will help. Don’t be afraid to create special classes only for the test. Some tests are worth deleting. Having clean code in tests is equally important as having it in application code.
  • 38. Thank you for your attention Let’s get down to your questions! petr.heinz@shopsys.com