Why Teams call analytics are critical to your entire business
Nashville Symfony Functional Testing
1. FUNCTIONAL TESTING
(overview)
Functional tests validate parts of your applications.
Functional tests simulate a browsing session to assert
desired user outcomes. They automate requests and check
elements in the response. It is advantageous to write your
functional tests to correspond to individual user stories.
User Story:
As an Administrator I want to create a
company so I can manage companies
Wednesday, January 6, 2010
2. WHY WRITE THEM?
- BETTER TEST COVERAGE!!
- Cover areas unit tests can’t reach
- Unit Tests: Model Layer
- Functional Tests: View and Control Layers
- Mimic Functional QA
- Write tests according to client-approved user
stories.
Wednesday, January 6, 2010
3. TOOLS FOR FUNCTIONAL TESTING
Cucumber
“Cucumber is designed to allow you to execute feature documentation
written in plain text (often known as ‘stories’).”
•We will discuss this later
Selenium
“Selenium is written in JavaScript; it's designed to run in a real browser to
test compatibility with a real browser”
•Uses a real browser
•Writes test in JavaScript, can play back through any browser
Wednesday, January 6, 2010
4. FUNCTIONAL TESTING IN SYMFONY
- Bootstrap Symfony Core
- sfBrowser
- Symfony class simulating a true browser
- Testing Blocks
- sfTestFunctional split into multiple classes
- sfTesterResponse, sfTesterRequest, sfTesterDoctrine, sfTesterUser, sfTesterForm
- Extend the functional test framework for
custom functionality
Wednesday, January 6, 2010
5. EXTEND THE TEST FRAMEWORK
Subclass sfTestFunctional
class csTestFunctional extends sfTestFunctional
{
public function clickAndCheck($link, $module, $action, $statusCode = 200)
{
return $this->click($link)->isModuleAction($module, $action, $statusCode);
}
public function isModuleAction($module, $action, $statusCode = 200)
{
$this->with('request')->begin()->
isParameter('module', $module)->
isParameter('action', $action)->
end()->
with('response')->begin()->
isStatusCode($statusCode)->
end();
return $this;
}
}
Wednesday, January 6, 2010
6. USE FIXTURES
// test/data/urls.yml
-
url: /blog Create a YAML file to hold
module: blog
action: index
statusCode: 200
data
-
url: /blog/new
module: blog
action: new
AND...
statusCode: 200
-
url: /blog/edit
module: blog
action: edit
Simplify Repetitive Tests!
statusCode: 401
// test/functional/frontend/routeTest.php
foreach(sfYaml::load(sfConfig::get('sf_test_dir').'/data/forms.yml') as $route)
{
$browser
->get($route['url'])
->isModuleAction($route['module'], $route['action'], $route['statusCode'])
;
}
Wednesday, January 6, 2010
7. CREATE YOUR OWN TESTER
Extend sfTesterForm
class csTesterForm extends sfTesterForm
{
// Called at every occurence of $browser->with('form')
public function initialize()
{
// Load form into local variable
parent::initialize();
}
// Called when a page is requested
// (at every occurence of $browser->call())
public function prepare()
{
}
public function fill($name, $values = array())
{
$formValues = Doctrine_Lib::arrayDeepMerge($this->getFormValues($name), $values);
foreach ($formValues as $key => $value)
{
$this->setDefaultField($key, $value);
}
return $this->getObjectToReturn();
}
}
Wednesday, January 6, 2010
8. Extend sfTesterForm (continued)
// test/data/forms.yml
login_bad:
signin:
username: admin
password: wrongpassword
login:
signin:
Create Form Fixtures
username: admin
password: rightpassword
login2:
signin[username]: admin
signin[password]: rightpassword Load them into your Tester!
// lib/test/csTesterForm.class.php
public function __construct(sfTestFunctionalBase $browser, $tester)
{
parent::__construct($browser, $tester);
if (file_exists(sfConfig::get('sf_test_dir').'/data/forms.yml'))
{
$this->setFormData(sfYaml::load(sfConfig::get('sf_test_dir').'/data/forms.yml'));
}
}
// test/functional/backend/loginTest.php
$browser = new csTestFunctional(new sfBrowser(), null, array('form' => 'csTesterForm'));
Instantiate Your Classes
Wednesday, January 6, 2010
9. USE FACTORIES
Helpful classes to generate objects and data
class sfFactory
{
protected static $lastRandom = null;
public static function generate($prefix = '')
{
self::$lastRandom = $prefix.rand();
return self::$lastRandom;
}
public static function last()
{
if (!self::$lastRandom)
{
throw new sfException("No previously generated random available");
}
return self::$lastRandom;
}
}
Wednesday, January 6, 2010
10. PUTTING IT ALL TOGETHER
$browser = new csTestFunctional(new sfBrowser(), null, array('form' => 'csTesterForm', 'doctrine' =>
'sfTesterDoctrine'));
$browser->info('Create a new Company');
Initialize your testers
$browser
->login()
->get('/company/new')
->with('form')->begin()
->fill('company', array('company[name]' => sfFactory::generate('Company')))
->end()
Fill the form with a
->info('Create Company "'.sfFactory::last().'"') unique value
->click('Save and add')
->with('form')->begin()
->hasErrors(false)
->end()
->followRedirect() Verify record exists
->with('doctrine')->begin()
->check('Company', array('name' => sfFactory::last()))
->end()
;
Wednesday, January 6, 2010
12. CAN YOU THINK OF OTHER USEFUL TESTERS?
Wednesday, January 6, 2010
13. USE PLUGINS
swFunctionalTestGenerationPlugin
Add to your dev toolbar
Quickly stub symfony functional tests while
clicking through the browser
Wednesday, January 6, 2010
14. SO YOU WANNA TEST YOUR PLUGINS?
sfTaskExtraPlugin
$ ./symfony generate:plugin sfScraperPlugin
--module=sfScraper
--test-application=frontend
Plugin Skeleton Test Fixtures
Wednesday, January 6, 2010
15. sfTaskExtraPlugin (continued)
Run all plugin tests
$ ./symfony test:plugin sfScraperPlugin
Connect plugin tests in ProjectConfiguration and....
// config/ProjectConfiguration.class.php
public function setupPlugins()
{
$this->pluginConfigurations['sfScraperPlugin']->connectTests();
}
Run plugin tests alongside project tests
$ ./symfony test:all
Run tests individually
$ ./symfony test:unit sfScraper
$ ./symfony test:functional frontend sfScraperActions
Wednesday, January 6, 2010
16. A LITTLE TASTE OF CUCUMBER
Scenario: See all vendors
Uses “Scenarios”
Given I am logged in as a user in the administrator role
And There are 3 vendors
When I go to the manage vendors page
Then I should see the first 3 vendor names
that are then $this->given("I am logged in as a user in the (.*) role",
function($browser, $matches) {
$browser->get('/')
interpreted using ->with('form')->begin()
->fill('login_'.$matches[1])
->end()
“Steps” ;
});
->click('Sign in')
Wednesday, January 6, 2010
17. CHECK OUT MORE
Selenium plugin to integrate with Symfony
- http://github.com/jatimo/cesTestSelenium
• Cucumber -
http://wiki.github.com/aslakhellesoy/cucumber
• Poocumber! -
http://github.com/bshaffer/Symfony-Snippets/tree/master/Poocumber/
• Presentation Notes -
http://github.com/bshaffer/Symfony-Snippets/tree/master/Presentation-Notes/
2010-02-05-FunctionalTesting
Wednesday, January 6, 2010