symfony (unit, functional) testing
TRANSCRIPT
Symfony Testing
Basel [email protected]
Why Testing?Prevent Bugs. (Reputation)
Prevent Fixing same Bugs more than once. (add additional cases each time)
Faster than manual testing.
Additional Documentation.
Upgrading your platform? Refactoring your Code? NO PROBLEM
Keeping your code quality.. Spaghetti Code is not testable ;)
Making QA jobless
1- Unit TestingUnit tests are used to test your “business logic”
If you want the design
of your code to be
better ... write Unit Tests
“Symfony Live London 2015 - Ciaran McNulty”
Most Used Assertions
AssertTrue/AssertFalseAssertEquals(AssertGreaterThan, LessThan)(GreaterThanOrEqual, LessThanOrEqual) AssertContainsAssertTypeAssertNullAssertFileExistsAssertRegExp
<?php
namespace TDD;
class Receipt {
public function total(array $items = []) {
return array_sum($items);
}
}
<?php
namespace TDD\Test;
use PHPUnit\Framework\TestCase;
use TDD\Receipt;
class ReceiptTest extends TestCase {
public function testTotal() {
$Receipt = new Receipt();
$this->assertEquals(
14,
$Receipt->total([0,2,5,8]),
'When summing the total should equal 15'
);
}
}
PHPUnit outcomes.
Printed when the test succeeds.
F
Printed when an assertion fails while running the test method.
E
Printed when an error occurs while running the test method.
S
Printed when the test has been skipped.
I
Printed when the test is marked as being incomplete or not yet implemented
PhpUnit Command linePhpunit
--filter=
--testsuite= <name,...>
--coverage-html <dir>
Other configurations are imported from phpunit.xml (next slide)
#
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="app/autoload.php" backupGlobals="false" colors="true" verbose="true"
stopOnFailure="false">
<php>
<server name="KERNEL_DIR" value="/app/" />
<env name="SYMFONY_DEPRECATIONS_HELPER" value="weak" />
</php>
<testsuites>
<!-- compose tests into test suites -->
<testsuite name="Project Test Suite">
<!-- You can either choose directory or single files -->
<directory>tests</directory>
<file>tests/General/ApplicationAvailabilityFunctionalTest.php</file>
</testsuite>
</testsuites>
<!--- This part is for code coverage (choose which directory to check and which to execlude from
coverage)-->
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src/AcmeBundle/Repository</directory>
<exclude>
<directory suffix=".php">./src/AcmeBundle/Tests</directory>
</exclude>
</whitelist>
</filter>
</phpunit>
Arrange-Act-Assert
General Principles
Test In Isolation
Test on thing per Function
If hard to test, reimplement your code
class ReceiptTest extends TestCase {
public function setUp() {
$this->Receipt = new Receipt();
}
public function tearDown() {
unset($this->Receipt);
}
public function testTotal() {
//arrange
$input = [0,2,5,8];
//ACT
$output = $this->Receipt->total($input);
//Assert
$this->assertEquals(
15,
$output,
'When summing the total should equal 15');
}}
//Test Function
public function testTax() {
$inputAmount = 10.00;
$taxInput = 0.10;
$output = $this->Receipt->tax($inputAmount, $taxInput);
$this->assertEquals(
1.00,
$output,
'The tax calculation should equal 1.00'
);
}
//Actual Function
public function tax($amount, $tax) {
return ($amount * $tax);
}
Data Providers/**
* @dataProvider provideTotal
*/
public function testTotal($items, $expected) {
$coupon = null;
$output = $this->Receipt->total($items, $coupon);
$this->assertEquals(
$expected,
$output,
"When summing the total should equal {$expected}"
);
}
public function provideTotal() {
return [
[[1,2,5,8], 16],
[[-1,2,5,8], 14],
[[1,2,8], 11],
];
}
Test DoublesReplace Dependency (Isolation)
Ensure a condition occurs
Improve the performance of our tests
Dummy
Fake
Stub
Spy
Mock
Dummy
● Dummy objects are passed around but never actually used. Usually they are just used to fill parameter lists.
Eg: empty $config array
Fake
● Fake objects actually have working implementations, but usually take some shortcut which makes them not suitable for production.
Eg: in memory database
Stub
● Stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test.
Eg: stub for isConnected() which returns false.
Spy
● Spies are stubs that also record some information based on how they were called.
Eg: an email service that records how many messages it was sent.
Mock
● Mocks objects pre-programmed with expectations which form a specification of the calls they are expected to receive.
public function total(array $items = [], $coupon) {
$sum = array_sum($items);
if (!is_null($coupon)) {
return $sum - ($sum * $coupon);
}
return $sum;
}
public function testTotal() {
$input = [0,2,5,8];
$coupon = null;
$output = $this->Receipt->total($input, $coupon);
$this->assertEquals(
15,
$output,
'When summing the total should equal 15'
);
}
public function testTotalAndCoupon() {
$input = [0,2,5,8];
$coupon = 0.20;
$output = $this->Receipt->total($input, $coupon);
$this->assertEquals(
12,
$output,
'When summing the total should equal 12'
);
}
Dummy
public function total(array $items = [], $coupon) {
$sum = array_sum($items);
if (!is_null($coupon)) {
return $sum - ($sum * $coupon);
}
return $sum;
}
public function tax($amount, $tax) {
return ($amount * $tax);
}
public function postTaxTotal($items, $tax, $coupon) {
$subtotal = $this->total($items, $coupon); //total -> 10
$subtotal += $tax;
return $subtotal + $this->tax($subtotal, $tax); //tax -> 1
}
public function testPostTaxTotal() {
$Receipt = $this->getMockBuilder('TDD\Receipt')
->setMethods(['tax', 'total'])
->getMock();
$Receipt->method('total')
->will($this->returnValue(10.00));
$Receipt->method('tax')
->will($this->returnValue(1.00));
$result = $Receipt->postTaxTotal([1,2,5,8], 0.20, null);
$this->assertEquals(11.00, $result);
}
Stub
public function testPostTaxTotal () {
$items = [1,2,5,8];
$tax = 0.20;
$coupon = null;
$Receipt = $this->getMockBuilder('TDD\Receipt')
->setMethods(['tax', 'total'])
->getMock();
$Receipt->expects($this->once())
->method('total')
->with($items, $coupon)
->will($this->returnValue(10.00));
$Receipt->expects($this->once())
->method('tax')
->with(10.00, $tax)
->will($this->returnValue(1.00));
$result = $Receipt->postTaxTotal([1,2,5,8], 0.20, null);
$this->assertEquals(11.00, $result);
}
Mock
TDDWrite Test, Run Test, Write Code, Run Test, Repeat Until Completed
Data Fixturesphp bin/console doctrine:fixtures:load
class LoadJobsData extends AbstractFixture implements OrderedFixtureInterface
{
public function load(ObjectManager $manager)
{
$user = $manager->getRepository('UserBundle:User')->findBy(array('username' => 'friik'));
$organization = $manager->getRepository('JobsBundle:Organization')->findBy(array('name' =>
'friikcom'));
$job = new Job();
$job->setCreatedBy($user[0]);
$job->setCreatedAt();
$job->setStartDate(new \DateTime());
$job->setEndDate(new \DateTime('+1 week'));
$job->setActive(1);
$job->setJobCaption('new job for all interested');
$job->setEmplType(2);
$job->setOrganization($organization[0]);
$manager->persist($job);
$manager->flush();
}
public function getOrder()
{
return 4;
}
}
protected function setUp()
{
self::bootKernel();
$this->em = static::$kernel->getContainer()
->get('doctrine')
->getManager();}
public function testFindAllJobsOrderedByDateReturnsResults()
{
$expectedOutput = 2;
$job = $this->em
->getRepository('JobsBundle:Job')
->findAllJobsOrderedByDate()->getResult(Query::HYDRATE_ARRAY);
$this->assertCount($expectedOutput, $job);
}
protected function tearDown()
{
parent::tearDown();
$this->em->close();
$this->em = null; // avoid memory leaks
}
}
2- Functional Testingcheck the integration of the different layers of an
application
Define a client then Request/**
* @dataProvider urlProvider
*/
public function testPageIsSuccessful($url)
{
$client = self::createClient();
$client->request('GET', $url);
$this->assertTrue($client->getResponse()->isSuccessful());
}
public function urlProvider()
{
return array(
array('/jobs'),
array('/jobs/organization/show'),
array('/jobs/organization/add'),
array('/jobs/organization/check'),
);
public function testIndexPageRequestAuthorized()
{
$client = static::createClient();
$crawler = $client->request('GET', '/jobs/job', array(), array(), array(
'PHP_AUTH_USER' => 'friik',
'PHP_AUTH_PW' => 'friikfriik',
));
$crawler = $client->followRedirect();
$response = $client->getResponse();
$this->assertEquals(200, $response->getStatusCode());
}
Authorized client
public function testOrganizationShowExampleTests()
{
$client = static::createClient();
$crawler = $client->request('GET', '/jobs/organization/show');
$response = $client->getResponse();
//Status Code
// $this->assertEquals(200, $response->getStatusCode());
//Check if page contains content
//$this->assertContains('company', $response->getContent());
$img = $crawler->filterXPath('//img');
$atr = $img->attr('src');
//check for alt attr in image tags
$crawler->filter('img')->each(function ($node, $i) {
$alt = $node->attr('alt');
$this->assertNotNull($alt);
});
}