REFACTORING USING CODECEPTION
$I->CANSEE(‘JEROEN’, ‘#VANDIJK’)
$I->USETWITTER(‘@JRVANDIJK’)
$I->AM(‘PHPBNL’, ‘.BOARD-MEMBER’)
$I->WORK(‘#ENRISE’)
NETHERLANDS UNITED KINGDOM BELGIUM GERMANY AMSTERDAM
NETHERLANDS UNITED KINGDOM BELGIUM GERMANY AMSTERDAM NOT AMSTERDAM
NETHERLANDS UNITED KINGDOM BELGIUM GERMANY AMERSFOORT
GRAIN WAREHOUSE
REFACTORED
THIS TALK IS NOT…
THIS TALK IS NOT… REFACTORING DEPENDENCY INJECTION, DECOUPLING, ENCAPSULATION, TESTABLE CODE
WHAT IS CODECEPTION? A SIMPLE BDD STYLE TESTING FRAMEWORK WHICH IS EASY TO READ, WRITE AND DEBUG
YOU WRITE IN YOUR FAVORITE EDITOR
COMPOSER INSTALL CODECEPTION/CODECEPTION VENDOR/BIN/CODECEPT BOOTSTRAP
ACCEPTANCE.SUITE.YML class_name: AcceptanceTester modules: enabled: - PhpBrowser - AcceptanceHelper config: PhpBrowser: url: 'http://www.zendcon.com/'
PAGE USED FOR TESTING
CODECEPT GENERATE:CEST ACCEPTANCE HOME public function seeIfNameExists(AcceptanceTester $I) { $I->wantTo('see if conference name exists'); $I->amOnPage(‘/'); $I->click(‘#rt-logo‘); $I->see('zendcon'); }
STEPS
CODECEPT GENERATE:STEPOBJECT ACCEPTANCE <NAME> GENERATED FILE EXTENDS ACCEPTANCETESTER CLASS
REUSE CODE FOR DIFFERENT TESTS class CompareSteps extends AcceptanceTester { public function seeIfNameExists() { $I = $this; $I->amOnPage('/'); $I->see('zendcon'); } } class MenuCest { public function seeIfNameExistsViaCCStep(CompareSteps $I) { $I->seeIfNameExists(); } }
PAGE OBJECTS
CODECEPT GENERATE:PAGEOBJECT ACCEPTANCE <NAME> GENERATED FILE IS JUST A CONTAINER
PAGE OBJECT CONTAINER class HomePage { public static $URL = '/'; … // removed code for slide layout purposes public static function of(AcceptanceTester $I) { return new static($I); } public function see($value) { $I = $this->acceptanceTester; $I->amOnPage(self::$URL); $I->see($value); } }
USE THE OBJECT IN A TEST public function seeIfNameExistsViaPageObject() { HomePage::of($this)->see('zendcon'); }
VERSION COMPARISON
MASTER !== RELEASE/NEXTGEN FROM A TECHNICAL PERSPECTIVE
MASTER === RELEASE/NEXTGEN FROM A FUNCTIONAL PERSPECTIVE
ATTENTION PLEASE LOST OF CODE COMING UP…
OVERRIDE DEFAULT CRAWLER public function getHtmlFromContent(InnerBrowser $innerBrowser, $css) { $crawler = $this->getCrawler($innerBrowser); $selector = CssSelector::toXPath($css); $value = $crawler->filterXPath($selector); return $value->html(); } protected function getCrawler(InnerBrowser $innerBrowser) { $reflection = new ReflectionClass(get_class($innerBrowser)); $property = $reflection->getProperty('crawler'); $property->setAccessible(true); return $property->getValue($innerBrowser); }
CREATE SECOND PHPBROWSER INSTANCE protected function getPhpBrowserByPage($page) { $phpBrowser = $this->getAlternatePhpBrowser(); $phpBrowser->amOnPage($page); return $phpBrowser; } protected function getAlternatePhpBrowser() { $config = Configuration::config(); $suite = Configuration::suiteSettings('acceptance', $config); $options = $suite['modules']['config']['PhpBrowser']; $options['url'] = $options['alternate-url']; $phpBrowser = new PhpBrowser($options)->_initialize(); $this->setProxyInGuzzle($phpBrowser->guzzle); return $phpBrowser; }
GET HTML OF BOTH VERSIONS public function getHtml($page, $path) { $I = $this; $I->amOnPage($page); return $this->getHtmlFromContent( $I->fetchModule('PhpBrowser'), $path); } public function getAlternateHtml($page, $path) { return $this->getHtmlFromContent( $this->getPhpBrowserByPage($page), $path); }
ADDING ALTERNATE URL class_name: AcceptanceTester modules: enabled: - PhpBrowser - AcceptanceHelper config: PhpBrowser: url: 'http://www.zendcon.com/' alternate-url: 'http://zendcon.com'
COMPARING 2 VERSIONS IN 1 RUN public function seeSameOnVersions($page, $path, $altPath, $message) { $I = $this; list($left, $right) = $this->getContentFromVersions( $page, $path, $altPath); $I->seeEquals($left, $right, $message); } public function getContentFromVersions($page, $path, $altPath) { return array( $this->getHtml($page, $path), $this->getAlternateHtml($page, $altPath) ); }
TEST PAGE HEADER public function seeIfPageHeaderIsIdentical(CompareSteps $I) { $I->seeSameOnVersions( HomePage::$URL, 'h2', 'h2', 'Homepage header not identical' ); }
TEST SIGNUP FORM public function seeIfFormActionIsIdentical(CompareSteps $I) { $I->seeSameOnVersions( HomePage::$URL, '.rsformbox1', '.rsformbox1', 'Homepage signup form not identical' ); }
TEST SIGNUP FORM public function seeIfFormActionIsIdentical(CompareSteps $I) { $I->seeSameOnVersions( HomePage::$URL, '.rsformbox1', '.rsformbox1', 'Homepage signup form not identical' ); } <div class="rsformbox1 title3"> - <form method=“post" id="userForm" action="http://www.zendcon.com/"> + <form method="post" id="userForm" action="http://zendcon.com/">
RUNNING THE TESTS!
EXAMPLES?
USER SPECIFIC SETTINGS
CODECEPTION.YML CODECEPTION.DIST.YML VS CODECEPTION.YML.DIST
TESTING AN API
class_name: ApiTester modules: enabled: - ApiHelper - PhpBrowser - REST config: PhpBrowser: url: https://api.github.com REST: url: https://api.github.com
public function testGetGists(ApiTester $I) { $I->wantTo('see if we can get the gists listing'); $I->haveHttpHeader('Accept', 'application/vnd.github.beta+json'); $I->sendGet('/users/weierophinney/gists'); $I->seeResponseCodeIs(200); $I->seeResponseIsJson(); } public function testGetGist(ApiTester $I) { $I->wantTo('see if we can get a gist'); $I->haveHttpHeader('Accept', 'application/vnd.github.beta+json'); $I->sendGet('/gists/2c47c9d59f4a5214f0c3'); $I->seeResponseCodeIs(200); $I->seeResponseIsJson(); }
ENVIRONMENTS
/** * @env beta */ public function testGetOldVersionGist(ApiTester $I) { $I->wantTo('see if we can get a gist'); $I->haveHttpHeader('Accept', $I->getAcceptHeader()); $I->sendGet('/gists/2c47c9d59f4a5214f0c3'); $I->seeResponseCodeIs(200); $I->seeResponseIsJson(); $I->seeResponseContainsJson( array('user' => array('login' => ‘weierophinney') )); }
SPOT THE DIFFERENCE /** * @env version3 */ public function testGetOldVersionGist(ApiTester $I) { $I->wantTo('see if we can get a gist'); $I->haveHttpHeader('Accept', $I->getAcceptHeader()); $I->sendGet('/gists/2c47c9d59f4a5214f0c3'); $I->seeResponseCodeIs(200); $I->seeResponseIsJson(); $I->seeResponseContainsJson( array('owner' => array('login' => ‘weierophinney') )); }
SUITE CONFIG ADDITIONS env: beta: config: data: accept: application/vnd.github.beta+json version3: config: data: accept: application/vnd.github.v3+json
CODECEPT RUN API —ENV BETA —ENV VERSION3 TESTING 2 API VERSION IN 1 RUN
EXAMPLES?
READY TO DIG DEEPER? USING MODULES
CODECEPT GENERATE:SUITE USE YOUR IMAGINATION
FILESYSTEM MODULE class MigrateHelper extends CodeceptionModule { public function seeIfLineExistsInFile($file, $line) { $filesystem = $this->getModule('Filesystem'); $filesystem->seeFileFound($file); $filesystem->seeInThisFile($line); } } class HostCest { public function testIfHostsFileIsConfigured(MigrateTester $I) { $I->seeIfLineExistsInFile('/etc/hosts', '127.0.0.1'); } }
CLI MODULE class MigrateHelper extends CodeceptionModule { public function seeIfPortIsReachable($host, $port) { $cli = $this->getModule('Cli'); $cli->runShellCommand('nmap '.$host.' -Pn -p '.$port); $cli->seeInShellOutput($port.'/tcp open'); } } class HostCest { public function testIfPortReachable(MigrateTester $I) { $I->seeIfPortIsReachable('www.zendcon.com', 80); } }
CLI MODULE class MigrateHelper extends CodeceptionModule { public function seeAddressIsMatchingIp($address, $ip) { $cli = $this->getModule('Cli'); $cli->runShellCommand('host '.$address); $cli->seeInShellOutput($address . ' has address '.$ip); } } class HostCest { public function testIfDnsCanBeResolved(MigrateTester $I) { $I->seeAddressIsMatchingIp('zendcon.com', '50.56.0.87'); } }
FTP MODULE class MigrateHelper extends CodeceptionModule { public function seeContentsInRemoteFile($file, $line) { $server = $this->getModule('FTP'); $server->seeFileFound(basename($file), dirname($file)); $server->openFile($file); $server->seeInThisFile($line); } } class HostCest { public function testIfRemoteFileHasContents(MigrateTester $I) { $I->seeContentsInRemoteFile('/etc/hosts', '127.0.0.1'); } }
CAVEAT! FTP MODULE SIGNS IN BEFORE EVERY TEST
RUNNING THE TESTS!
GITHUB.COM/JVANDIJK/ZC14-CODECEPTION JOIND.IN/11997

Refactoring using Codeception

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 7.
    NETHERLANDS UNITED KINGDOM BELGIUM GERMANY AMSTERDAM
  • 8.
    NETHERLANDS UNITED KINGDOM BELGIUM GERMANY AMSTERDAM NOT AMSTERDAM
  • 9.
    NETHERLANDS UNITED KINGDOM BELGIUM GERMANY AMERSFOORT
  • 10.
  • 11.
  • 12.
  • 13.
    THIS TALK ISNOT… REFACTORING DEPENDENCY INJECTION, DECOUPLING, ENCAPSULATION, TESTABLE CODE
  • 14.
    WHAT IS CODECEPTION? A SIMPLE BDD STYLE TESTING FRAMEWORK WHICH IS EASY TO READ, WRITE AND DEBUG
  • 15.
    YOU WRITE INYOUR FAVORITE EDITOR
  • 16.
    COMPOSER INSTALL CODECEPTION/CODECEPTION VENDOR/BIN/CODECEPT BOOTSTRAP
  • 17.
    ACCEPTANCE.SUITE.YML class_name: AcceptanceTester modules: enabled: - PhpBrowser - AcceptanceHelper config: PhpBrowser: url: 'http://www.zendcon.com/'
  • 18.
  • 19.
    CODECEPT GENERATE:CEST ACCEPTANCEHOME public function seeIfNameExists(AcceptanceTester $I) { $I->wantTo('see if conference name exists'); $I->amOnPage(‘/'); $I->click(‘#rt-logo‘); $I->see('zendcon'); }
  • 20.
  • 21.
    CODECEPT GENERATE:STEPOBJECT ACCEPTANCE<NAME> GENERATED FILE EXTENDS ACCEPTANCETESTER CLASS
  • 22.
    REUSE CODE FORDIFFERENT TESTS class CompareSteps extends AcceptanceTester { public function seeIfNameExists() { $I = $this; $I->amOnPage('/'); $I->see('zendcon'); } } class MenuCest { public function seeIfNameExistsViaCCStep(CompareSteps $I) { $I->seeIfNameExists(); } }
  • 23.
  • 24.
    CODECEPT GENERATE:PAGEOBJECT ACCEPTANCE<NAME> GENERATED FILE IS JUST A CONTAINER
  • 25.
    PAGE OBJECT CONTAINER class HomePage { public static $URL = '/'; … // removed code for slide layout purposes public static function of(AcceptanceTester $I) { return new static($I); } public function see($value) { $I = $this->acceptanceTester; $I->amOnPage(self::$URL); $I->see($value); } }
  • 26.
    USE THE OBJECTIN A TEST public function seeIfNameExistsViaPageObject() { HomePage::of($this)->see('zendcon'); }
  • 27.
  • 28.
    MASTER !== RELEASE/NEXTGEN FROM A TECHNICAL PERSPECTIVE
  • 29.
    MASTER === RELEASE/NEXTGEN FROM A FUNCTIONAL PERSPECTIVE
  • 30.
    ATTENTION PLEASE LOSTOF CODE COMING UP…
  • 31.
    OVERRIDE DEFAULT CRAWLER public function getHtmlFromContent(InnerBrowser $innerBrowser, $css) { $crawler = $this->getCrawler($innerBrowser); $selector = CssSelector::toXPath($css); $value = $crawler->filterXPath($selector); return $value->html(); } protected function getCrawler(InnerBrowser $innerBrowser) { $reflection = new ReflectionClass(get_class($innerBrowser)); $property = $reflection->getProperty('crawler'); $property->setAccessible(true); return $property->getValue($innerBrowser); }
  • 32.
    CREATE SECOND PHPBROWSERINSTANCE protected function getPhpBrowserByPage($page) { $phpBrowser = $this->getAlternatePhpBrowser(); $phpBrowser->amOnPage($page); return $phpBrowser; } protected function getAlternatePhpBrowser() { $config = Configuration::config(); $suite = Configuration::suiteSettings('acceptance', $config); $options = $suite['modules']['config']['PhpBrowser']; $options['url'] = $options['alternate-url']; $phpBrowser = new PhpBrowser($options)->_initialize(); $this->setProxyInGuzzle($phpBrowser->guzzle); return $phpBrowser; }
  • 33.
    GET HTML OFBOTH VERSIONS public function getHtml($page, $path) { $I = $this; $I->amOnPage($page); return $this->getHtmlFromContent( $I->fetchModule('PhpBrowser'), $path); } public function getAlternateHtml($page, $path) { return $this->getHtmlFromContent( $this->getPhpBrowserByPage($page), $path); }
  • 34.
    ADDING ALTERNATE URL class_name: AcceptanceTester modules: enabled: - PhpBrowser - AcceptanceHelper config: PhpBrowser: url: 'http://www.zendcon.com/' alternate-url: 'http://zendcon.com'
  • 35.
    COMPARING 2 VERSIONSIN 1 RUN public function seeSameOnVersions($page, $path, $altPath, $message) { $I = $this; list($left, $right) = $this->getContentFromVersions( $page, $path, $altPath); $I->seeEquals($left, $right, $message); } public function getContentFromVersions($page, $path, $altPath) { return array( $this->getHtml($page, $path), $this->getAlternateHtml($page, $altPath) ); }
  • 36.
    TEST PAGE HEADER public function seeIfPageHeaderIsIdentical(CompareSteps $I) { $I->seeSameOnVersions( HomePage::$URL, 'h2', 'h2', 'Homepage header not identical' ); }
  • 37.
    TEST SIGNUP FORM public function seeIfFormActionIsIdentical(CompareSteps $I) { $I->seeSameOnVersions( HomePage::$URL, '.rsformbox1', '.rsformbox1', 'Homepage signup form not identical' ); }
  • 38.
    TEST SIGNUP FORM public function seeIfFormActionIsIdentical(CompareSteps $I) { $I->seeSameOnVersions( HomePage::$URL, '.rsformbox1', '.rsformbox1', 'Homepage signup form not identical' ); } <div class="rsformbox1 title3"> - <form method=“post" id="userForm" action="http://www.zendcon.com/"> + <form method="post" id="userForm" action="http://zendcon.com/">
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
    class_name: ApiTester modules: enabled: - ApiHelper - PhpBrowser - REST config: PhpBrowser: url: https://api.github.com REST: url: https://api.github.com
  • 45.
    public function testGetGists(ApiTester$I) { $I->wantTo('see if we can get the gists listing'); $I->haveHttpHeader('Accept', 'application/vnd.github.beta+json'); $I->sendGet('/users/weierophinney/gists'); $I->seeResponseCodeIs(200); $I->seeResponseIsJson(); } public function testGetGist(ApiTester $I) { $I->wantTo('see if we can get a gist'); $I->haveHttpHeader('Accept', 'application/vnd.github.beta+json'); $I->sendGet('/gists/2c47c9d59f4a5214f0c3'); $I->seeResponseCodeIs(200); $I->seeResponseIsJson(); }
  • 46.
  • 47.
    /** * @envbeta */ public function testGetOldVersionGist(ApiTester $I) { $I->wantTo('see if we can get a gist'); $I->haveHttpHeader('Accept', $I->getAcceptHeader()); $I->sendGet('/gists/2c47c9d59f4a5214f0c3'); $I->seeResponseCodeIs(200); $I->seeResponseIsJson(); $I->seeResponseContainsJson( array('user' => array('login' => ‘weierophinney') )); }
  • 48.
    SPOT THE DIFFERENCE /** * @env version3 */ public function testGetOldVersionGist(ApiTester $I) { $I->wantTo('see if we can get a gist'); $I->haveHttpHeader('Accept', $I->getAcceptHeader()); $I->sendGet('/gists/2c47c9d59f4a5214f0c3'); $I->seeResponseCodeIs(200); $I->seeResponseIsJson(); $I->seeResponseContainsJson( array('owner' => array('login' => ‘weierophinney') )); }
  • 49.
    SUITE CONFIG ADDITIONS env: beta: config: data: accept: application/vnd.github.beta+json version3: config: data: accept: application/vnd.github.v3+json
  • 50.
    CODECEPT RUN API—ENV BETA —ENV VERSION3 TESTING 2 API VERSION IN 1 RUN
  • 51.
  • 52.
    READY TO DIGDEEPER? USING MODULES
  • 53.
  • 54.
    FILESYSTEM MODULE classMigrateHelper extends CodeceptionModule { public function seeIfLineExistsInFile($file, $line) { $filesystem = $this->getModule('Filesystem'); $filesystem->seeFileFound($file); $filesystem->seeInThisFile($line); } } class HostCest { public function testIfHostsFileIsConfigured(MigrateTester $I) { $I->seeIfLineExistsInFile('/etc/hosts', '127.0.0.1'); } }
  • 55.
    CLI MODULE classMigrateHelper extends CodeceptionModule { public function seeIfPortIsReachable($host, $port) { $cli = $this->getModule('Cli'); $cli->runShellCommand('nmap '.$host.' -Pn -p '.$port); $cli->seeInShellOutput($port.'/tcp open'); } } class HostCest { public function testIfPortReachable(MigrateTester $I) { $I->seeIfPortIsReachable('www.zendcon.com', 80); } }
  • 56.
    CLI MODULE classMigrateHelper extends CodeceptionModule { public function seeAddressIsMatchingIp($address, $ip) { $cli = $this->getModule('Cli'); $cli->runShellCommand('host '.$address); $cli->seeInShellOutput($address . ' has address '.$ip); } } class HostCest { public function testIfDnsCanBeResolved(MigrateTester $I) { $I->seeAddressIsMatchingIp('zendcon.com', '50.56.0.87'); } }
  • 57.
    FTP MODULE classMigrateHelper extends CodeceptionModule { public function seeContentsInRemoteFile($file, $line) { $server = $this->getModule('FTP'); $server->seeFileFound(basename($file), dirname($file)); $server->openFile($file); $server->seeInThisFile($line); } } class HostCest { public function testIfRemoteFileHasContents(MigrateTester $I) { $I->seeContentsInRemoteFile('/etc/hosts', '127.0.0.1'); } }
  • 58.
    CAVEAT! FTP MODULESIGNS IN BEFORE EVERY TEST
  • 59.
  • 60.