I’m just diving into testing/test driven development in php, and am a bit confused about how to handle a very typical scenario in our legacy codebase. Our codebase does not have many classes, it’s very functional programming oriented.
In this example code, which is pseudocode, I want to write a test for getAndOrderUsersByAge(). As you can see it first gets all the users from our database (in a giant array lets say, because we don’t have objects/classes) then sorts them. This is of course a VERY common occurrence in our code, manipulating or analyzing data gotten from the database; not objects, but an array of database rows basically.
Note that I've added an error in the if statement to simulate an error I want my tests to catch.
//gets all users in our db into an array, then sorts them by age
function getAndOrderUsersByAge(){
//get all users from the db
$all_users = getAllUsers();
//sort
$all_users_sorted = sortUsers($all_users)
//notice I've made a mistake here on purpose, left the "d" off of "$all_users_sorted"
//so the function will always return false, a mistake...which I want tests to catch
if(!empty($all_users_sorte){
return $all_users_sorted;
}
else{
return false;
}
}
My questions are:
1.) Is testing the function getAndOrderUsersByAge() unit testing, integration testing, or …?
2.) In my (unit?) test(s) of getAndOrderUsersByAge(), I want to assume that getAllUsers (the function which gets things from the db) behaves correctly because I only want to test getAndOrderUsersByAge() right? I’m not really sure what “concept” this falls under when I’m reading the two books I have on testing. Is this where I would “mock”, e.g. create some fake data for getAllUsers() to return?
3.) What tests would you write for getAndOrderUsersByAge()? I think I would write one that ensures a non-empty array is returned when getAllUsers() returns a non-empty array, as this would catch the error I've created. I'm not sure what else though.
Any tips, recommended reading material, etc. is very welcomed.
1.) Is testing the function getAndOrderUsersByAge() unit testing, integration testing, or …?
Any test that you do at code level is unit testing, basically testing methods , classes, interfaces. Tests that validate a unit or block of code.
2.) In my (unit?) test(s) of getAndOrderUsersByAge(), I want to assume that getAllUsers (the function which gets things from the db)
You can test getAndOrderUsersByAge() by actual data or mock data. Mock data is preferred so you will have more control on the test and you can create more test cases for sortUsers function.
3.) What tests would you write for getAndOrderUsersByAge()?
The test should be of nature that would validate your sortUsers function. I am not sure how your data looks but some tests could be:
1) data set that will have valid data , which will pass the test
2) data set that has invalid data like special characters in name
3) data set that has junk in age field or floating numbers or out of range integers, basically test how your function behaves when age has different type of data
4) data set that has null values
5) data set with 1 element array
6) data set with repeated element array
7) data set with invalid types in array
Related
public function addPic($loggedInId,$PicId){
$chatCoverPhotoObj = new COVER_PHOTO();
$out = $chatCoverPhotoObj->add($loggedInId,$PicId);
if($out)
return true;
return false;
}
Above sample code is written in php and simple add record in table COVER_PHOTO
Record : "picId" corresponding to loggedin user identified by "loggedInId".
I want know what should i write in unit test of this function.
Or for such function we should not write unit test.
Please suggest?
Firsty, if your System-/Class Under Test is using anything that is infrastructural concern -which in your case is the database- that tends to be an integration test.
Unit test are happening in complete isolation with every possible dependencies mocked/stubbed out.
The first problem I see in your code is that you new() up a dependency of your function. By this you can't really easily test your code.
I suggest that you better invert this dependency and have an interface in the constructor of your class, which can receive the concrete implementation of that interface. Once this is done, you can mock out the database part in your test. However by looking at the code you have shared in your question I'm not really convinced what behaviour you are going to test here.
Always make sure what you want to test for, in this case like persistence specification or the mapping configuration (of course in case you are using some sort of an OR/M). Just checking the return value -which is the success or failure flag- of the persistence operation doesn't add much business value to test against.
quick example of dataProvider:
return [
['180d-1pc', '6m-1pc'],
]
and test:
public function test_convert($title, $expected)
{
$uut = new Converter();
$this->assertEquals($expected, $uut->convertDayTitle($title));
}
(simply put: test if we convert 180d(days) to 6m(months))
as you can see - in data provider there are input data defined and also an expected output.
This works fine in many cases, but I keep having this feeling that maybe it's not the best idea. So I'm wondering if could be considered bad practice. If so - when I will see it was a bad idea to do it?
One example is, when you would like to use the same dataProvider in two tests - should you define two expected values and use one of them?
Quick example (I just made that up, so don't pay attention that I make product object just from title ;)):
public function test_gets_discount_when_licence_period_longer_than_1year($title, $expectedTitle, $expectedDiscount) {
$prod = new Product($title);
$this->assertEquals($expectedDiscount, $product->hasDiscount();
}
How to make this more elegant?
What you're doing is totally fine. Data providers can, and should, include the expected test result.
About the issue of reusing the same dataProvider for 2 tests, and only using some fields: I'd say that if the fields are related (i.e. they are properties of the same object), it can be acceptable.
If you feel the dataProvider is getting too big and complex because 2 tests must use data from it, just create 2 separate dataProviders, and use another private common method for building the common data.
Let me explain myself.
function insertObjects($objs) {
foreach ($objs as $obj) {
$this->repository->insert($obj);
}
}
I don't want to test that insertion into the database worked because I assume it works (it's a different unit). I also don't want to test foreach because obviously foreach is going to work. So the only thing to test here is that $objs is a well formed array. But if $objs is the mock data that I will be passing in... so does this mean there is nothing to test for this function?
If there's any chance of invalid input (a not well-formed array, null value, etc.), you need to handle that case in your method by explicitly checking for it.
In your test, you would then try to call your method with various invalid values, and check whether your method responds correctly, i.e. the database insert method is not called, exceptions are thrown, errors are logged, etc.
Other than that, the only thing to test is whether your database insert method is called with the parameters that correspond to the values in the valid test array you pass in.
I've been working on getting our systems more compatible with PHPUnit so we can do more unit testing of our classes and have managed to get some of them working with mock objects, but I've run across a problem which I can't seem to get around.
One of the classes we have (which I'm creating a mock version of) is for queries. You pass the query string into it's "query()" method, it logs the query, runs it and returns the result. It also wraps the mysql_fetch_assoc with a method called "get_row()", which returns an array value much like the original.
The problem is that, in some methods, there's more than one query being passed to the "query()" method and as a result it needs to run through multiple while loops to load the data into different variables. I've created a simplified version below:
class object{
public function __construct($query){
$this->query = $query;
}
public function loadData(){
$data1 = queryDataSource("SELECT * FROM data1");
$data2 = queryDataSource("SELECT * FROM data2");
return Array(
"data1" => $data1,
"data2" => $data2,
);
}
private function queryDataSource($query){
$this->query->query($query)
while($row = $this->query->get_row()){
$result[] = $row;
}
return $result
}
}
class testObject extends PHPUnit_Framework_TestCase{
method testLoadData(){
$test_data = Array('name' => 'Bob', 'number' => '98210');
$query = $this->getMock('Query');
$query->expects($this->any())->method('query');
$query->expects($this->at(1))->method('get_row')->will($this->returnValue($test_data);
$query->expects($this->at(2))->method('get_row')->will($this->returnValue(False);
$query->expects($this->at(3))->method('get_row')->will($this->returnValue($test_data);
$query->expects($this->at(4))->method('get_row')->will($this->returnValue(False);
}
}
In order to escape the first while loop in $object->queryDataSource() I'm returning a boolean FALSE value, as would happen when doing mysql_fetch_assoc. The problem is that, when it tries to run the second query and fetch the data through get_row(), the mock object seems to keep returning FALSE ratehr than moving on to the at(3) point. This happens even with 4 objects, only the first will get the test data as a return value then get FALSE the second time, the others will get FALSE every time.
Does anyone know if there's a way to get around this? I tried removing the FALSE flags and just having the odd values in at(), but that had the same problem, and I tried just having it return the data for at(1-2), but that just passed all the data into the first while loop and nothing for the other.
Thanks for any help you can give, hope the description of the problem's clear enough
I can't run the code as it seems to only be pseudocode but from what I understood is that you are trying to mock like this:
Call to query, get_row, get_row, query, get_row, get_row.
The issue you seem to have run into is that the number in the ->at() matcher doesn't count up per method but per object.
So what you probably want to write is:
$query->expects($this->any())->method('query');
$query->expects($this->at(1))->method('get_row')->will($this->returnValue($test_data);
$query->expects($this->at(2))->method('get_row')->will($this->returnValue(False);
$query->expects($this->at(4))->method('get_row')->will($this->returnValue($test_data);
$query->expects($this->at(5))->method('get_row')->will($this->returnValue(False);
Or to make it a litte easer to read maybe even:
$query->expects($this->at(0))->method('query');
$query->expects($this->at(1))->method('get_row')->will($this->returnValue($test_data);
$query->expects($this->at(2))->method('get_row')->will($this->returnValue(False);
$query->expects($this->at(3))->method('query');
$query->expects($this->at(4))->method('get_row')->will($this->returnValue($test_data);
$query->expects($this->at(5))->method('get_row')->will($this->returnValue(False);
With your mocks you ran into the issue that the second call to "query" was counting up one "call" and hence skipping over the second return($test_data);.
Unfortunately the at() binds your tests to the implementations very strongly.
Imagine if you rearranged 2 method calls inside a tested method, the functionality is exactly the same but all tests using at() would now fail, often with cryptic messages such as method doesn't exist at index N
On the occasions you want to specifically say "this called exactly this way then this called exactly this way" that's great, but if you just want assertions then one of the PHPUnit Mock Extensions seems more friendly, particularly Mockery and a guide here (a touch out of date I believe)
There are others also.
I need to test a number of functions that I have created using PHP 5 which carry out the required database CRUD type actions (SELECT, UPDATE, INSERT, DELETE) which are required by my web application.
I have been looking at PHP unit testing suites such as Simple Test and PHP Unit which seem to offer what I need however I am unsure how I am meant to achieve this, as equivalence partitioning and boundary analysis isn't all that clear. Do I just need to input different variables and vary this? This seems rather pointless as a string that is different may not necessarily make any difference.
Any guidance on this would be helpful as I have not encountered this before.
If you are testing the interaction between your PHP code and your MySQL database, you are performing integration testing, rather than unit testing.
Here are some examples of integration testing with Enhance PHP framework, it tests a repository class that saves and retrieves a Tenant object.
Instead of running against a pre-populated database, it runs on an entirely empty database and creates and destroys the tables as it goes using a simple table helper. This removes the dependency on particular data being in the right state in a test database, which is hard to keep in step.
<?php
class TenantRepositoryTestFixture extends EnhanceTestFixture {
private $Target;
public function SetUp() {
$tables = new TableHelper();
$tables->CreateTenantTable();
$this->Target = Enhance::GetCodeCoverageWrapper('TenantRepository');
}
public function TearDown() {
$tables = new TableHelper();
$tables->DropTenantTable();
}
public function SaveWithNewTenantExpectSavedTest() {
$tenant = new Tenant();
$tenant->Name = 'test';
$saved = $this->Target->Save($tenant);
$result = $this->Target->GetById($saved->Id);
Assert::AreNotIdentical(0, $result->Id);
Assert::AreIdentical($tenant->Name, $result->Name);
}
public function SaveWithExistingTenantExpectSavedTest() {
$tenant = new Tenant();
$tenant->Name = 'test';
$saved = $this->Target->Save($tenant);
$saved->Name = 'changed';
$saved = $this->Target->Save($saved);
$result = $this->Target->GetById($saved->Id);
Assert::AreIdentical($saved->Id, $result->Id);
Assert::AreIdentical($saved->Name, $result->Name);
}
}
?>
Generally the idea with unit testing is to ensure that, if you make a change, you can simply run a simple series of tests to ensure no existing functionality will break. So that said, you'll want to cover the typical data you're expecting, as well as edge/boundary cases, which might include strings with quotes (to verify that they're being escaped properly), SQL injection attacks (same), empty strings, strings of different encoding, NULL, a boolean true, etc. Each test should verify that, given the data you input, you're getting the expected result, in this case (respectively): escaped string inserted, escaped string inserted, empty string inserted, different encoding converted or thrown out then inserted, an error thrown on a NULL value, the string 'true' inserted, etc.
I haven't used either test framework in a few years, but I remember having good results with PHPUnit.