Refactoring using Codeception

Post on 24-Jun-2015

547 views 0 download

Tags:

description

You must’ve heard of Unit testing… If not, then this talk is definitely for you! If you do know Unit testing, you probably ran at some point into a hurdle: “Where do I start?” And despite your best efforts, you end up not having enough tests for your application – Then that change request comes in, requiring you to change that very same complex piece of code for which you are lacking tests! How do you going refactor while maintaining all those ‘undocumented’ business rules? This talk will show how Codeception can be leveraged to refactor the visuals aspects of an application, maintaining backwards compatibility on API changes and even assist in moving to a whole different server infrastructure.

Transcript of Refactoring using Codeception

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…

DEPENDENCY INJECTION, DECOUPLING, ENCAPSULATION, TESTABLE CODE

REFACTORING

A SIMPLE BDD STYLE TESTING FRAMEWORK WHICH IS EASY TO READ, WRITE AND DEBUG

WHAT IS CODECEPTION?

YOU WRITE IN YOUR FAVORITE EDITOR

VENDOR/BIN/CODECEPT BOOTSTRAP

COMPOSER INSTALL CODECEPTION/CODECEPTION

class_name: AcceptanceTester modules: enabled: - PhpBrowser - AcceptanceHelper config: PhpBrowser: url: 'http://www.zendcon.com/'

ACCEPTANCE.SUITE.YML

PAGE USED FOR TESTING

public function seeIfNameExists(\AcceptanceTester $I) { $I->wantTo('see if conference name exists'); $I->amOnPage(‘/'); $I->click(‘#rt-logo‘); $I->see('zendcon'); }

CODECEPT GENERATE:CEST ACCEPTANCE HOME

STEPS

GENERATED FILE EXTENDS ACCEPTANCETESTER CLASS

CODECEPT GENERATE:STEPOBJECT ACCEPTANCE <NAME>

class CompareSteps extends \AcceptanceTester { public function seeIfNameExists() { $I = $this; $I->amOnPage('/'); $I->see('zendcon'); } }

class MenuCest { public function seeIfNameExistsViaCCStep(\CompareSteps $I) { $I->seeIfNameExists(); } }

REUSE CODE FOR DIFFERENT TESTS

PAGE OBJECTS

GENERATED FILE IS JUST A CONTAINER

CODECEPT GENERATE:PAGEOBJECT ACCEPTANCE <NAME>

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); } }

PAGE OBJECT CONTAINER

public function seeIfNameExistsViaPageObject() { HomePage::of($this)->see('zendcon'); }

USE THE OBJECT IN A TEST

VERSION COMPARISON

FROM A TECHNICAL PERSPECTIVE

MASTER !== RELEASE/NEXTGEN

FROM A FUNCTIONAL PERSPECTIVE

MASTER === RELEASE/NEXTGEN

LOST OF CODE COMING UP…

ATTENTION PLEASE

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); }

OVERRIDE DEFAULT CRAWLER

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; }

CREATE SECOND PHPBROWSER INSTANCE

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); }

GET HTML OF BOTH VERSIONS

class_name: AcceptanceTester modules: enabled: - PhpBrowser - AcceptanceHelper config: PhpBrowser: url: 'http://www.zendcon.com/' alternate-url: 'http://zendcon.com'

ADDING ALTERNATE URL

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) ); }

COMPARING 2 VERSIONS IN 1 RUN

public function seeIfPageHeaderIsIdentical(\CompareSteps $I) { $I->seeSameOnVersions( \HomePage::$URL, 'h2', 'h2', 'Homepage header not identical' ); }

TEST PAGE HEADER

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/">

TEST SIGNUP FORM

RUNNING THE TESTS!

EXAMPLES?

USER SPECIFIC SETTINGS

CODECEPTION.YML

VS

CODECEPTION.DIST.YML

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') )); }

/** * @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') )); }

SPOT THE DIFFERENCE

env: beta: config: data: accept: application/vnd.github.beta+json version3: config: data: accept: application/vnd.github.v3+json

SUITE CONFIG ADDITIONS

TESTING 2 API VERSION IN 1 RUN

CODECEPT RUN API —ENV BETA —ENV VERSION3

EXAMPLES?

READY TO DIG DEEPER?

USING MODULES

USE YOUR IMAGINATION

CODECEPT GENERATE:SUITE

class MigrateHelper extends \Codeception\Module { 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'); } }

FILESYSTEM MODULE

class MigrateHelper extends \Codeception\Module { 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 \Codeception\Module { 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'); } }

CLI MODULE

class MigrateHelper extends \Codeception\Module { 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'); } }

FTP MODULE

FTP MODULE SIGNS IN BEFORE EVERY TEST

CAVEAT!

RUNNING THE TESTS!

GITHUB.COM/JVANDIJK/ZC14-CODECEPTION

JOIND.IN/11997