race condition in PHP using pthreads - php

I have small code that demonstrate how to perform a race condition in multithreads PHP.
The idea is I and my friend is sharing the pot for cooking. if the pot already have ingredient, so the pot can not cook.
class Pot:
class Pot
{
public $id;
function __construct()
{
$this->id = rand();
}
public $ingredient;
public function cook($ingredient, $who, $time){
if ($this->ingredient==null){
$this->ingredient = $ingredient;
print "pot".$this->id.'/'.$who." cooking ".$this->ingredient. " time spent: ".$time." \n";
sleep($time);
print "pot".$this->id.'/'.$who." had flush ingredient \n";
$this->ingredient = null;
}else{
throw new Exception("Pot still cook ".$this->ingredient);
}
}
}
class Friend:
class Friend extends Thread
{
/**
* #var Pot
*/
protected $pot;
function run() {
Cocking::cleanVegetable("Friend");
print "Friend will cook: \n";
$this->pot->cook("vegetable", 'Friend',4);
Cocking::digVegetable("Friend");
}
public function __construct($pot)
{
$this->pot = $pot;
}
}
class My:
class My
{
/**
* #var Pot
*/
private $pot;
public function doMyJob(){
Cocking::cleanRice("I");
print "I will cook: \n";
$this->pot->cook("rice", "I",10);
Cocking::digRice("I");
}
public function playGame(Friend $friend){
print "play with friend \n";
}
public function __construct($pot)
{
$this->pot = $pot;
}
}
class Coocking:
<?php
class Cocking
{
static function cleanRice($who){
print $who." is cleaning rice \n";
}
static function cleanVegetable($who){
print $who."is cleaning vegetable \n";
}
static function digRice($who){
print $who." is digging rice \n";
}
static function digVegetable($who){
print $who." is digging vegetable \n";
}
}
running script:
require_once "Friend.php";
require_once "My.php";
require_once "Cocking.php";
require_once "Pot.php";
$pot = new Pot();
$friend = new Friend($pot);
$my = new My($pot);
$friend->start();
$my->doMyJob();
$friend->join();
$my->playGame($friend);
that is so wreid that the output never throw exception? that i assume always happen.
root#e03ed8b56f21:/app/RealLive# php index.php
Friendis cleaning vegetable
I is cleaning rice
Friend will cook:
I will cook:
pot926057642/I cooking rice time spent: 10
pot926057642/Friend cooking vegetable time spent: 4
pot926057642/Friend had flush ingredient
Friend is digging vegetable
pot926057642/I had flush ingredient
I is digging rice
play with friend
the Pot had used by me, but my friend still can use it to cook vegetable. that so freak?
i expect the result would be:
Friend will cook:
I will cook:
pot926057642/I cooking rice time spent: 10
PHP Fatal error: Uncaught Exception: Pot still cook rice in /app/RealLive/Pot.php:23
Stack trace:
#0 /app/RealLive/My.php(14): Pot->cook('rice', 'I', 10)
#1 /app/RealLive/index.php(12): My->doMyJob()
#2 {main}
thrown in /app/RealLive/Pot.php on line 23
ps: my env is
PHP 7.0.10 (cli) (built: Apr 30 2019 21:14:24) ( ZTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2016 Zend Technologies
Many thanks from your comment.

Your assumption seems to be that your if condition followed by an immediate member assign always needs to run in one go. However, it is entirely possible that Friend runs this line of code in the thread:
if ($this->ingredient==null){
... and concludes to go ahead, but before it reaches the next line that assigns $this->ingredient, execution switches back to My/main thread, where it also gets to this line:
if ($this->ingredient==null){
And since Friend has passed the if but not proceeded to actually assigned the ingredient yet, My can now also pass inside. Whatever runs next doesn't matter, you now got both threads accessing the pot cooking at the same time.
Additional correction/note: it seems like that the example also doesn't work since $this->ingredient isn't a Volatile. However, that would still make it prone to above race condition and hence still a bad idea.
How to do it properly: You really need to use a mutex or synchronized section for proper synchronization. Also, never ever assume threads can't switch in the middle of anywhere, including any two lines like an if followed by a variable assign that was meant as a pair.
Here is the PHP documentation on the synchronized section: https://www.php.net/manual/en/threaded.synchronized.php

Reading and writing a variable in a multithreaded application does not guarantee synchronization, you need some synchronization mechanism, the variable should be declared atomic to ensure that only one thread at a time can access it for reading or writing, to guarantee consistency between the two threads, or using mutex to synchronize accesses between shared resources (lock / trylock / unlock).
What is currently happening is that the two threads run parallel, the ingredient variable takes random values ​​depending on the order of execution, and when the longest sleep ends the application exits.
In the following example I used flock which is one of the simplest systems to synchronize accesses between multiple processes, during the tests I had problems because probably the Friend constructor is not executed in the same thread as the run function of the same instance ... there are a lot of factors to take into consideration, Thread in php seems deprecated to me and the implementation a bit convoluted compared to languages ​​like C.
class Friend extends Thread
{
protected $pot;
function run() {
$this->pot->cook("vegetable", 'Friend',2);
}
public function __construct($pot)
{
$this->pot = $pot;
}
}
class Pot
{
public $id;
public $ingredient;
function __construct()
{
$this->id = rand();
}
public function cook($ingredient, $who, $time)
{
$fp = fopen('/tmp/.flock.pot', 'r');
if (flock($fp, LOCK_EX|LOCK_NB)) {
if ($this->ingredient==null){
$this->ingredient = $ingredient;
print "pot".$this->id.'/'.$who." cooking ".$this->ingredient. " time spent: ".$time." \n";
sleep($time);
print "pot".$this->id.'/'.$who." had flush ingredient \n";
$this->ingredient = null;
}
flock($fp, LOCK_UN);
} else {
// throw new Exception("Pot still cook ".$this->ingredient);
print "ingredient busy for {$this->id}/$who\n";
}
fclose($fp);
}
}
class My
{
private $pot;
public function run(){
$this->pot->cook("rice", "I",3);
}
public function __construct($pot)
{
$this->pot = $pot;
}
}
touch('/tmp/.flock.pot');
$pot = new Pot();
$friend = new Friend($pot);
$my = new My($pot);
$friend->start();
sleep(1); // try comment me
$my->run();
$friend->join();
unlink('/tmp/.flock.pot');

Each thread of program have its own memory. at this example it is Pot and is saved in main memory. and one of threads have read & changed it, and the changed would not reflected to main memory,
So other threads can not be see that changed.
so we should make Pot extends Volatile to make the changed can reflected to main memory.
Or make the block synchronized:
if ($this->ingredient==null)
$this->ingredient = $ingredient;

Related

command to handler to aggregate root to repository flow in DDD and CQRS

while learning DDD and cqrs, here is something I need to clarify. In a shopping context, I have a customer which I believe is my aggregate root and I would like to implement the simple use-case of change customer name.
Here is my implementation take on this using DDD/CQRS as much as I know.
My concerns are
for validation, should the command also validate the input to make it conform with the value object or is it okay to leave it to handler?
is my overall flow alright or am I heavily missing somewhere?
If this flow is right, I see that Customer Aggregate root will be a huge class with numerous functions like changeName, changeAddress, changePhoneNumber, deleteSavedPaymentMethod and so on.
It will become a god class, and that seems a bit odd to me, is it actually the right way of DDD aggregate root implementation?
// Value Object
class CustomerName
{
private string $name;
public function __construct(string $name)
{
if(empty($name)){
throw new InvalidNameException();
}
$this->name = $name;
}
}
// Aggregate Root
class Customer
{
private UUID $id;
private CustomerName $name;
public function __construct(UUID $id, CustomerName $name)
{
$this->id = $id;
$this->name = $name;
}
public function changeName(CustomerName $oldName, CustomerName $newName) {
if($oldName !== $this->name){
throw new InconsistencyException('Probably name was already changed');
}
$this->name = $newName;
}
}
// the command
class ChangeNameCommand
{
private string $id;
private string $oldName;
private string $newName;
public function __construct(string $id, string $oldName, string $newName)
{
if(empty($id)){ // only check for non empty string
throw new InvalidIDException();
}
$this->id = $id;
$this->oldName = $oldName;
$this->newName = $newName;
}
public function getNewName(): string
{
return $this->newName; // alternately I could return new CustomerName($this->newName)] ?
}
public function getOldName(): string
{
return $this->oldName;
}
public function getID(): string
{
return $this->id;
}
}
//the handler
class ChangeNameHandler
{
private EventBus $eBus;
public function __construct(EventBus $bus)
{
$this->eBus = $bus;
}
public function handle(ChangeNameCommand $nameCommand) {
try{
// value objects for verification
$newName = new CustomerName($nameCommand->getNewName());
$oldName = new CustomerName($nameCommand->getOldName());
$customerTable = new CustomerTable();
$customerRepo = new CustomerRepo($customerTable);
$id = new UUID($nameCommand->id());
$customer = $customerRepo->find($id);
$customer->changeName($oldName, $newName);
$customerRepo->add($customer);
$event = new CustomerNameChanged($id);
$this->eBus->dispatch($event);
} catch (Exception $e) {
$event = new CustomerNameChangFailed($nameCommand, $e);
$this->eBus->dispatch($event);
}
}
}
//controller
class Controller
{
public function change($request)
{
$cmd = new ChangeNameCommand($request->id, $request->old_name, $request->new_name);
$eventBus = new EventBus();
$handler = new ChangeNameHandler($eventBus);
$handler->handle($cmd);
}
}
PS. some classes like UUID, Repo etc skipped for brevity.
should the command also validate the input to make it conform with the value object or is it okay to leave it to handler?
"Is it okay" -- of course; the DDD police are not going to come after you.
That said, you may be better off in the long run designing your code so that the different concepts are explicit, rather than implicit.
For example:
$cmd = new ChangeNameCommand($request->id, $request->old_name, $request->new_name);
What this tells me -- a newcomer to your code base -- is that ChangeNameCommand is an in memory representation of the schema of your HTTP API, which is to say it is a representation of your contract with your consumers. Customer contracts and domain models don't change for the same reasons, so it may be wise to make the separation of the two explicit in your code (even though the underlying information is "the same").
Validation that the values that appear in the http request really do satisfy the requirements of the customer schema should happen out near the controller, rather than in near the model. It's the controller, after all, that is responsible for returning client errors if the payload doesn't satisfy the schema (ex: 422 Unprocessable Entity).
Having verified that the input is satisfactory, you can then transform the information (if necessary) from the HTTP representation of the information to the domain model's representation. That should always Just Work[tm] -- if it doesn't it indicates that you have a requirements gap somewhere.
It doesn't particularly matter where this translation happens; but if you were to imagine having multiple different schemas, or different interfaces that accept this information (a command line app, or a queue reading service, or something), then the translation code probably belongs with the interface, rather than with the domain model.
is my overall flow alright or am I heavily missing somewhere?
Your composition choices look suspicious - in particular the fact that the lifetime of the EventBus belongs to Controller::change but the lifetime of the CustomerRepo belongs to ChangeNameHander::handle.
It will become a god class...
Then break it up. See Mauro Servienti's 2019 talk.
Truth is: data models that are just storing copies of information provided by the outside world aren't particularly interesting. The good bits, that really justify the investment of work, are the state machines that decide things based on the information provided by the outside world.
If a state machine doesn't use a piece of information to make decisions, then that information belongs "somewhere else" - either a different state machine, or someplace less complicated like a database or a cache.

PHP returns object as null occasionally and unintentionally

We have PHP code in production that sometimes fails with "Call to member function on null", although the same code path executes fine several times before that in one invocation. We have a test that reproduces the error consistently at the same run of the loop.
I already proved that the object gets created correctly in the factory even if it gets returned as null. The factory method must not return null in any case, as indicated in the DocBlock. This question is not related to nullable return types or something like that.
The process does not exceed memory or runtime limitations and I already tried turning off the garbage collector, but no luck. The error happens both in PHP 7.0 and 7.3 on Debian, did not try on other versions or operating systems.
I am not allowed to paste the real code here, but I wrote a simple mockup to explain in more detail. Please keep in mind that this demo code will not result in the error, it is just meant to show the general structure of the program that runs into this fault.
// Three placeholder classes with common methods
class Bender
{
public function common()
{
echo "Bend, bend!" . PHP_EOL;
}
}
class Clamper
{
public function common()
{
echo "Clamp, clamp!" . PHP_EOL;
}
}
class Worker
{
public function common()
{
echo "Work, work!" . PHP_EOL;
}
}
// abstract class with static factory to produce objects
abstract class MomCorp
{
/**
* Factory to create one of several objects
*
* #param string $name
* #return Bender|Clamper|Worker
*/
public static function factory($name)
{
$type = self::managementDecision($name);
switch ($type)
{
case "bender":
$robot = new Bender();
break;
case "clamper":
$robot = new Clamper();
break;
default:
$robot = new Worker();
}
// optional QA works flawlessly here, object is fine all the time!
// $robot->common();
return $robot;
}
public static function managementDecision($name)
{
// irrelevant magic happens on $name here
return "bender";
}
}
foreach (['Rodriguez', 'Angle-ine', 'Flexo', 'Billie'] as $newName)
{
echo "$newName: ";
// The next two lines break after some loops - why?
// The perfectly functional object in factory gets returned as null
$bot = MomCorp::factory($newName);
$bot->common();
}
// SAMPLE OUTPUT
// Rodriguez: Bend, bend!
// Angle-ine: Bend, bend!
// Flexo: Bend, bend!
// Billie: Call to a member function common() on null
Has anyone experienced the same and has any hints on what might cause such an error and how to fix it?

Synchronize and pause Thread in PHP

I am running 2 threads at the same time, but I have critical section where I need to put something in a MySql DB. The problem is that they can put the same thing in at the same time.
I have done some calculations that shows that for indexing 20000 different news pages, the indexes are from 20000 to 20020. (So 0 to 20 are duplicates)
How do I pause one thread while the other is accessing the database?
-----thread.php
class Process extends Thread {
public function __construct($website_url){
$this->website_url = $website_url;
}
public function run() {
work($this->website_url);
}
}
-------------- work
function work($website_url) {
while(condition) {
some work...
if(something->check){ // if this exist in base
mysqli->query("INSERT something IN db...");
prepare bind exec...
}
// between check and insert, second thread can put that element
// critical section is really small but sometimes occurs ...
}
}
------ main.php
$job1 = new Process($website_url,$trigger);
$job2 = new Process($website_url,$trigger);
$job1->start();
$job2->start();
Mutual Exclusion
The simplest way of achieving what you want here is by the use of a single Mutex:
<?php
class Process extends Thread {
public function __construct($url, $mutex) {
$this->url = $url;
$this->mutex = $mutex;
}
public function run() {
work($this->url, $this->mutex);
}
protected $url;
protected $mutex;
}
function work($url, $mutex) {
while (1) {
/* some work */
/* failing to check the return value of calls to acquire
or release mutex is bad form, I haven't done so for brevity */
Mutex::lock($mutex);
{
/* critical section */
printf("working on %s\n", $url);
/* sleeping here shows you that the critical section is
not entered by the second thread, this is obviously not needed */
sleep(1);
}
Mutex::unlock($mutex);
/* breaking here allows the example code to end, not needed */
break;
}
}
$website = "stackoverflow.com";
$lock = Mutex::create();
$jobs = [
new Process($website, $lock),
new Process($website, $lock)
];
foreach ($jobs as $job)
$job->start();
foreach ($jobs as $job)
$job->join();
/* always destroy mutex when finished with them */
Mutex::destroy($lock);
?>
This code should explain itself, I have added a few comments to guide you through it.

Is a destructor the right place to mark the end of a script's execution?

I'm doing some broad performance investigation in an application I maintain and I've set up a simple solution to track the execution time of requests, but I'm unable to find information to verify if this is going to be satisfyingly accurate.
This appears to have netted some good information and I've already eliminated some performance issues as a result, but I'm also seeing some confusing entries that make me question the accuracy of the recorded execution time.
Should I add an explicit method call at the end of each calling script to mark the end of its execution or is this (rather tidy) approach using the destructor good enough?
The calling code at the top of the requested script:
if( file_exists('../../../bench') )
{
require('../../../includes/classes/Bench.php');
$Bench = new Bench;
}
And here is the class definition (reduced for clarity):
require_once('Class.php');
class Bench extends Class
{
protected $page_log = 'bench-pages.log';
protected $page_time_end;
protected $page_time_start;
public function __construct()
{
$this->set_page_time_start();
}
public function __destruct()
{
$this->set_page_time_end();
$this->add_page_record();
}
public function add_page_record()
{
$line = $this->page_time_end - $this->page_time_start.','.
base64_encode(serialize($_SERVER)).','.
base64_encode(serialize($_GET)).','.
base64_encode(serialize($_POST)).','.
base64_encode(serialize($_SESSION))."\n";
$fh = fopen( APP_ROOT . '/' . $this->page_log, 'a' );
fwrite( $fh, $line );
}
public function set_page_time_end()
{
$this->page_time_end = microtime(true);
}
public function set_page_time_start()
{
$this->page_time_start = microtime(true);
}
}
What you need to do is use Xdebug. It is very simple once you set it up and don't have to change anything about your code to get full coverage of what took how long.

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