I am very happy to say that I have been selected for a project in Google Summer of Code 2012. The project idea is "Refactoring: Display query results" which is under phpMyAdmin organization. In this project I'm going to refactor the code which is related to displaying query results and implement some feature requests.
Short description of my project:
In current code base of phpMyAdmin,
the code for displaying query results is not much reusable. To improve
the ability of re usability of the code, I have suggestions to refactor
the code with better approaches. The code is going to be improved by
applying Object Oriented Programming concepts.
Along with that, new features like support for displaying
results for multiple queries at once, introducing some more built-in
transformations where it is applicable will be considered.
Now I'm ready to walk with GSoC 2012 and hope to do a great job with phpMyAdmin developers and other GSoCERS. :)
TDD (Test Driven Development) concept is common practice of
most of the companies which involve with the long time projects. It
helps to keep the static behavior of functions over the development
period. The functions are implemented after implementing test methods
(unit tests) for that function. After implementing the function, run the tests and if any test is failing, again modify the function. This process repeats until the all tests are passing.
Test method is a function which check whether the specific behavior of a
function is correct. Any function can have at least two test methods. one
positive test and one negative test. Most probably a function may have
many test methods. The class in which all the test methods are placed is
called Test Case. By setting up a test suite for this class, the all
tests methods can be run at once.
In long time
projects like open source projects, the functions are frequently
modified by the developers during a long time. If the modified function
is not given the previous behavior of that function, the output for other places will be going wrong. It can be cause to failures. By
writing tests this problem can be reduced. Before and after modifying
the test cases, by running the tests, developer can be verify the
function behave correctly. As well by going through the test cases helps
even understand what actually function does.
To follow this TDD concept with Java, JUnit framework can be used. As
well with PHP, PHPUnit framework can be used. Let's do some experiments
with PHPUnit with Ubuntu OS.
Install PHPUnit framework
$sudo apt-get install phpunit
Now create your test case as a
sub class of 'PHPUnit_Framework_TestCase' having just one dummy method
to verify we can forward without issue.
<?php
require_once 'PHPUnit/Framework.php'; // installed file with phpunit framework which is located at : /usr/share/php/PHPUnit/
class MyClassTest extends PHPUnit_Framework_TestCase {
// This function just return true
function testCheckAllWorksFine() {
$this->assertTrue(true);
}
}
?>
Run this MyClassTest.php file as :
$phpunit MyClassTest.php
If
the result is as following (Fig.
1), you can proceed the work with phpunit.
I
have used following useful assert functions for this article :
assertTrue()
: Check whether the parameter is True
assertFalse()
: Check whether the parameter is False
assertEqual() : Match two
results
Start
work
1. Requirement : need a function which accept two strings
and return the length of the most lengthy string, if both arguments are
not strings return false
* First just declare your
function properly with arguments
<?php
class MyClass {
function getMaxLength ($str1, $str2) {
}
}
?>
* Now start
to write test methods,
a). Check the arguments
<?php
require_once 'MyClass.php';
require_once 'PHPUnit/Framework.php';
class MyClassTest extends PHPUnit_Framework_TestCase {
var $obj;
// This function calls before any test merhod run
function setUp() {
$this->obj = new MyClass();
}
// Positive : Test getMaxLength for correct arguments
function testGetMaxLengthForAtLeastOneIsString() {
$paramArray = array("str1", 2);
$result = $this->obj->getMaxLength($paramArray[0], $paramArray[1]);
$this->assertEquals(4, $result);
}
// Negative : Test getMaxLength for non of arguments are string
function testGetMaxLengthForNoStrings() {
$paramArray = array(array(1,2), 12.3);
$result = $this->obj->getMaxLength($paramArray[0], $paramArray[1]);
$this->assertFalse($result);
}
}
?>
The setUp() will call automatically
before running any test method. It can be used for create a fresh
objects, load data for databases if using etc. All test methods must be
start with 'test'. Using the function name which is going to use by
specific test method regardless of the length of the test method name,
will be increase the readability and understandability while reviewing
later.
b).
Check whether the function returns an integer
// Test getMaxLength for output integer for correct arguments
function testGetMaxLengthForCorrectReturnType() {
$paramArray = array("str1", 2);
$result = $this->obj->getMaxLength($paramArray[0], $paramArray[1]);
$this->assertTrue(is_int($result));
}
c).
Check whether is returns the length of the most lengthy string
// Test getMaxLength for Length of the lengthy string
function testGetMaxLengthForCorrectLength() {
$paramArray = array("short_string", "lenghty_string");
$result = $this->obj->getMaxLength($paramArray[0], $paramArray[1]);
$this->assertEquals(14, $result);
}
Now
the most needed test cases are implemented. From the above test
methods, some scenarios like check the function
return length of the string for arguments having only one string, is
already covered. Now just run the test and
see most of them are failing. Its time to start implement the actual
function in a
way that all te test cases are passing.
Now
again run the test. See whether all the test are passing or not. If not
your function may have some issues. Improve the function and again run
the test. Do this process until all the test are passing.
The
example we discussed was very easy one. But in real applications we
have use more complex algorithms and tactics dealing with databases. To
check the functionality of the data transaction functions, we can use a
test database with some preloaded data. Inside the setUp() function we
can create tables and load the data to the
test database. As well using the function tearDown() which automatically call after execution of any test method, we can clean the data from the database.
Sometimes
applications with MVC architecture have two or more separate layers in
Data Layer. In such kind of situation, functions need to test
independent from their layers. Lets assume in our application there are
two layers in Data Layer. One is data transaction layer which directly
deal with the database and other one is data processing layer which
process data before or after deal with database through data transaction
layer. So, while writing test for a function in data process layer
which get some data by calling a function in data transaction layer, we
should verify that the correct data set is returned from the function in
data transaction layer. To do this we can use Moc Objects.
Lets
go through one example.
* getStrings() method in DataTransaction
class return an array with two strings (No need of even declare this
class)
* getProcessedResult() method in DataProcess
class, get the array of two strings by calling the above mentioned
function (getStrings()), and return the length of the most lengthy
string out of them. This class contain getter and setter methods for use
DataTransaction class.
(Declare the getProcessedResult() function
and implement getter and setter methods)
<?php
class DataProcess {
private $dataTransaction;
function getDataTransaction() {
if(is_null($this->dataTransaction)) {
return new DataTransaction();
}
return $this->dataTransaction;
}
function setDataTransaction($dataTransaction) {
$this->dataTransaction = $dataTransaction;
}
function getProcessedResult() {
}
}
?>
* Now write the test class using the Moc Object
of DataTransaction class
<?php
require_once 'DataProcess.php';
require_once 'PHPUnit/Framework.php';
class TestMoc extends PHPUnit_Framework_TestCase {
var $dataProcess;
// This function calls before any test merhod run
function setUp() {
$this->dataProcess = new DataProcess();
}
public function testGetProcessedResult() {
// Create a Mock Object for the DataTransaction class
// mocking only the getStrings() method.
$dataTransaction = $this->getMock('DataTransaction', array('getStrings'));
// Set up the expectation for the getStrings() method
// to be called only once and with no arguments
// and return value is array("abc", "abcde")
$dataTransaction->expects($this->once())
->method('getStrings')
->with()
->will($this->returnValue(array("abc", "abcde")));
// Set the mocked DataTransaction object to
// DataProcess object using setter
$this->dataProcess->setDataTransaction($dataTransaction);
// Call the getProcessedResult() method on the $dataProcess object
// which we expect to call the mocked DataTransaction object's
// getStrings() method with no arguments.
$this->assertEquals(5, $this->dataProcess->getProcessedResult());
}
}
?>
Here, even we didn't implement (even declare) the DataTransaction class, we could run the test using Moc Object. So that we can test functions independent from the layer it contains and other needed functions.
Setting
up test suite
Test suit can be used to mange which test cases should run. We can run even all the test methods in several test cases at once. Test suite can be create as following example code snippet.
<?php
require_once 'PHPUnit/Framework.php';
class AllTests {
public static function suite() {
$suite = new PHPUnit_Framework_TestSuite("PHPUnit");
// Add needed test cases
$suite->addTestFile("MyTestClass1.php");
$suite->addTestFile("MyTestClass2.php");
return $suite;
}
}
?>
With
PHPUnit framework the percentage of the functions which covers from the unit
tests can be obtained as a coverage report. Following command can be used to obtain a coverage report. (probably you need to install xDebug : http://xdebug.org/docs/install)
$phpunit --coverage-html ./report AllTests.php
From
this report we can find out the uncovered code segments. Some of code segments in MyClass.php has not covered from the unit tests. To cover
them again test can be implemented.
So, this is a
basic idea of how to follow test driven development with PHPUnit framework. Hope this helpful to you.