Using $this when not in object context error within array_walk - php

I am experiencing an odd problem with using array_walk with closures within a class. The problem does not arise within my development environment using php version 5.4.7 but it does on my deployment environment 5.3.3.
The following code runs fine on my production box, but crashes on my deployment environment:
<?php
error_reporting(-1);
Class TestArrayWalk
{
/** #var null|array */
protected $userInput = null;
/**
* This expects to be passed an array of the users input from
* the input fields.
*
* #param array $input
* #return void
*/
public function setUserInput( array $input )
{
$this->userInput = $input;
// Lets explode the users input and format it in a way that this class
// will use for marking
array_walk( $this->userInput, function( &$rawValue )
{
$rawValue = array(
'raw' => $rawValue,
'words' => $this->splitIntoKeywordArray( $rawValue ),
'marked' => false,
'matched' => array()
);
}
);
}
public function getUserInput()
{
return $this->userInput;
}
protected function splitIntoKeywordArray( $input )
{
if ( ! is_string( $input )){ return array(); }
return preg_split('/(\s|[\.,\/:;!?])/', $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
}
}
$testArrayWalk = new TestArrayWalk();
$testArrayWalk->setUserInput(
array(
'This is a test input',
'This is another test input'
)
);
var_dump( $testArrayWalk->getUserInput() );
The error I am getting is: Using $this when not in object context on line 26 which is the only usage of $this within that test class. I am assuming something changed between the versions I am using that has made the above code possible in my development environment.
I also assume that as I cant change the deployment environment (its the clients and they wont change it) that I am going to have to use a foreach rather than array_walk.
My question is this: Given the above, is this possible in 5.3.3 using array_walk if not how do I use foreach in the same way that I am using array_walk ( more specifically the &$rawValue bit)?
My environments are:
My development environment is PHP version 5.4.7
My server (deployment) environment is PHP version 5.3.3
Thanks.
Edit 2
Thanks to everyone who helped. With your help I got this working and have posted my working code to https://gist.github.com/carbontwelve/6727555 for future reference.

This is described in PHP manual:
Version Description
5.4.0 $this can be used in anonymous functions.
Anonymous functions
Possible workaround would be to re-assign this to another variable and pass it via use:
$_this = $this;
function() use($_this) { ... }
but keep in mind you will not be able to access private and protected members so you will have to make splitIntoKeywordArray public

You may also use if you are using PHP < 5.4
public function setUserInput( array $input )
{
$this->userInput = $input;
$userInput_array = &$this->userInput;
array_walk( &$userInput_array, function( &$rawValue ) use (&$userInput_array) {
// use $userInput_array instaed of $this->userInput_array
});
}

I had a similar problem with a lambda function defined in a private function within a class:
MyClass{
private $str="doesn't work :-(";
private function foo(){
$bar=function(){
echo $this->str; // triggers an "Using $this when not in object context" error
};
$bar();
}
}
Solution for PHP 5.3.0: declare a variable $obj=&$this in your parent scope (ie. the private function) and pass it to the anonymouse function by using the use language construct. Also make sure the function/variable that you access has a public visibility (protected|private might not work).
MyClass{
public $str="it just works :-)";
private function foo(){
$obj=&$this;
$bar=function() use(&$obj){
echo $this->str; // it works!
};
$bar();
}
}

Related

How do I simulate php://input in PHP?

I'm writing an unit test for my PHP project,
the unit test is to simulate a php://input data,
and I read the manual, it says:
php://input is a read-only stream that allows you to read raw data
from the request body.
How do I simulate the php://input, or write the request body in my PHP?
Here's my source code and unit test, both are simplified.
Source:
class Koru
{
static function build()
{
// This function will build an array from the php://input.
parse_str(file_get_contents('php://input'), $input);
return $input;
}
//...
Unit Test:
function testBuildInput()
{
// Trying to simulate the `php://input` data here.
// NOTICE: THIS WON'T WORK.
file_put_contents('php://input', 'test1=foobar&test2=helloWorld');
$data = Koru::build();
$this->assertEquals($data, ['test1' => 'foobar',
'test2' => 'helloWorld']);
}
Use a test double
Given the code in the question, the simplest solution is to restructure the code:
class Koru
{
static function build()
{
parse_str(static::getInputStream(), $input);
return $input;
}
/**
* Note: Prior to PHP 5.6, a stream opened with php://input could
* only be read once;
*
* #see http://php.net/manual/en/wrappers.php.php
*/
protected static function getInputStream()
{
return file_get_contents('php://input');
}
And use a test double:
class KoruTestDouble extends Koru
{
protected static $inputStream;
public static function setInputStream($input = '')
{
static::$inputStream = $input;
}
protected static function getInputStream()
{
return static::$inputStream;
}
}
The test method then uses the test double, not the class itself:
function testBuildInput()
{
KoruTestDouble::setInputStream('test1=foobar&test2=helloWorld');
$expected = ['test1' => 'foobar', 'test2' => 'helloWorld'];
$result = KoruTestDouble::build();
$this->assertSame($expected, $result, 'Stuff be different');
}
Avoid static classes if possible
Most of the difficulties with the scenario in the question are caused by the use of static class methods, static classes make testing hard. If at all possible avoid the use of static classes and use instance methods which allows solving the same sort of problem using mock objects.
See vfsStream package and this SO question and answers.
Basically, you would want to parametrize your service that reads data to accept a path:
public function __construct($path)
{
$data = file_get_contents($path); // you might want to use another FS read function here
}
And then, in a test, provide an vfsStream stream path:
\vfsStreamWrapper::register();
\vfsStream::setup('input');
$service = new Service('vfs://input')
In your code you would provide php://input as per usual.
This sort of extreme decomposition gains nothing and leads very brittle code. Your tests should express the expectations of your interfaces, and not the data you've supplied them with: Is PHP truly not free to return ["test2"=>"helloWorld","test1"=>"foobar"] in some future version? Is your code broken if it does? What exactly do you think you are testing?
I think you're overcomplicating this.
$a->doit should take $input as an argument and not call Koru::build as part of its initialisation. Then you can test $a->doit instead of testing parse_str.
If you insist on pressing on this example, then Koru::build needs to take an argument of 'php://input' – this is often called dependency injection, where you tell your functions everything they need to know. Then, when you want to "test" things, you can simply pass in some other file (or e.g. a data url).
With Kahlan you can monkey patch the file_get_contents function directly like so:
use My\Name\Space\Koru;
describe("::build()", function() {
it("parses data", function() {
allow('file_put_contents')->toBeCalled()->andRun(function() {
return 'test1=foobar&test2=helloWorld';
});
expect(Koru::build())->toBe([
'test1' => 'foobar',
'test2' => 'helloWorld'
]);
});
});
Use a Zend\Diactoros\Stream
https://zendframework.github.io/zend-diactoros/usage/
$_POST['foo'] = 'bar';
use Zend\Diactoros\ServerRequestFactory;
$psrRequest = ServerRequestFactory::fromGlobals();
var_dump($psrRequest->getParsedBody()); // foo => bar
var_dump($_POST); // foo => bar
more info https://laracasts.com/discuss/channels/general-discussion/psr-7?page=1

Is the call_user_func_array() changed in PHP 5.3?

Has the function call_user_func_array() changed in PHP 5.3 ? Because I have module that has several calls of this function, but it doesn't work after upgrade to PHP 5.3. I traced the code and it seems it doesn't call this function.
Should I change it?
Edited:
some cuts of codes:
function complete($message, $endpoint, $return_to)
{
$mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode',
'<no mode set>');
$mode_methods = array(
'cancel' => '_complete_cancel',
'error' => '_complete_error',
'setup_needed' => '_complete_setup_needed',
'id_res' => '_complete_id_res',
);
$method = Auth_OpenID::arrayGet($mode_methods, $mode,
'_completeInvalid');
$method = '_complete_id_res';
return call_user_func_array(array(&$this, $method),
array($message, $endpoint, $return_to));
}
/**
* #access private
*/
function _complete_id_res($message, &$endpoint, $return_to)
{
$user_setup_url = $message->getArg(Auth_OpenID_OPENID1_NS,
'user_setup_url');
if ($this->_checkSetupNeeded($message)) {
return new Auth_OpenID_SetupNeededResponse(
$endpoint, $user_setup_url);
} else {
return $this->_doIdRes($message, $endpoint, $return_to);
}
}
if I put die('*'); command in the second function,it doesn't die that show it doesn't enter it.
why not check that on php.net ?
Changelog
Version Description
5.3.0 The interpretation of object oriented keywords like parent and self has
changed. Previously, calling them using the double colon syntax would emit an
E_STRICT warning because they were interpreted as static.
Yes, it has changed.
5.3.0: The interpretation of object oriented keywords like parent and self has changed. Previously, calling them using the double colon
syntax would emit an E_STRICT warning because they were interpreted as
static.
The problem is solved now. It was because of defining function parameters with reference(&) but the calling was with a value. I remove the '&' from function definition and it works well.
It is solved now. The problem was function parameters that were defined by reference (&) in the function but were passed with value. I removed & from them and the problem was resolved.

PHP Class Function List in Comments

Whenever I write a class in PHP, I like to put a list of public functions at the top of the class like this:
// add_project($apikey, $post, $email)
// create_user($apikey, $email, $firstname, $lastname, $rolename, $pid)
// user_join_project($apikey, $email, $pid)
I was wondering if there were any tools that can automate this process. Like perhaps I could load the class file and it could generate a list of the function names, variables and such?
Thanks!
Try phpDocumentor. You use DocBlock syntax for comments (similar to Javadoc and in other languages) then pass your PHP source files through the phpDocumentor parser, and it generates API documentation for you.
A rough example:
/**
* Adds a project.
*
* #param string $apikey The API key.
* #param object $post The post.
* #param string $email A supplied email address.
* #return void
*/
function add_project($apikey, $post, $email) {
...
}
There's a not great deal of value in this approach.
First off, if you're not already doing so you should mark each of your methods (and indeed member variables) with the appropriate visibility (public/protected/private). You could then use a solution such as phpDoc, which to properly document each method argument in addition to providing an overall purpose for each method/class, etc.
You can then automatically generate documentation for your project in HTML format (amongst others).
There are various tools for this. They are called PHP documentor's. I use an IDE called PHPStorm that has a documentor integrated.
Here is some more information on PHPDoc.
This can be done pretty easily with Parsekit.
Using this ("tmp.php") as sample data:
<?php
class Fruit
{
public function apple($cored) {}
public function orange($peeled) {}
public function grape($colour, $seedless) {}
}
Here's a simple Parsekit example to dump a class's functions:
<?php
$parsed = parsekit_compile_file($argv[1]);
foreach ($parsed['class_table'] as $class => $classdat) {
foreach ($classdat['function_table'] as $func => $funcdat) {
echo "{$class}::{$func}(";
$first = true;
foreach ($funcdat['arg_info'] as $arg => $argdat) {
if (!$first) {
echo ', ';
}
echo "\${$argdat['name']}";
$first = false;
}
echo ")\n";
}
}
…and here it is in use:
$ php parse.php tmp.php
Fruit::grape($colour, $seedless)
Fruit::orange($peeled)
Fruit::apple($cored)

forcing access to __PHP_Incomplete_Class object properties

I'm writing a module for a php cms. In a function (a callback) I can access an object that comes from the framework code.
This object is of type __PHP_Incomplete_Class because the needed header file is not included before the session starts. I cannot include it without hacking the core cms code.
I wonder if is possibile to access the object properties anyway (casting to array does not work). I ask this because I can see the values with var_dump() but using $object->var I always get nulls.
This issue appends when you un serialize an object of a class that hasn't been included yet.
For exemple, if you call session_start before include the class.
A PHPIncompleteClass object can't be accessed directly, but it's ok with foreach, serialize and gettype.
Calling is_object with an PHPIncompleteClass object will result false.
So, if you find a '__PHP_Incomplete_Class' object in your session and you've included your class after the session_load, you can use this function :
function fixObject (&$object)
{
if (!is_object ($object) && gettype ($object) == 'object')
return ($object = unserialize (serialize ($object)));
return $object;
}
This will results a usable object :
fixObject($_SESSION['member']);
I found this hack which will let you cast an object:
function casttoclass($class, $object)
{
return unserialize(preg_replace('/^O:\d+:"[^"]++"/', 'O:' . strlen($class) . ':"' . $class . '"', serialize($object)));
}
From http://blog.adaniels.nl/articles/a-dark-corner-of-php-class-casting/
So you can do:
$obj = casttoclass('stdClass', $incompleteObject);
and then access properties as normal.
You could also define an unserialize_callback_func in a .htaccess/Apache configuration file. That way you wouldn't need to hack any PHP but you could include the file on demand.
As an addition here is my version of the fix_object() function:
The main change is step 3 in the code: Make all properties public.
When PHP serializes an object, all private and protected properties are prefixed with two null-bytes! These null-bytes are the actual reason, why the property cannot be accessed via $obj->key because actually it is something like $obj->{NULL*NULL}key.
/**
* Takes an __PHP_Incomplete_Class and casts it to a stdClass object.
* All properties will be made public in this step.
*
* #since 1.1.0
* #param object $object __PHP_Incomplete_Class
* #return object
*/
function fix_object( $object ) {
// preg_replace_callback handler. Needed to calculate new key-length.
$fix_key = create_function(
'$matches',
'return ":" . strlen( $matches[1] ) . ":\"" . $matches[1] . "\"";'
);
// 1. Serialize the object to a string.
$dump = serialize( $object );
// 2. Change class-type to 'stdClass'.
$dump = preg_replace( '/^O:\d+:"[^"]++"/', 'O:8:"stdClass"', $dump );
// 3. Make private and protected properties public.
$dump = preg_replace_callback( '/:\d+:"\0.*?\0([^"]+)"/', $fix_key, $dump );
// 4. Unserialize the modified object again.
return unserialize( $dump );
}
var_dump will not display these NULL byte prefixes to you, but you can see them with this code:
class Test {
private $AAA = 1;
protected $BBB = 2;
public $CCC = 3;
}
$test = new Test();
echo json_encode( serialize( $test ) );
// Output:
// "O:4:\"Test\":3:{s:9:\"\u0000Test\u0000AAA\";i:1;s:6:\"\u0000*\u0000BBB\";i:2;s:3:\"CCC\";i:3;}"
$test2 = fix_object( $test );
echo json_encode( serialize( $test2 ) );
// Output:
// "O:8:\"stdClass\":3:{s:3:\"AAA\";i:1;s:3:\"BBB\";i:2;s:3:\"CCC\";i:3;}"
There you see:
The private property is prefixed with NULL + classname + NULL
The protected property is prefixed with NULL + "*" + NULL
None of the above answers actually worked for me, except this solution:
$object = unserialize(serialize($object));
$object->function();
Hope it helps someone
I tried the answer of Tom Haigh here, but discovered 2 problems.
when you have other "Incomplete_Class" objects as properties of the top-level-class they stay untouched as __PHP_Incomplete_Class Object
if you have private properties, they will still be private in your stdClass object
So I rewrote the function handle this:
/**
* #see: https://stackoverflow.com/a/965704/2377961
*
* #param object $object The object that should be casted
* #param String $class The name of the class
* #return mixed The new created object
*/
function casttoclass($object, $class = 'stdClass')
{
$ser_data = serialize($object);
# preg_match_all('/O:\d+:"([^"]++)"/', $ser_data, $matches); // find all classes
/*
* make private and protected properties public
* privates is stored as "s:14:\0class_name\0property_name")
* protected is stored as "s:14:\0*\0property_name")
*/
$ser_data = preg_replace_callback('/s:\d+:"\0([^\0]+)\0([^"]+)"/',
function($prop_match) {
list($old, $classname, $propname) = $prop_match;
return 's:'.strlen($propname) . ':"' . $propname . '"';
}, $ser_data);
// replace object-names
$ser_data = preg_replace('/O:\d+:"[^"]++"/', 'O:' . strlen($class) . ':"' . $class . '"', $ser_data);
return unserialize($ser_data);
}
I switch the function arguments too, so that you can use
$obj = casttoclass($incompleteObject);
And you get an stdClass object with only public properties.
Even if you have objects in childclasses they are converted to stdClass with public properties too.
If you just need to access raw data (like class variables) from a PHP_Incomplete_Class object, you can use the foreach hack, or you can also do:
$result_array = (array)$_SESSION['incomplete_object_index'];
echo $result_array['desired_item'];
Put the session_start() after your require to the class of the object you are trying to read from the SESSION
I've read a lot of suggestions on how to fix incomplete classobjects and I actually needed to fix those problems myself, in a ecommerce-project.
One suggestion I've found is to simply use json_decode/json_encode to convert incomplete classes without preloading anything. However, I didn't want to take the risk using this, if there are older PHP versions that are dependent in for example PECL, that is described at http://php.net/manual/en/function.json-encode.php - so I finally succeeded to make my own solution.
However, the code is a way to get the data out of the object properly, so it may not fit all needs - and it will primarily, use the json-solution first, if it is available in the environment and fail over to manual handling if needed.
It also works recursively, which in my own case is required, to save the whole array.
/**
* Convert a object to a data object (used for repairing __PHP_Incomplete_Class objects)
* #param array $d
* #return array|mixed|object
*/
function arrayObjectToStdClass($d = array())
{
/**
* If json_decode and json_encode exists as function, do it the simple way.
* http://php.net/manual/en/function.json-encode.php
*/
if (function_exists('json_decode') && function_exists('json_encode')) {
return json_decode(json_encode($d));
}
$newArray = array();
if (is_array($d) || is_object($d)) {
foreach ($d as $itemKey => $itemValue) {
if (is_array($itemValue)) {
$newArray[$itemKey] = (array)$this->arrayObjectToStdClass($itemValue);
} elseif (is_object($itemValue)) {
$newArray[$itemKey] = (object)(array)$this->arrayObjectToStdClass($itemValue);
} else {
$newArray[$itemKey] = $itemValue;
}
}
}
return $newArray;
}

How do I write unit tests in PHP? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
I've read everywhere about how great they are, but for some reason I can't seem to figure out how exactly I'm supposed to test something. Could someone perhaps post a piece of example code and how they would test it? If it's not too much trouble :)
There is a 3rd "framework", which is by far easier to learn - even easier than SimpleTest, it's called phpt.
A primer can be found here:
http://qa.php.net/write-test.php
Edit: Just saw your request for sample code.
Let's assume you have the following function in a file called lib.php:
<?php
function foo($bar)
{
return $bar;
}
?>
Really simple and straight forward, the parameter you pass in, is returned. So let's look at a test for this function, we'll call the test file foo.phpt:
--TEST--
foo() function - A basic test to see if it works. :)
--FILE--
<?php
include 'lib.php'; // might need to adjust path if not in the same dir
$bar = 'Hello World';
var_dump(foo($bar));
?>
--EXPECT--
string(11) "Hello World"
In a nutshell, we provide the parameter $bar with value "Hello World" and we var_dump() the response of the function call to foo().
To run this test, use: pear run-test path/to/foo.phpt
This requires a working install of PEAR on your system, which is pretty common in most circumstances. If you need to install it, I recommend to install the latest version available. In case you need help to set it up, feel free to ask (but provide OS, etc.).
There are two frameworks you can use for unit testing. Simpletest and PHPUnit, which I prefer. Read the tutorials on how to write and run tests on the homepage of PHPUnit. It is quite easy and well described.
You can make unit testing more effective by changing your coding style to accommodate it.
I recommend browsing the Google Testing Blog, in particular the post on Writing Testable Code.
I rolled my own because i didnt have time to learn someone elses way of doing things, this took about 20 minutes to write up, 10 to adapt it for posting here.
Unittesting is very usefull to me.
this is kinda long but it explains itself and there is an example at the bottom.
/**
* Provides Assertions
**/
class Assert
{
public static function AreEqual( $a, $b )
{
if ( $a != $b )
{
throw new Exception( 'Subjects are not equal.' );
}
}
}
/**
* Provides a loggable entity with information on a test and how it executed
**/
class TestResult
{
protected $_testableInstance = null;
protected $_isSuccess = false;
public function getSuccess()
{
return $this->_isSuccess;
}
protected $_output = '';
public function getOutput()
{
return $_output;
}
public function setOutput( $value )
{
$_output = $value;
}
protected $_test = null;
public function getTest()
{
return $this->_test;
}
public function getName()
{
return $this->_test->getName();
}
public function getComment()
{
return $this->ParseComment( $this->_test->getDocComment() );
}
private function ParseComment( $comment )
{
$lines = explode( "\n", $comment );
for( $i = 0; $i < count( $lines ); $i ++ )
{
$lines[$i] = trim( $lines[ $i ] );
}
return implode( "\n", $lines );
}
protected $_exception = null;
public function getException()
{
return $this->_exception;
}
static public function CreateFailure( Testable $object, ReflectionMethod $test, Exception $exception )
{
$result = new self();
$result->_isSuccess = false;
$result->testableInstance = $object;
$result->_test = $test;
$result->_exception = $exception;
return $result;
}
static public function CreateSuccess( Testable $object, ReflectionMethod $test )
{
$result = new self();
$result->_isSuccess = true;
$result->testableInstance = $object;
$result->_test = $test;
return $result;
}
}
/**
* Provides a base class to derive tests from
**/
abstract class Testable
{
protected $test_log = array();
/**
* Logs the result of a test. keeps track of results for later inspection, Overridable to log elsewhere.
**/
protected function Log( TestResult $result )
{
$this->test_log[] = $result;
printf( "Test: %s was a %s %s\n"
,$result->getName()
,$result->getSuccess() ? 'success' : 'failure'
,$result->getSuccess() ? '' : sprintf( "\n%s (lines:%d-%d; file:%s)"
,$result->getComment()
,$result->getTest()->getStartLine()
,$result->getTest()->getEndLine()
,$result->getTest()->getFileName()
)
);
}
final public function RunTests()
{
$class = new ReflectionClass( $this );
foreach( $class->GetMethods() as $method )
{
$methodname = $method->getName();
if ( strlen( $methodname ) > 4 && substr( $methodname, 0, 4 ) == 'Test' )
{
ob_start();
try
{
$this->$methodname();
$result = TestResult::CreateSuccess( $this, $method );
}
catch( Exception $ex )
{
$result = TestResult::CreateFailure( $this, $method, $ex );
}
$output = ob_get_clean();
$result->setOutput( $output );
$this->Log( $result );
}
}
}
}
/**
* a simple Test suite with two tests
**/
class MyTest extends Testable
{
/**
* This test is designed to fail
**/
public function TestOne()
{
Assert::AreEqual( 1, 2 );
}
/**
* This test is designed to succeed
**/
public function TestTwo()
{
Assert::AreEqual( 1, 1 );
}
}
// this is how to use it.
$test = new MyTest();
$test->RunTests();
This outputs:
Test: TestOne was a failure
/**
* This test is designed to fail
**/ (lines:149-152; file:/Users/kris/Desktop/Testable.php)
Test: TestTwo was a success
Get PHPUnit. It is very easy to use.
Then start with very simple assertions. You can do alot with AssertEquals before you get into anything else. That's a good way to get your feet wet.
You may also want to try writing your test first (since you gave your question the TDD tag) and then write your code. If you haven't done this before it is an eye-opener.
require_once 'ClassYouWantToTest';
require_once 'PHPUnit...blah,blah,whatever';
class ClassYouWantToTest extends PHPUnit...blah,blah,whatever
{
private $ClassYouWantToTest;
protected function setUp ()
{
parent::setUp();
$this->ClassYouWantToTest = new ClassYouWantToTest(/* parameters */);
}
protected function tearDown ()
{
$this->ClassYouWantToTest = null;
parent::tearDown();
}
public function __construct ()
{
// not really needed
}
/**
* Tests ClassYouWantToTest->methodFoo()
*/
public function testMethodFoo ()
{
$this->assertEquals(
$this->ClassYouWantToTest->methodFoo('putValueOfParamHere), 'expectedOutputHere);
/**
* Tests ClassYouWantToTest->methodBar()
*/
public function testMethodFoo ()
{
$this->assertEquals(
$this->ClassYouWantToTest->methodBar('putValueOfParamHere), 'expectedOutputHere);
}
For simple tests AND documentation, php-doctest is quite nice and it's a really easy way to get started since you don't have to open a separate file. Imagine the function below:
/**
* Sums 2 numbers
* <code>
* //doctest: add
* echo add(5,2);
* //expects:
* 7
* </code>
*/
function add($a,$b){
return $a + $b;
}
If you now run this file through phpdt (command-line runner of php-doctest) 1 test will be run. The doctest is contained inside the < code > block. Doctest originated in python and is fine for giving useful & runnable examples on how the code is supposed to work. You can't use it exclusively because the code itself would litter up with test cases but I've found that it's useful alongside a more formal tdd library - i use phpunit.
This 1st answer here sums it up nicely (it's not unit vs doctest ).
phpunit is pretty much the defacto unit testing framework for php. there is also DocTest (available as a PEAR package) and a few others.
php itself is tested for regressions and the like via phpt tests which can also be run via pear.
Codeception tests are much like common unit tests but are much powerful in things where you need mocking and stubbing.
Here is the sample controller test. Notice how easily stubs are created. How easily you check the method was invoked.
<?php
use Codeception\Util\Stub as Stub;
const VALID_USER_ID = 1;
const INVALID_USER_ID = 0;
class UserControllerCest {
public $class = 'UserController';
public function show(CodeGuy $I) {
// prepare environment
$I->haveFakeClass($controller = Stub::makeEmptyExcept($this->class, 'show'));
$I->haveFakeClass($db = Stub::make('DbConnector', array('find' => function($id) { return $id == VALID_USER_ID ? new User() : null ))); };
$I->setProperty($controller, 'db', $db);
$I->executeTestedMethodOn($controller, VALID_USER_ID)
->seeResultEquals(true)
->seeMethodInvoked($controller, 'render');
$I->expect('it will render 404 page for non existent user')
->executeTestedMethodOn($controller, INVALID_USER_ID)
->seeResultNotEquals(true)
->seeMethodInvoked($controller, 'render404','User not found')
->seeMethodNotInvoked($controller, 'render');
}
}
Also there are other cool things. You can test database state, filesystem, etc.
Besides the excellent suggestions about test frameworks already given, are you building your application with one of the PHP web frameworks that has automated testing built in, such as Symfony or CakePHP? Sometimes having a place to just drop in your test methods reduces the start-up friction some people associate with automated testing and TDD.
Way too much to re-post here, but here is a great article on using phpt. It covers a number of aspects around phpt that are often overlooked, so it could be worth a read to expand your knowledge of php beyond just writing a test. Fortunately the article also discusses writing tests!
The main points of discussion
Discover how marginally documented aspects of PHP work (or pretty much any part for that matter)
Write simple unit tests for your own PHP code
Write tests as part of an extension or to convey a potential bug to the internals or QA groups
I know there is a lot of info here already, but since this still shows up on Google searches i might as well add Chinook Test Suite to the list. It is a simple and small test framework.
You can easily test your classes with it and also create mock objects. You run the tests through a web browser and (not yet) through a console.
In the browser you can specify what test class or even what test method to run. Or you can simply run all tests.
A screenshot from the github page:
What i like about it is the way you assert tests. This is done with so called "fluent assertions". Example:
$this->Assert($datetime)->Should()->BeAfter($someDatetime);
And creating mock objects is a breeze too (with a fluent like syntax):
$mock = new CFMock::Create(new DummyClass());
$mock->ACallTo('SomeMethod')->Returns('some value');
Anyway, more info can be found on the github page with a code example as well:
https://github.com/w00/Chinook-TestSuite

Categories