Experiences, Best Pratices How to setup Unit, Functional and Acceptance Tests with PHPUnit and Codeception for TYPO3 Applications. Describes many Codeception modules + Integration with Travis CI
2. Software
Testing
PHPUnit, PHPCS, Codeception, PHPMD,
PHPStan, PHPLint, PHPCPD, Selenium, ...
2 1 • 1 1 • 1 7
A N D R É W U T T I G T Y P O 3 U S E R G R O U P D R E S D E N
D Y N A M I C T E S T I N G
Execution of Code
U N I T T E S T S
F U N C T I O N A L T E S T S
S T A T I C T E S T I N G
Static Code Analysis, Review, No Code Execution
A C C E P T A N C E T E S T S
Functions, Classes, Isolated, No DB!
Complete System, DB, Environment, No real FE
Browser (UI), API, Technology Independent
3. 1 0 0 0 F U N C T I O N A L T E S T S
A N D R É W U T T I G T Y P O 3 U S E R G R O U P D R E S D E N
TESTING
TYPO3’S
CORE
9 1 0 0 U N I T T E S T S
C H R I S T I A N K U H N | L O L L I 4 2
H T T P S : / / T Y P O 3 . C O M / B L O G / T E S T I N G - T Y P O 3 S - C O R E - P A R T - I -
I N F R A S T R U C T U R E /
2 0 0 A C C E P T A N C E T E S T S
4. CODECEPTION
E L E G A N T A N D E F F I C I E N T
T E S T I N G F O R P H P
5. <?php
class FirstCest
{
public function frontpageWorks(AcceptanceTester $I)
{
$I->amOnPage('/');
$I->see('Home');
}
}
L A U R E N H I L L S L I D E 0 6A N D R É W U T T I G T Y P O 3 U S E R G R O U P D R E S D E N
6. composer require "codeception/codeception" --dev
L A U R E N H I L L S L I D E 0 6A N D R É W U T T I G T Y P O 3 U S E R G R O U P D R E S D E N
I N S T A L L A T I O N V I A C O M P O S E R
I N S T A L L P H A R G L O B A L L Y
sudo curl -LsS http://codeception.com/codecept.phar -o
/usr/local/bin/codecept
sudo chmod a+x /usr/local/bin/codecept
7. codecept bootstrap
L A U R E N H I L L S L I D E 0 6A N D R É W U T T I G T Y P O 3 U S E R G R O U P D R E S D E N
S E T U P
/tests
/acceptance
/functional
/unit
acceptance.suite.yml
functional.suite.yml
unit.suite.yml
codeception.yml
8. codecept bootstrap
L A U R E N H I L L S L I D E 0 6A N D R É W U T T I G T Y P O 3 U S E R G R O U P D R E S D E N
S E T U P
T Y P O 3 E X A M P L E 1
EXT:Example
..
/Tests
/Acceptance
/tests
/acceptance
/api
acceptance.suite.yml
api.suite.yml
codeception.yml
/Functional
/Unit
..
EXT:Example
..
/tests
/acceptance
/functional
/unit
acceptance.suite.yml
functional.suite.yml
unit.suite.yml
codeception.yml
..
T Y P O 3 E X A M P L E 2
codecept init api
codecept init acceptance
9. codeception.yml
paths:
tests: tests
output: tests/_output
data: tests/_data
support: tests/_support
envs: tests/_envs
settings:
colors: true
memory_limit: 1024M
actor_suffix: Tester
extensions:
enabled:
- CodeceptionExtensionRunFailed
A N D R É W U T T I G T Y P O 3 U S E R G R O U P D R E S D E N
acceptance.suite.yml
actor: AcceptanceTester
modules:
enabled:
- PhpBrowser:
url: http://localhost:8080/
- HelperAcceptance
10. codeception.yml (2)
modules:
enabled:
- Db:
dsn: 'mysql:host=localhost;dbname=%CODECEPT_DB_NAME%;charset=utf8'
user: %CODECEPT_DB_USER%
password: %CODECEPT_DB_PASSWORD%
dump: tests/_data/dump.sql
cleanup: true
populate: true
populator: 'mysql -u $user -p$password $dbname < $dump'
reconnect: true
- Asserts
- PortrinoCodeceptionModuleTypo3
depends: Asserts
- REST
depends: PhpBrowser
url: http://%CODECEPT_DOMAIN%/api/
A N D R É W U T T I G T Y P O 3 U S E R G R O U P D R E S D E N
11. L A U R E N H I L L S L I D E 0 6A N D R É W U T T I G T Y P O 3 U S E R G R O U P D R E S D E N
G E N E R A T E T E S T S K E L E T O N S
codecept generate:cept acceptance First
<?php
$I = new AcceptanceTester($scenario);
$I->wantTo('perform actions and see result');
12. L A U R E N H I L L S L I D E 0 6A N D R É W U T T I G T Y P O 3 U S E R G R O U P D R E S D E N
G E N E R A T E T E S T S K E L E T O N S
codecept generate:cest acceptance First
<?php
class FirstCest
{
public function _before(AcceptanceTester $I)
{
}
public function _after(AcceptanceTester $I)
{
}
public function tryToTest(AcceptanceTester $I)
{
}
}
13. L A U R E N H I L L S L I D E 0 6A N D R É W U T T I G T Y P O 3 U S E R G R O U P D R E S D E N
W R I T E T E S T S
<?php
class FirstCest
{
public function frontpageWorks(AcceptanceTester $I)
{
$I->wantTo('Open TYPO3Camp Website');
$I->amOnUrl('https://www.typo3camp-mitteldeutschland.de/');
$I->seeInTitle('TYPO3Camp Mitteldeutschland');
$I->seeElement('.navbar');
}
}
14. L A U R E N H I L L S L I D E 0 6A N D R É W U T T I G T Y P O 3 U S E R G R O U P D R E S D E N
R U N T E S T S
codecept run
codecept run --debug
codecept run api
codecept run acceptance FirstCest:frontpageWorks
codecept run tests/acceptance/FirstCest.php::frontpageWorks
codecept run -g fast
codecept run --env chrome
codecept run --xml
15. L A U R E N H I L L S L I D E 0 6A N D R É W U T T I G T Y P O 3 U S E R G R O U P D R E S D E N
C O D E C E P T I O N S Y N T A X
$I->click('Login');
$I->fillField('#input-username', 'John Dough');
$I->pressKey('#input-remarks', 'foo');
A C T I O N S
$I->see('Welcome');
$I->seeInTitle('My Company');
$I->seeElement('nav');
$I->dontSeeElement('#error-message');
$this->assertEquals(123, $foo);
A S S E R T I O N S
16. L A U R E N H I L L S L I D E 0 6A N D R É W U T T I G T Y P O 3 U S E R G R O U P D R E S D E N
A C T O R S
UnitTester, FunctionalTester, AcceptanceTester
Methods are taken from Codeception Modules
actor: AcceptanceTester
modules:
enabled:
- PhpBrowser:
url: http://localhost/myapp/
- HelperAcceptance
acceptance.suite.yml:
17. + high quality
+ less bugs
+ better maintainability
+ easy refactoring
+ fun
P R O
L A U R E N H I L L S L I D E 0 1
- high experience
- high effort
- consequent
C O N T R A
A N D R É W U T T I G T Y P O 3 U S E R G R O U P D R E S D E N
T E S T D R I V E N D E V E L O P M E N T ?
19. L A U R E N H I L L S L I D E 0 6A N D R É W U T T I G T Y P O 3 U S E R G R O U P D R E S D E N
P H P U N I T O R C O D E C E P T I O N ?
TYPO3 Unit Testing is easier with pure PHPUnit
https://github.com/TYPO3/testing-framework
https://github.com/Nimut/testing-framework
F R A M E W O R K ?
W H E R E ?
EXT:Foo/Tests/Unit/
EXT:Foo/Tests/Unit/ViewHelpers/RenderViewhelperTest.php
EXT:Foo/Tests/Unit/Fixtures/test_data.txt
20. L A U R E N H I L L S L I D E 0 6A N D R É W U T T I G T Y P O 3 U S E R G R O U P D R E S D E N
class FooViewHelperTest extends NimutTestingFrameworkTestCaseUnitTestCase
{
protected function setUp()
{
}
protected function tearDown()
{
}
/**
* @test
*/
public function renderNothingWhenNothingToRender()
{
$this->assertEquals(1,1);
}
}
22. L A U R E N H I L L S L I D E 0 6A N D R É W U T T I G T Y P O 3 U S E R G R O U P D R E S D E N
class FooViewHelperTest extends NimutTestingFrameworkTestCaseUnitTestCase
{
protected function setUp()
{
$environementServiceMock = $this->getMockBuilder(EnvironmentService::class)->setMethods(
[
'isEnvironmentInCliMode'
])->getMock();
$environementServiceMock->expects(static::any())
->method('isEnvironmentInCliMode')
->willReturn(true);
GeneralUtility::setSingletonInstance(
EnvironmentService::class, $environementServiceMock
);
}
}
PHPUnit MockObjects
23. L A U R E N H I L L S L I D E 0 6A N D R É W U T T I G T Y P O 3 U S E R G R O U P D R E S D E N
class FooViewHelperTest extends NimutTestingFrameworkTestCaseUnitTestCase
{
protected function setUp()
{
$environmentServiceMock = $this->prophesize(EnvironmentService::class);
$environmentServiceMock->isEnvironmentInCliMode()->willReturn(false);
GeneralUtility::setSingletonInstance(
EnvironmentService::class, $environmentService->reveal()
);
}
Prophecy
25. L A U R E N H I L L S L I D E 0 6A N D R É W U T T I G T Y P O 3 U S E R G R O U P D R E S D E N
modules:
enabled:
- Db:
dsn: 'mysql:host=localhost;dbname=%CODECEPT_DB_NAME%;charset=utf8'
user: %CODECEPT_DB_USER%
password: %CODECEPT_DB_PASSWORD%
dump: tests/_data/dump.sql
cleanup: true
populate: true
populator: 'mysql -u $user -p$password $dbname < $dump' #faster ;-)
reconnect: true
Database - Configuration
26. L A U R E N H I L L S L I D E 0 6A N D R É W U T T I G T Y P O 3 U S E R G R O U P D R E S D E N
$I->seeInDatabase('fe_users', array('name' => 'Andre', 'email' => 'wuttig@mail.com'));
$I->dontSeeInDatabase('fe_users', array('name' => 'Andre', 'email' => 'wuttig@mail.com'));
$mail = $I->grabFromDatabase('fe_users', 'email', array('name' => 'Andre'));
$I->haveInDatabase('fe_users', array('name' => 'miles', 'email' => 'wuttig@mail.com'));
$I->seeNumRecords(1, 'fe_users', ['name' => 'andre']);
$I->updateInDatabase('fe_users', ['isAdmin' => true], ['email' => 'miles@davis.com']);
Database Testing - Actions / Assertions / Grabbers
27. L A U R E N H I L L S L I D E 0 6A N D R É W U T T I G T Y P O 3 U S E R G R O U P D R E S D E N
#!/bin/bash
PASSWORD=$CODECEPT_DB_PASSWORD
HOST=$CODECEPT_DB_HOST
USER=$CODECEPT_DB_USER
DATABASE=$CODECEPT_DB_NAME
DB_FILE=dump.sql
EXCLUDED_TABLES=(
be_sessions
cf_cache_hash
sys_log
...
)
...
Database Testing - Creating Testdata
https://gist.github.com/aWuttig/58599ae722a5a993172b39046ca07d0a
dump.sh
28. L A U R E N H I L L S L I D E 0 6A N D R É W U T T I G T Y P O 3 U S E R G R O U P D R E S D E N
modules:
enabled:
- REST:
depends: PhpBrowser
url: 'http://serviceapp/api/v1/'
REST API - Configuration
29. L A U R E N H I L L S L I D E 0 6A N D R É W U T T I G T Y P O 3 U S E R G R O U P D R E S D E N
$I->amHttpAuthenticated('jon_snow', 'targaryen');
$I->haveHttpHeader('X-Requested-With', 'Codeception');
$I->sendGET('user');
$I->sendPOST('/user', 'name' => 'andre'));
$I->sendPUT('/user', array('id' => 123, 'name' => 'andré'));
$I->sendDELETE('/user/123');
REST API - Actions / Assertions / Grabbers
$I->seeResponseCodeIs(200);
$I->seeResponseContains('foo');
$I->seeHttpHeaderOnce('Cache-Control');
$I->seeResponseContainsJson(array('name' => 'john'));
$I->seeResponseJsonMatchesJsonPath('$.data.0.name');
$I->seeResponseMatchesJsonType(['user_id' => 'integer','is_active' => 'boolean']);
43. Mailhog https://github.com/mailhog/MailHog
W E B A P I B A S E D S M T P
T E S T I N G
A N D R É W U T T I G T Y P O 3 U S E R G R O U P D R E S D E N
https://github.com/ericmartel/codeception-email-mailhog
44. Mailhog - Web API based SMTP Testing
"require-dev": {
"ericmartel/codeception-email-mailhog": "^1.0"
}
modules:
enabled:
- MailHog:
url: %CODECEPT_DOMAIN%
port: '8025'
A N D R É W U T T I G
45. Mailhog - Web API based SMTP Testing
$I->fetchEmails();
$I->accessInboxFor('testuser@example.com');
$I->haveEmails();
$I->haveUnreadEmails();
$I->seeInOpenedEmailSubject('Your Password Reset Link');
$I->seeInOpenedEmailBody('Follow this link to reset your password');
$I->seeInOpenedEmailRecipients('testuser@example.com');