The document discusses unit testing and provides examples of writing unit tests in PHP using the PHPUnit framework. It explains what unit tests are, why they are useful for finding and preventing bugs, and different types of tests. It also demonstrates how to write tests for a "slugify" string method, use data providers to reduce duplicated code, and mock objects to test code in isolation without external dependencies like databases. The document advocates for test-driven development by writing tests before code. It also mentions how to integrate tests with version control using hooks and generate code coverage reports.
4. Why Test?
• Sometimes we make mistakes in our code
• Sometimes we forget to finish parts of our
code because we get interrupted, and then
we never come back and finish
5. Why Test?
• Sometimes we make mistakes in our code
• Sometimes we forget to finish parts of our
code because we get interrupted, and then
we never come back and finish
• //TODO: add another reason
7. Types of testing
regression tests
functional tests
acceptance tests
integration tests
load/stress tests
security tests
unit tests
8. Unit Testing
Unit Testing: the execution of [code] that has
been written by a single programmer or team
of programmers, which is tested in isolation
from the more complete system.
- Code Complete 2nd ed.
9. How we end up testing stuff
class User{
public function __construct($userName){
//do stuff
}
public function checkPassword($password){
//do stuff
}
public function setLogIn(){
//do stuff
}
10. How we end up testing stuff
$u = new User(‘davidhaskins@ieee.org’);
//test to fail
$u->checkPassword(‘bad_password’);
//test to succeed
$u->checkPassword(‘s3kr3t_p@sswd’);
11. How we end up testing stuff
$u = new User(‘davidhaskins@ieee.org’);
//test to fail
if($u->checkPassword(‘bad_password’) === false){
echo ‘successfully rejected’;
}
//test to succeed
if($u->checkPassword(‘s3kr3t_p@sswd’) === true){
echo ‘successfully accepted’;
}
19. Installing PHPUnit
Composer can be used to download and install
“dependencies”(other libraries and such).
download composer:
curl -s http://getcomposer.org/installer | php
20. Installing PHPUnit
create a file named composer.json containing:
{
“require-dev”: {
“phpunit/phpunit”: “3.7.4”
}
}
27. class myTest extends PHPUnitTest_Framework_TestCase {
public function someSimpleTest(){
}
}
28. class myTest extends PHPUnitTest_Framework_TestCase {
public function someSimpleTest(){
$myVar = true;
$this->assertTrue($myVar);
}
}
29. class myTest extends PHPUnitTest_Framework_TestCase {
public function someSimpleTest(){
$myVar = true;
$this->assertTrue($myVar);
}
public function anotherSimpleTest(){
$myVar = false;
$this->assertTrue($myvar); //fails
}
40. Other tests we can add
public function testSluggifyReturnsExpectedForStringsContainingNumbers() {
$originalString = 'This1 string2 will3 be 44 sluggified10';
$expectedResult = 'this1-string2-will3-be-44-sluggified10';
$url = new URL();
$result = $url->sluggify($originalString);
$this->assertEquals($expectedResult, $result);
}
41. Other tests we can add
public function testSluggifyReturnsExpectedForStringsContainingSpecialCharacters() {
$originalString = 'This! @string#$ %$will ()be "sluggified';
$expectedResult = 'this-string-will-be-sluggified';
$url = new URL();
$result = $url->sluggify($originalString);
$this->assertEquals($expectedResult, $result);
}
42. Other tests we can add
public function testSluggifyReturnsExpectedForStringsContainingNonEnglishCharacters() {
$originalString = "Tänk efter nu – förr'n vi föser dig bort";
$expectedResult = 'tank-efter-nu-forrn-vi-foser-dig-bort';
$url = new URL();
$result = $url->sluggify($originalString);
$this->assertEquals($expectedResult, $result);
}
43. Other tests we can add
public function testSluggifyReturnsExpectedForEmptyStrings() {
$originalString = '';
$expectedResult = '';
$url = new URL();
$result = $url->sluggify($originalString);
$this->assertEquals($expectedResult, $result);
}
44.
45. That’s neat, but it seems we are writing the
same code over and over.
46. That’s neat, but it seems we are writing the
same code over and over.
We can use the “dataProvider annotation.”
47. That’s neat, but it seems we are writing the
same code over and over.
We can use the “dataProvider annotation.”
Annotations are described in docblocks
(comments) of methods.
48. class URLTest extends PHPUnit_Framework_TestCase
{
/**
* @dataProvider providerTestSluggifyReturnsSluggifiedString
*/
public function testSluggifyReturnsSluggifiedString($originalString, $expectedResult)
{
//do our assertion here
}
public function providerTestSluggifyReturnsSluggifiedString()
{
//return an array of arrays which contains our data
}
}
49. class URLTest extends PHPUnit_Framework_TestCase
{
/**
* @dataProvider providerTestSluggifyReturnsSluggifiedString
*/
public function testSluggifyReturnsSluggifiedString($originalString, $expectedResult)
{
$url = new URL();
$result = $url->sluggify($originalString);
$this->assertEquals($expectedResult, $result);
}
public function providerTestSluggifyReturnsSluggifiedString()
{
return array(
array('This string will be sluggified', 'this-string-will-be-sluggified'),
array('THIS STRING WILL BE SLUGGIFIED', 'this-string-will-be-sluggified'),
array('This1 string2 will3 be 44 sluggified10', 'this1-string2-will3-be-44-sluggified10'),
array('This! @string#$ %$will ()be "sluggified', 'this-string-will-be-sluggified'),
array("Tänk efter nu – förr'n vi föser dig bort", 'tank-efter-nu-forrn-vi-foser-dig-bort'),
array('', ''),
);
}
}
50. I haven’t been honest here
URL::Sluggify() was deceptively simple it; had
no dependencies.
51. When you're doing testing like this, you're focusing on one
element of the software at a time -hence the common
term unit testing. The problem is that to make a single
unit work, you often need other units...
- Martin Fowler
http://martinfowler.com/articles/mocksArentStubs.html
52. What happens when we depend on other stuff
(databases, other objects, etc.)?
53. setUp() and tearDown()
class FoobarTest extends PHPUnit_Framework_Testcase{
public function setUp(){
//do things before these tests run
// copy a production database into a test db
// instantiate objects
// define constants
}
public function tearDown(){
//do things after the tests run
// delete test db
}
}
54. But we still have problems
Databases can go down which could cause our
tests to fail.
Instantiating objects can be slow which could
make testing take forever (we want to be able
to test often).
...and we want isolation!
61. Mock objects
class User{
public function __construct($id){
$this-> id = $id;
}
public function verifyUser(){
//validate against an LDAP server
}
public function getName(){
//get user name from the LDAP server
}
62. Mock objects
class ForumPost{
public function deletePostsByUser($user){
$user_id = $user->getUserID();
$query = “delete from posts where userID = ? ”;
….//do stuff
}
//...other methods
}
63. require_once ‘User.php’;
require_once ‘ForumPost.php’;
class testForum extends PHPUnitTest_Framework_TestCase {
public function testUserPostDelete(){
$user = new User(7);
$userName = $user->getName();
$post = new ForumPost();
$rowsDeleted = $post->deletePostsByUser($user);
$message = “$rowsDeleted posts removed for $userName”;
$expectedMessage = “5 posts removed for David Haskins”;
$this->assertEquals($message,$expectedMessage);
}
}
64. require_once ‘User.php’;
require_once ‘ForumPost.php’;
class testForum extends PHPUnitTest_Framework_TestCase {
public function testUserPostDelete(){
$user = new User(7); //fails when LDAP server is down!
$userName = $user->getName();
$post = new ForumPost();
$rowsDeleted = $post->deletePostsByUser($user);
$message = “$rowsDeleted posts removed for $userName”;
$expectedMessage = “5 posts removed for David Haskins”;
$this->assertEquals($message,$expectedMessage);
}
}
65. require_once ‘User.php’;
require_once ‘ForumPost.php’;
class testForum extends PHPUnitTest_Framework_TestCase {
public function testUserPostDelete(){
$user = $this->getMockBuilder(‘User’)
->setConstructorArgs(array(7))
->getMock();
$user->expects($this->once())
->method(‘getName’)
->will($this->returnValue(‘David Haskins’);
$userName = $user->getName();
$post = new ForumPost();
$rowsDeleted = $post->deletePostsByUser($user);
$message = “$rowsDeleted posts removed for $userName”;
$expectedMessage = “5 posts removed for David Haskins”;
$this->assertEquals($message,$expectedMessage);
}
69. Git and unit tests
You can add client or server side “hooks” to git
to run your unit tests and reject submissions
that fail the unit tests.
http://www.masnun.com/2012/03/18/running-phpunit-on-git-hook.html