More SQL command (drop table, drop database)
delete, select & insert upgraded More utility function (node to array, arrayToNode) XMLDB special move command PHP Unit Test
This commit is contained in:
260
XMLDB.php
260
XMLDB.php
@@ -10,25 +10,45 @@ class XMLDB{
|
||||
// Selected table
|
||||
protected $_table;
|
||||
|
||||
// XPATH
|
||||
// XPATH of the doc
|
||||
protected $_xpath;
|
||||
|
||||
// Content of the XML DB File
|
||||
protected $_doc;
|
||||
|
||||
// Name of the main root
|
||||
protected $_databaseName;
|
||||
|
||||
// Name of each table root
|
||||
protected $_tableName;
|
||||
|
||||
// Name of each item inside tables
|
||||
protected $_itemName;
|
||||
|
||||
// Encoding used for the XML
|
||||
protected $_encoding;
|
||||
|
||||
// Node buffered
|
||||
protected $_buffer;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param $file string path to the file to read/create
|
||||
* @param $pk string name of the primary key
|
||||
* @param $createIfNotExist bool create the file if it doesn't exist
|
||||
*/
|
||||
public function __construct($file, $pk = "id", $createIfNotExist = false){
|
||||
public function __construct($file, $pk = "id", $createIfNotExist = false, $databaseName = "Database", $tableName = "table", $itemName = "item", $encoding = "utf-8"){
|
||||
ini_set("display_errors", "off");
|
||||
ini_set("log_errors", "on");
|
||||
ini_set('error_log', $_SERVER['DOCUMENT_ROOT'].'/test/XMLDB/XMLDB.log');
|
||||
$this->_primaryKey = $pk;
|
||||
$this->_file = $file;
|
||||
$this->_doc = new DOMDocument;
|
||||
$this->_buffer = null;
|
||||
$this->_databaseName = $databaseName;
|
||||
$this->_itemName = $itemName;
|
||||
$this->_tableName = $tableName;
|
||||
$this->_encoding = $encoding;
|
||||
$this->_primaryKey = $pk;
|
||||
$this->_file = $file;
|
||||
$this->_doc = new DOMDocument;
|
||||
$this->_doc->preserveWhiteSpace = false;
|
||||
$this->_doc->formatOutput = true;
|
||||
if($this->_doc->load($this->_file)){
|
||||
@@ -38,27 +58,39 @@ class XMLDB{
|
||||
if($createIfNotExist){
|
||||
$this->createDatabase($file);
|
||||
}else{
|
||||
$this->_file = null;
|
||||
$this->_doc = null;
|
||||
$this->xpath = null;
|
||||
$this->_file = null;
|
||||
$this->_doc = null;
|
||||
$this->xpath = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function createDatabase($file){
|
||||
$this->_file = $file;
|
||||
$this->_doc = DOMDocument::loadXML('<?xml version="1.0" encoding="utf-8"?>
|
||||
<Database>
|
||||
</Database>');
|
||||
$this->_xpath = new DOMXpath($this->_doc);
|
||||
$this->_file = $file;
|
||||
$this->_doc = DOMDocument::loadXML('<?xml version="1.0" encoding="' . $this->_encoding . '"?>
|
||||
<' . $this->_databaseName . '>
|
||||
</' . $this->_databaseName . '>');
|
||||
$this->_xpath = new DOMXpath($this->_doc);
|
||||
return $this->save();
|
||||
}
|
||||
|
||||
public function dropDatabase($definitely = false){
|
||||
if($definitely){
|
||||
unlink($this->_file);
|
||||
}else{
|
||||
$this->createDatabase($this->_file);
|
||||
}
|
||||
}
|
||||
|
||||
public function createTable($name){
|
||||
if($name == '*' || $this->tableAlreadyExists($name))
|
||||
return false;
|
||||
else
|
||||
return $this->insertNode(array('name'=>'table', 'attributes'=>array('name'=>$name)));
|
||||
return $this->insert(array('name'=>$this->_tableName, 'attributes'=>array('name'=>$name)));
|
||||
}
|
||||
|
||||
public function dropTable($table){
|
||||
return $this->delete($table);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,11 +109,6 @@ class XMLDB{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public function selectTable($name, $format = 'node'){
|
||||
return $this->select($name, null, null, null, $format);
|
||||
}
|
||||
|
||||
public function isLoaded(){
|
||||
if($this->_doc != null)
|
||||
return true;
|
||||
@@ -105,6 +132,14 @@ class XMLDB{
|
||||
return $this->_xpath;
|
||||
}
|
||||
|
||||
public function setBuffer($node){
|
||||
$this->_buffer = $node;
|
||||
}
|
||||
|
||||
public function getBuffer($buffer){
|
||||
return $this->_buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saving the DB file
|
||||
*/
|
||||
@@ -119,43 +154,88 @@ class XMLDB{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function getResult($request, $format){
|
||||
switch($format){
|
||||
case "node":
|
||||
return $request;
|
||||
break;
|
||||
case "count":
|
||||
return $request->length;
|
||||
break;
|
||||
case "array":
|
||||
default:
|
||||
return $this->requestToArray($request);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO checking $request
|
||||
public function requestToArray($request){
|
||||
private function requestToArray($request){
|
||||
$return = array();
|
||||
$number = 0;
|
||||
|
||||
foreach($request as $element){
|
||||
/*if($childName != null && $childValue != null)
|
||||
$element = $element->parentNode;*/
|
||||
$elementValue = $element->attributes->item(0)->value;
|
||||
$return[$elementValue]['attributes'] = array();
|
||||
$return[$elementValue]['childs'] = array();
|
||||
$return[$number]['name'] = $this->_itemName;
|
||||
$return[$number]['attributes'] = array($this->_primaryKey => $elementValue);
|
||||
$return[$number]['childs'] = array();
|
||||
|
||||
//Retrieving Attributes
|
||||
$attributes = $element->attributes;
|
||||
$length = $attributes->length;
|
||||
for ($i = 1; $i <= $length; $i++) {
|
||||
$return[$elementValue]['attributes'][$attributes->item($i)->name] = $attributes->item($i)->value;
|
||||
for ($i = 0; $i <= $length; $i++) {
|
||||
if($attributes->item($i)->name != '')
|
||||
$return[$number]['attributes'][$attributes->item($i)->name] = $attributes->item($i)->value;
|
||||
}
|
||||
|
||||
// Retrivieving childs
|
||||
$nodes = $element->childNodes;
|
||||
$length = $nodes->length;
|
||||
for ($i = 1; $i <= $length; $i++) {
|
||||
$return[$elementValue]['childs'][$nodes->item($i)->nodeName] = $nodes->item($i)->nodeValue;
|
||||
for ($i = 0; $i <= $length; $i++) {
|
||||
if($nodes->item($i)->nodeName != '')
|
||||
$return[$number]['childs'][$nodes->item($i)->nodeName] = $nodes->item($i)->nodeValue;
|
||||
}
|
||||
$number++;
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function arrayToNode($node){
|
||||
if(!is_array($node) || !in_array($node['name'], array($this->_tableName, $this->_itemName)))
|
||||
return;
|
||||
$element = $this->_doc->createElement($node['name']);
|
||||
if(isset($node['attributes'])){
|
||||
foreach($node['attributes'] as $attributeName=>$attributeValue){
|
||||
if($attributeName != '')
|
||||
$element->setAttribute($attributeName, $attributeValue);
|
||||
}
|
||||
}
|
||||
if(isset($node['childs'])){
|
||||
foreach($node['childs'] as $childName=>$childValue){
|
||||
if($childName != ''){
|
||||
$newElement = $this->_doc->createElement($childName, $childValue);
|
||||
$element->appendChild($newElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $element;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shortcuts for select
|
||||
*/
|
||||
public function selectTable($name, $format = 'node'){
|
||||
return $this->select($name, null, null, null, $format);
|
||||
}
|
||||
public function selectFromAttribute($table, $attributes, $format = 'array'){
|
||||
return $this->select($table, null, $attributes, null, $format);
|
||||
}
|
||||
|
||||
public function selectFromChilds($table, $childs, $format = 'array'){
|
||||
}
|
||||
public function selectFromChildren($table, $childs, $format = 'array'){
|
||||
return $this->select($table, null, null, $childs, $format);
|
||||
}
|
||||
|
||||
public function selectFromPK($table, $pk, $format = "array"){
|
||||
return $this->select($table, $pk, null, null, $format);
|
||||
}
|
||||
@@ -168,43 +248,24 @@ class XMLDB{
|
||||
* @param $attributes array name/value of the attribute
|
||||
* @return array
|
||||
*/
|
||||
public function select($from, $id = null, $attributes = null, $childs = null, $format = 'array'){
|
||||
if (!$from) {
|
||||
throw new Exception('uhoh, no table selected');
|
||||
}
|
||||
if($id != null){
|
||||
private function select($from, $id = null, $attributes = null, $childs = null, $format = 'array'){
|
||||
if($id != null && !is_array($id)){
|
||||
$attribute = '[@' . $this->_primaryKey . ' = "' . $id . '"]';
|
||||
}
|
||||
if($attributes != null){
|
||||
if($attributes != null && is_array($attributes)){
|
||||
foreach($attributes as $attributeName=>$attributeValue)
|
||||
$attribute .= '[@' . $attributeName . ' = "' . $attributeValue . '"]';
|
||||
}
|
||||
if($childs != null){
|
||||
if($childs != null && is_array($childs)){
|
||||
foreach($childs as $childName=>$childValue)
|
||||
$child .= '[' . $childName . '="' . $childValue . '"]';
|
||||
/*$child .= '/' . $childName . '[.="' . $childValue . '"]';
|
||||
if(count($childs)>1){
|
||||
$child = str_replace('/', '', $child);
|
||||
$child = '/'.$child;
|
||||
}*/
|
||||
}
|
||||
if($from == '*')
|
||||
$request = $this->_xpath->query('//item'.$attribute.$child);
|
||||
else
|
||||
$request = $this->_xpath->query('//table[@name = "'.$from.'"]/item'.$attribute.$child);
|
||||
$request = $this->_xpath->query('//' . $this->_tableName . '[@name = "'.$from.'"]/'.$this->_itemName.$attribute.$child);
|
||||
|
||||
switch($format){
|
||||
case "node":
|
||||
$return = $request;
|
||||
break;
|
||||
case "count":
|
||||
$return = $request->length;
|
||||
break;
|
||||
case "array":
|
||||
default:
|
||||
$return = $this->requestToArray($request);
|
||||
}
|
||||
return $return;
|
||||
return $this->getResult($request, $format);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -214,7 +275,7 @@ class XMLDB{
|
||||
if (!$from || !$xpath) {
|
||||
throw new Exception('uhoh, no table selected');
|
||||
}
|
||||
$request = $this->_xpath->query('//table[@name = "'.$from.'"]/'.$xpath);
|
||||
$request = $this->_xpath->query('//' . $this->_tableName . '[@name = "'.$from.'"]/'.$xpath);
|
||||
switch($format){
|
||||
case "node":
|
||||
$return = $request;
|
||||
@@ -237,25 +298,25 @@ class XMLDB{
|
||||
* @param $position string 'before' or 'after'
|
||||
* @return bool
|
||||
*/
|
||||
public function insertItem($id, $attributes = null, $childs = null, $table){
|
||||
if($attributes == null)
|
||||
$attributes = array($this->_primaryKey=>$id);
|
||||
else
|
||||
$attributes += array($this->_primaryKey=>$id);
|
||||
if($this->tableAlreadyExists($table) && !$this->pkAlreadyExists($id))
|
||||
return $this->insert(array('name'=>$this->_itemName, 'attributes'=>$attributes, 'childs'=>$childs), $table);
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO other $where and $position
|
||||
public function insertNode($node, $table = null, $position = null){
|
||||
if(!is_array($node) || !isset($node['name']) || !isset($node['attributes']))
|
||||
// TODO $position
|
||||
private function insert($node, $table = null, $position = null){
|
||||
if(isset($node[0]))
|
||||
$node = $node[0];
|
||||
if(!is_array($node) || !isset($node['name']) || !isset($node['attributes'])){
|
||||
return false;
|
||||
|
||||
}
|
||||
// Creating the node from an array
|
||||
$element = $this->_doc->createElement($node['name']);
|
||||
if(isset($node['attributes'])){
|
||||
foreach($node['attributes'] as $attributeName=>$attributeValue){
|
||||
$element->setAttribute($attributeName, $attributeValue);
|
||||
}
|
||||
}
|
||||
if(isset($node['childs'])){
|
||||
foreach($node['childs'] as $childName=>$childValue){
|
||||
$newElement = $this->_doc->createElement($childName, $childValue);
|
||||
$element->appendChild($newElement);
|
||||
}
|
||||
}
|
||||
$element = $this->arrayToNode($node);
|
||||
|
||||
// Inserting the node into the DB
|
||||
// case : creation of a new table
|
||||
@@ -266,10 +327,11 @@ class XMLDB{
|
||||
if(!$this->tableAlreadyExists($table) || $this->pkAlreadyExists($node['attributes'][$this->_primaryKey], $table)){
|
||||
return false;
|
||||
}
|
||||
$request = $this->_xpath->query('//table[@name = "'.$table.'"]');
|
||||
$request = $this->_xpath->query('//' . $this->_tableName . '[@name = "'.$table.'"]');
|
||||
$request->item(0)->appendChild($element);
|
||||
}else
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
return $this->save();
|
||||
}
|
||||
|
||||
@@ -315,23 +377,53 @@ class XMLDB{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public function deleteNode($table, $id = null, $attributes = null){
|
||||
public function deleteItem($table, $id = null, $attributes = null){
|
||||
if($id == null && $attributes == null)
|
||||
return false;
|
||||
if($id != null)
|
||||
return $this->delete($table, $id);
|
||||
return $this->delete($table, null, $attributes);
|
||||
}
|
||||
/**
|
||||
* Delete an entry
|
||||
* @param $table name of the table in which the entry is
|
||||
* @param $id $attributes array where condition(s)
|
||||
* @return bool
|
||||
*/
|
||||
private function delete($table, $id = null, $attributes = null){
|
||||
if($id != null && $attributes != null)
|
||||
return false;
|
||||
if($id != null)
|
||||
$request = $this->select($table, $id, null, null, 'node')->item(0);
|
||||
$request = $this->selectFromPK($table, $id, 'node')->item(0);
|
||||
if($attributes != null)
|
||||
$request = $this->select($table, null, array($attribute[0]=>$attribute[1]), null, 'node')->item(0);
|
||||
$request->parentNode->removeChild($request);
|
||||
$request = $this->selectFromAttribute($table, array($attribute[0]=>$attribute[1]), 'node')->item(0);
|
||||
if($attributes == null && $id == null)
|
||||
$request = $this->selectTable($table);
|
||||
if($request == null)
|
||||
return false;
|
||||
try{
|
||||
$request->parentNode->removeChild($request);
|
||||
}catch(Exception $e){
|
||||
echo $e->getMessage();
|
||||
return false;
|
||||
}
|
||||
return $this->save();
|
||||
}
|
||||
|
||||
public function moveNode($node, $from, $to){
|
||||
|
||||
public function deleteNode($node){
|
||||
if($node == null)
|
||||
return false;
|
||||
$node = $node->item(0);
|
||||
$node->parentNode->removeChild($node);
|
||||
return $this->save();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public function move($node, $to, $itemAfter = ''){
|
||||
$this->_buffer = $node;
|
||||
if($this->deleteNode($node)){
|
||||
$nodeArray = $this->requestToArray($this->_buffer);
|
||||
return $this->insert($nodeArray, $to);
|
||||
}else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,104 +1,60 @@
|
||||
<?
|
||||
require_once('simpletest/autorun.php');
|
||||
require_once('log.php');
|
||||
require_once 'XMLDB.php';
|
||||
echo 'Creating new DB (should be "ok" creating a new file "test")<br/>';
|
||||
if($xmldbtest1 = new XMLDB('test.xml', 'id', true))
|
||||
echo "ok";
|
||||
else
|
||||
echo "ko";
|
||||
echo '<br/>Creating new table (should be "ok" adding table1 in test)<br/>';
|
||||
if($xmldbtest1->createTable('table1'))
|
||||
echo "ok";
|
||||
else
|
||||
echo "ko";
|
||||
|
||||
echo '<br/>loading an inexistant DB (error)<br/>';
|
||||
$xmldbtest = new XMLDB('text.xml');
|
||||
if(!$xmldbtest->isLoaded()){
|
||||
echo '<br/><br/>can\'t load test.xml<br/>';
|
||||
$xmldbtest = new XMLDB('database.xml');
|
||||
if($xmldbtest->isLoaded()){
|
||||
// Testing select($from, $attributeName = null, $attributeValue = null, $childName = null, $childValue = null)
|
||||
echo 'Testing empty select (exception)<br/>';
|
||||
try {
|
||||
$xmldbtest->select();
|
||||
}
|
||||
catch(Exception $e){
|
||||
echo $e->getMessage().'<br/><br/>';
|
||||
}
|
||||
class TestOfLogging extends UnitTestCase {
|
||||
function test(){
|
||||
//$this->expectException(new Exception('uhoh, no table selected'));
|
||||
@unlink('test.log');
|
||||
$log = new Log('test.log');
|
||||
$log->message('creating "test.xml" with a table');
|
||||
$this->assertTrue($xmldbtest1 = new XMLDB('test.xml', 'id', true));
|
||||
$this->assertTrue($xmldbtest1->createTable('table1'));
|
||||
//$xmldbtest = new XMLDB('text.xml');
|
||||
//$log->message('Trying to load an inexistant DB');
|
||||
//$this->assertFalse($xmldbtest->isLoaded());
|
||||
$log->message('Trying to load "database.xml"');
|
||||
$xmldbtest = new XMLDB('database.xml');
|
||||
$this->assertTrue($xmldbtest->isLoaded());
|
||||
|
||||
echo '<br/>Creating new table name "*" (should be "ko" table already exist)<br/>';
|
||||
if($xmldbtest->createTable('*'))
|
||||
echo "ok";
|
||||
else
|
||||
echo "ko";
|
||||
|
||||
echo '<br/>Creating new table (should be "ko" table already exist)<br/>';
|
||||
if($xmldbtest->createTable('table1'))
|
||||
echo "ok";
|
||||
else
|
||||
echo "ko";
|
||||
echo '<br/>Testing insertNode at XML root (should insert an item named "test" into table1)<br/>';
|
||||
if($xmldbtest->insertNode(array('name'=>'item', 'attributes'=>array('id'=>'test'), 'childs'=>array('visibility'=>'true', 'x'=>'33')), 'table1'))
|
||||
echo "ok<br/><br/>";
|
||||
else
|
||||
echo "ko<br/><br/>";
|
||||
$log->message('Trying to create 2 wrong tables');
|
||||
$this->assertFalse($xmldbtest->createTable('*'));
|
||||
$this->assertFalse($xmldbtest->createTable('table1'));
|
||||
$log->message('Trying to insert item "test" into "table1"');
|
||||
$this->assertTrue($xmldbtest->insertItem('test', array('pwet'=>'cacahuete'), array('visibility'=>'true', 'x'=>'33'), 'table1'));
|
||||
$log->message('Trying to do several select');
|
||||
$this->assertEqual($xmldbtest->selectTable('table1', 'count'), 8);
|
||||
$this->assertEqual($xmldbtest->selectFromPK('table1', 'weather', 'count'), 1);
|
||||
$this->assertEqual($xmldbtest->selectFromAttribute('table1', array('id'=>'weather'), 'count'),1);
|
||||
$this->assertEqual($xmldbtest->selectFromChildren('table1',array('visibility'=>'true'),'count'), 6);
|
||||
$this->assertEqual($xmldbtest->selectFromChildren('*',array('visibility'=>'true'),'count'), 8);
|
||||
$this->assertEqual($xmldbtest->selectFromChildren('table1',array('visibility'=>'true', 'x'=>'32'),'count'), 1);
|
||||
$log->message('Trying to do several inserts');
|
||||
$this->assertFalse($xmldbtest->insertItem('test', null, array('visibility'=>'true', 'x'=>'33'), 'table1'));
|
||||
$this->assertTrue($xmldbtest->insertItem('test2', null, array('visibility'=>'true', 'x'=>'33'), 'table1'));
|
||||
$log->message('Trying to do several updates');
|
||||
$this->assertTrue($xmldbtest->updateNodeAttribute('table1', array('id', 'links'), array('id', 'zelda')));
|
||||
$this->assertTrue($xmldbtest->updateNodeValue('table1', array('id', 'notes'), null, 'booga!'));
|
||||
$log->message('Trying to delete the item "clock" from "table1"');
|
||||
//var_dump($xmldbtest->selectFromPK('table1', 'clock', 'node'), true);
|
||||
//$this->assertTrue($xmldbtest->deleteNode($xmldbtest->selectFromPK('table1', 'clock', 'node')));
|
||||
$this->assertTrue($xmldbtest->deleteItem('table1','clock'));
|
||||
//var_dump($xmldbtest->selectFromPK('table1', 'search', 'node')->item(0));
|
||||
$log->message('Trying to move an element to another table');
|
||||
$this->assertTrue($xmldbtest->move( $xmldbtest->selectFromPK('table1', 'search', 'node'), 'table2'));
|
||||
|
||||
echo 'Testing select (should give 8 results - 7 from the file + 1 we created earlier)<br/>';
|
||||
$result = $xmldbtest->select('table1');
|
||||
echo count($result).' results <br/><br/>';
|
||||
$log->message('Trying to erase a DB');
|
||||
$xmldbtest1->dropDatabase(true);
|
||||
|
||||
echo 'Testing select id=weather with pk (should find 1 result)<br/>';
|
||||
$result = $xmldbtest->selectFromPK('table1', 'weather');
|
||||
echo count($result).' results <br/><br/>';
|
||||
|
||||
echo 'Testing select id=weather (should find 1 result)<br/>';
|
||||
$result = $xmldbtest->select('table1', null, array('id'=>'weather'));
|
||||
echo count($result).' results <br/><br/>';
|
||||
|
||||
echo 'Testing select visibility = true (should throw 6 results - 5 from the base file + 1 we created)<br/>';
|
||||
$result = $xmldbtest->select('table1', null,null,array('visibility'=>'true'));
|
||||
echo count($result).' results<br/><br/>';
|
||||
/*$log->message('Trying to select w/o anything');
|
||||
$xmldbtest->select();*/
|
||||
}
|
||||
|
||||
echo 'Testing select visibility = true on all tables (should find 8 results - 7 from the base file + 1 we created)<br/>';
|
||||
$result = $xmldbtest->select('*', null,null,array('visibility'=>'true'));
|
||||
echo count($result).' results <br/><br/>';
|
||||
|
||||
echo 'Testing select visibility = true and x = 32 (experimental - should find 1)<br/>';
|
||||
$result = $xmldbtest->selectFromChilds('table1',array('visibility'=>'true', 'x'=>'32'));
|
||||
echo count($result).' results<br/><br/>';
|
||||
|
||||
echo '<br/>Testing insertNode that already exist (should do "ko")<br/>';
|
||||
if($xmldbtest->insertNode(array('name'=>'item', 'attributes'=>array('id'=>'test'), 'childs'=>array('visibility'=>'true', 'x'=>'33')), 'table1'))
|
||||
echo "ok<br/><br/>";
|
||||
else
|
||||
echo "ko<br/><br/>";
|
||||
|
||||
echo '<br/>Testing insertNode that doesn\'t exist (should do "ok")<br/>';
|
||||
if($xmldbtest->insertNode(array('name'=>'item', 'attributes'=>array('id'=>'test2'), 'childs'=>array('visibility'=>'true', 'x'=>'33')), 'table1'))
|
||||
echo "ok<br/><br/>";
|
||||
else
|
||||
echo "ko<br/><br/>";
|
||||
|
||||
echo '<br/>Testing updatingNodeAttribute with no insert (should be ok and change the item "links" into "zelda" haha)<br/>';
|
||||
if($xmldbtest->updateNodeAttribute('table1', array('id', 'links'), array('id', 'zelda')))
|
||||
echo "ok<br/><br/>";
|
||||
else
|
||||
echo "ko<br/><br/>";
|
||||
|
||||
echo '<br/>Testing updateNodeValue via attribute (should be ok - inserting "booga!" into the item named "notes")<br/>';
|
||||
if($xmldbtest->updateNodeValue('table1', array('id', 'notes'), null, 'booga!'))
|
||||
echo "ok<br/><br/>";
|
||||
else
|
||||
echo "ko<br/><br/>";
|
||||
|
||||
echo '<br/>Testing deleteNode via pk (should be ok - deleting the item clock into table1)<br/>';
|
||||
if($xmldbtest->deleteNode('table1', 'clock', null))
|
||||
echo "ok<br/><br/>";
|
||||
else
|
||||
echo "ko<br/><br/>";
|
||||
|
||||
}else{
|
||||
exit("can't load config.xml either");
|
||||
}
|
||||
}
|
||||
|
||||
$test = new TestOfLogging;
|
||||
$test->test();
|
||||
|
||||
|
||||
|
||||
23
log.php
Normal file
23
log.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?
|
||||
class Log {
|
||||
|
||||
protected $logfile;
|
||||
|
||||
function __construct($filename) {
|
||||
$file = $filename;
|
||||
$this->logfile = fopen($file, 'a+');
|
||||
$this->message('Starting log');
|
||||
}
|
||||
|
||||
function message($message) {
|
||||
$message = '['. date("Y-m-d / H:i:s") . ']'.' - '.$message;
|
||||
$message .= "\n";
|
||||
return fwrite( $this->logfile, $message );
|
||||
}
|
||||
|
||||
function __destruct(){
|
||||
$this->message('Finishing log');
|
||||
return fclose( $this->logfile );
|
||||
}
|
||||
}
|
||||
|
||||
348
simpletest/HELP_MY_TESTS_DONT_WORK_ANYMORE
Normal file
348
simpletest/HELP_MY_TESTS_DONT_WORK_ANYMORE
Normal file
@@ -0,0 +1,348 @@
|
||||
Simple Test interface changes
|
||||
=============================
|
||||
Because the SimpleTest tool set is still evolving it is likely that tests
|
||||
written with earlier versions will fail with the newest ones. The most
|
||||
dramatic changes are in the alpha releases. Here is a list of possible
|
||||
problems and their fixes...
|
||||
|
||||
No method getRelativeUrls() or getAbsoluteUrls()
|
||||
------------------------------------------------
|
||||
These methods were always a bit weird anyway, and
|
||||
the new parsing of the base tag makes them more so.
|
||||
They have been replaced with getUrls() instead. If
|
||||
you want the old functionality then simply chop
|
||||
off the current domain from getUrl().
|
||||
|
||||
Method setWildcard() removed in mocks
|
||||
-------------------------------------
|
||||
Even setWildcard() has been removed in 1.0.1beta now.
|
||||
If you want to test explicitely for a '*' string, then
|
||||
simply pass in new IdenticalExpectation('*') instead.
|
||||
|
||||
No method _getTest() on mocks
|
||||
-----------------------------
|
||||
This has finally been removed. It was a pretty esoteric
|
||||
flex point anyway. It was there to allow the mocks to
|
||||
work with other test tools, but no one does this.
|
||||
|
||||
No method assertError(), assertNoErrors(), swallowErrors()
|
||||
----------------------------------------------------------
|
||||
These have been deprecated in 1.0.1beta in favour of
|
||||
expectError() and expectException(). assertNoErrors() is
|
||||
redundant if you use expectError() as failures are now reported
|
||||
immediately.
|
||||
|
||||
No method TestCase::signal()
|
||||
----------------------------
|
||||
This has been deprecated in favour of triggering an error or
|
||||
throwing an exception. Deprecated as of 1.0.1beta.
|
||||
|
||||
No method TestCase::sendMessage()
|
||||
---------------------------------
|
||||
This has been deprecated as of 1.0.1beta.
|
||||
|
||||
Failure to connect now emits failures
|
||||
-------------------------------------
|
||||
It used to be that you would have to use the
|
||||
getTransferError() call on the web tester to see if
|
||||
there was a socket level error in a fetch. This check
|
||||
is now always carried out by the WebTestCase unless
|
||||
the fetch is prefaced with WebTestCase::ignoreErrors().
|
||||
The ignore directive only lasts for test case fetching
|
||||
action such as get() and click().
|
||||
|
||||
No method SimpleTestOptions::ignore()
|
||||
-------------------------------------
|
||||
This is deprecated in version 1.0.1beta and has been moved
|
||||
to SimpleTest::ignore() as that is more readable. In
|
||||
addition, parent classes are also ignored automatically.
|
||||
If you are using PHP5 you can skip this directive simply
|
||||
by marking your test case as abstract.
|
||||
|
||||
No method assertCopy()
|
||||
----------------------
|
||||
This is deprecated in 1.0.1 in favour of assertClone().
|
||||
The assertClone() method is slightly different in that
|
||||
the objects must be identical, but without being a
|
||||
reference. It is thus not a strict inversion of
|
||||
assertReference().
|
||||
|
||||
Constructor wildcard override has no effect in mocks
|
||||
----------------------------------------------------
|
||||
As of 1.0.1beta this is now set with setWildcard() instead
|
||||
of in the constructor.
|
||||
|
||||
No methods setStubBaseClass()/getStubBaseClass()
|
||||
------------------------------------------------
|
||||
As mocks are now used instead of stubs, these methods
|
||||
stopped working and are now removed as of the 1.0.1beta
|
||||
release. The mock objects may be freely used instead.
|
||||
|
||||
No method addPartialMockCode()
|
||||
------------------------------
|
||||
The ability to insert arbitrary partial mock code
|
||||
has been removed. This was a low value feature
|
||||
causing needless complications. It was removed
|
||||
in the 1.0.1beta release.
|
||||
|
||||
No method setMockBaseClass()
|
||||
----------------------------
|
||||
The ability to change the mock base class has been
|
||||
scheduled for removal and is deprecated since the
|
||||
1.0.1beta version. This was a rarely used feature
|
||||
except as a workaround for PHP5 limitations. As
|
||||
these limitations are being resolved it's hoped
|
||||
that the bundled mocks can be used directly.
|
||||
|
||||
No class Stub
|
||||
-------------
|
||||
Server stubs are deprecated from 1.0.1 as the mocks now
|
||||
have exactly the same interface. Just use mock objects
|
||||
instead.
|
||||
|
||||
No class SimpleTestOptions
|
||||
--------------------------
|
||||
This was replced by the shorter SimpleTest in 1.0.1beta1
|
||||
and is since deprecated.
|
||||
|
||||
No file simple_test.php
|
||||
-----------------------
|
||||
This was renamed test_case.php in 1.0.1beta to more accurately
|
||||
reflect it's purpose. This file should never be directly
|
||||
included in test suites though, as it's part of the
|
||||
underlying mechanics and has a tendency to be refactored.
|
||||
|
||||
No class WantedPatternExpectation
|
||||
---------------------------------
|
||||
This was deprecated in 1.0.1alpha in favour of the simpler
|
||||
name PatternExpectation.
|
||||
|
||||
No class NoUnwantedPatternExpectation
|
||||
-------------------------------------
|
||||
This was deprecated in 1.0.1alpha in favour of the simpler
|
||||
name NoPatternExpectation.
|
||||
|
||||
No method assertNoUnwantedPattern()
|
||||
-----------------------------------
|
||||
This has been renamed to assertNoPattern() in 1.0.1alpha and
|
||||
the old form is deprecated.
|
||||
|
||||
No method assertWantedPattern()
|
||||
-------------------------------
|
||||
This has been renamed to assertPattern() in 1.0.1alpha and
|
||||
the old form is deprecated.
|
||||
|
||||
No method assertExpectation()
|
||||
-----------------------------
|
||||
This was renamed as assert() in 1.0.1alpha and the old form
|
||||
has been deprecated.
|
||||
|
||||
No class WildcardExpectation
|
||||
----------------------------
|
||||
This was a mostly internal class for the mock objects. It was
|
||||
renamed AnythingExpectation to bring it closer to JMock and
|
||||
NMock in version 1.0.1alpha.
|
||||
|
||||
Missing UnitTestCase::assertErrorPattern()
|
||||
------------------------------------------
|
||||
This method is deprecated for version 1.0.1 onwards.
|
||||
This method has been subsumed by assertError() that can now
|
||||
take an expectation. Simply pass a PatternExpectation
|
||||
into assertError() to simulate the old behaviour.
|
||||
|
||||
No HTML when matching page elements
|
||||
-----------------------------------
|
||||
This behaviour has been switched to using plain text as if it
|
||||
were seen by the user of the browser. This means that HTML tags
|
||||
are suppressed, entities are converted and whitespace is
|
||||
normalised. This should make it easier to match items in forms.
|
||||
Also images are replaced with their "alt" text so that they
|
||||
can be matched as well.
|
||||
|
||||
No method SimpleRunner::_getTestCase()
|
||||
--------------------------------------
|
||||
This was made public as getTestCase() in 1.0RC2.
|
||||
|
||||
No method restartSession()
|
||||
--------------------------
|
||||
This was renamed to restart() in the WebTestCase, SimpleBrowser
|
||||
and the underlying SimpleUserAgent in 1.0RC2. Because it was
|
||||
undocumented anyway, no attempt was made at backward
|
||||
compatibility.
|
||||
|
||||
My custom test case ignored by tally()
|
||||
--------------------------------------
|
||||
The _assertTrue method has had it's signature changed due to a bug
|
||||
in the PHP 5.0.1 release. You must now use getTest() from within
|
||||
that method to get the test case. Mock compatibility with other
|
||||
unit testers is now deprecated as of 1.0.1alpha as PEAR::PHPUnit2
|
||||
should soon have mock support of it's own.
|
||||
|
||||
Broken code extending SimpleRunner
|
||||
----------------------------------
|
||||
This was replaced with SimpleScorer so that I could use the runner
|
||||
name in another class. This happened in RC1 development and there
|
||||
is no easy backward compatibility fix. The solution is simply to
|
||||
extend SimpleScorer instead.
|
||||
|
||||
Missing method getBaseCookieValue()
|
||||
-----------------------------------
|
||||
This was renamed getCurrentCookieValue() in RC1.
|
||||
|
||||
Missing files from the SimpleTest suite
|
||||
---------------------------------------
|
||||
Versions of SimpleTest prior to Beta6 required a SIMPLE_TEST constant
|
||||
to point at the SimpleTest folder location before any of the toolset
|
||||
was loaded. This is no longer documented as it is now unnecessary
|
||||
for later versions. If you are using an earlier version you may
|
||||
need this constant. Consult the documentation that was bundled with
|
||||
the release that you are using or upgrade to Beta6 or later.
|
||||
|
||||
No method SimpleBrowser::getCurrentUrl()
|
||||
--------------------------------------
|
||||
This is replaced with the more versatile showRequest() for
|
||||
debugging. It only existed in this context for version Beta5.
|
||||
Later versions will have SimpleBrowser::getHistory() for tracking
|
||||
paths through pages. It is renamed as getUrl() since 1.0RC1.
|
||||
|
||||
No method Stub::setStubBaseClass()
|
||||
----------------------------------
|
||||
This method has finally been removed in 1.0RC1. Use
|
||||
SimpleTestOptions::setStubBaseClass() instead.
|
||||
|
||||
No class CommandLineReporter
|
||||
----------------------------
|
||||
This was renamed to TextReporter in Beta3 and the deprecated version
|
||||
was removed in 1.0RC1.
|
||||
|
||||
No method requireReturn()
|
||||
-------------------------
|
||||
This was deprecated in Beta3 and is now removed.
|
||||
|
||||
No method expectCookie()
|
||||
------------------------
|
||||
This method was abruptly removed in Beta4 so as to simplify the internals
|
||||
until another mechanism can replace it. As a workaround it is necessary
|
||||
to assert that the cookie has changed by setting it before the page
|
||||
fetch and then assert the desired value.
|
||||
|
||||
No method clickSubmitByFormId()
|
||||
-------------------------------
|
||||
This method had an incorrect name as no button was involved. It was
|
||||
renamed to submitByFormId() in Beta4 and the old version deprecated.
|
||||
Now removed.
|
||||
|
||||
No method paintStart() or paintEnd()
|
||||
------------------------------------
|
||||
You should only get this error if you have subclassed the lower level
|
||||
reporting and test runner machinery. These methods have been broken
|
||||
down into events for test methods, events for test cases and events
|
||||
for group tests. The new methods are...
|
||||
|
||||
paintStart() --> paintMethodStart(), paintCaseStart(), paintGroupStart()
|
||||
paintEnd() --> paintMethodEnd(), paintCaseEnd(), paintGroupEnd()
|
||||
|
||||
This change was made in Beta3, ironically to make it easier to subclass
|
||||
the inner machinery. Simply duplicating the code you had in the previous
|
||||
methods should provide a temporary fix.
|
||||
|
||||
No class TestDisplay
|
||||
--------------------
|
||||
This has been folded into SimpleReporter in Beta3 and is now deprecated.
|
||||
It was removed in RC1.
|
||||
|
||||
No method WebTestCase::fetch()
|
||||
------------------------------
|
||||
This was renamed get() in Alpha8. It is removed in Beta3.
|
||||
|
||||
No method submit()
|
||||
------------------
|
||||
This has been renamed clickSubmit() in Beta1. The old method was
|
||||
removed in Beta2.
|
||||
|
||||
No method clearHistory()
|
||||
------------------------
|
||||
This method is deprecated in Beta2 and removed in RC1.
|
||||
|
||||
No method getCallCount()
|
||||
------------------------
|
||||
This method has been deprecated since Beta1 and has now been
|
||||
removed. There are now more ways to set expectations on counts
|
||||
and so this method should be unecessery. Removed in RC1.
|
||||
|
||||
Cannot find file *
|
||||
------------------
|
||||
The following public name changes have occoured...
|
||||
|
||||
simple_html_test.php --> reporter.php
|
||||
simple_mock.php --> mock_objects.php
|
||||
simple_unit.php --> unit_tester.php
|
||||
simple_web.php --> web_tester.php
|
||||
|
||||
The old names were deprecated in Alpha8 and removed in Beta1.
|
||||
|
||||
No method attachObserver()
|
||||
--------------------------
|
||||
Prior to the Alpha8 release the old internal observer pattern was
|
||||
gutted and replaced with a visitor. This is to trade flexibility of
|
||||
test case expansion against the ease of writing user interfaces.
|
||||
|
||||
Code such as...
|
||||
|
||||
$test = &new MyTestCase();
|
||||
$test->attachObserver(new TestHtmlDisplay());
|
||||
$test->run();
|
||||
|
||||
...should be rewritten as...
|
||||
|
||||
$test = &new MyTestCase();
|
||||
$test->run(new HtmlReporter());
|
||||
|
||||
If you previously attached multiple observers then the workaround
|
||||
is to run the tests twice, once with each, until they can be combined.
|
||||
For one observer the old method is simulated in Alpha 8, but is
|
||||
removed in Beta1.
|
||||
|
||||
No class TestHtmlDisplay
|
||||
------------------------
|
||||
This class has been renamed to HtmlReporter in Alpha8. It is supported,
|
||||
but deprecated in Beta1 and removed in Beta2. If you have subclassed
|
||||
the display for your own design, then you will have to extend this
|
||||
class (HtmlReporter) instead.
|
||||
|
||||
If you have accessed the event queue by overriding the notify() method
|
||||
then I am afraid you are in big trouble :(. The reporter is now
|
||||
carried around the test suite by the runner classes and the methods
|
||||
called directly. In the unlikely event that this is a problem and
|
||||
you don't want to upgrade the test tool then simplest is to write your
|
||||
own runner class and invoke the tests with...
|
||||
|
||||
$test->accept(new MyRunner(new MyReporter()));
|
||||
|
||||
...rather than the run method. This should be easier to extend
|
||||
anyway and gives much more control. Even this method is overhauled
|
||||
in Beta3 where the runner class can be set within the test case. Really
|
||||
the best thing to do is to upgrade to this version as whatever you were
|
||||
trying to achieve before should now be very much easier.
|
||||
|
||||
Missing set options method
|
||||
--------------------------
|
||||
All test suite options are now in one class called SimpleTestOptions.
|
||||
This means that options are set differently...
|
||||
|
||||
GroupTest::ignore() --> SimpleTestOptions::ignore()
|
||||
Mock::setMockBaseClass() --> SimpleTestOptions::setMockBaseClass()
|
||||
|
||||
These changed in Alpha8 and the old versions are now removed in RC1.
|
||||
|
||||
No method setExpected*()
|
||||
------------------------
|
||||
The mock expectations changed their names in Alpha4 and the old names
|
||||
ceased to be supported in Alpha8. The changes are...
|
||||
|
||||
setExpectedArguments() --> expectArguments()
|
||||
setExpectedArgumentsSequence() --> expectArgumentsAt()
|
||||
setExpectedCallCount() --> expectCallCount()
|
||||
setMaximumCallCount() --> expectMaximumCallCount()
|
||||
|
||||
The parameters remained the same.
|
||||
502
simpletest/LICENSE
Normal file
502
simpletest/LICENSE
Normal file
@@ -0,0 +1,502 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
||||
108
simpletest/README
Normal file
108
simpletest/README
Normal file
@@ -0,0 +1,108 @@
|
||||
SimpleTest
|
||||
==========
|
||||
You probably got this package from...
|
||||
http://simpletest.sourceforge.net/projects/simpletest/
|
||||
|
||||
If there is no licence agreement with this package please download
|
||||
a version from the location above. You must read and accept that
|
||||
licence to use this software. The file is titled simply LICENSE.
|
||||
|
||||
What is it? It's a framework for unit testing, web site testing and
|
||||
mock objects for PHP 4.2.0+ (and PHP 5.0 to 5.3 without E_STRICT).
|
||||
|
||||
If you have used JUnit, you will find this PHP unit testing version very
|
||||
similar. Also included is a mock objects and server stubs generator.
|
||||
The stubs can have return values set for different arguments, can have
|
||||
sequences set also by arguments and can return items by reference.
|
||||
The mocks inherit all of this functionality and can also have
|
||||
expectations set, again in sequences and for different arguments.
|
||||
|
||||
A web tester similar in concept to JWebUnit is also included. There is no
|
||||
JavaScript or tables support, but forms, authentication, cookies and
|
||||
frames are handled.
|
||||
|
||||
You can see a release schedule at http://www.lastcraft.com/overview.php
|
||||
which is also copied to the documentation folder with this release.
|
||||
A full PHPDocumenter API documentation exists at
|
||||
http://simpletest.sourceforge.net/.
|
||||
|
||||
The user interface is minimal
|
||||
in the extreme, but a lot of information flows from the test suite.
|
||||
After version 1.0 we will release a better web UI, but we are leaving XUL
|
||||
and GTk versions to volunteers as everybody has their own opinion
|
||||
on a good GUI, and we don't want to discourage development by shipping
|
||||
one with the toolkit. YOucan download an Eclipse plug-in separately.
|
||||
|
||||
You are looking at a second full release. The unit tests for SimpleTest
|
||||
itself can be run here...
|
||||
|
||||
simpletest/test/unit_tests.php
|
||||
|
||||
And tests involving live network connections as well are here...
|
||||
|
||||
simpletest/test/all_tests.php
|
||||
|
||||
The full tests will typically overrun the 8Mb limit often allowed
|
||||
to a PHP process. A workaround is to run the tests on the command
|
||||
with a custom php.ini file if you do not have access to your server
|
||||
version.
|
||||
|
||||
You will have to edit the all_tests.php file if you are accesssing
|
||||
the internet through a proxy server. See the comments in all_tests.php
|
||||
for instructions.
|
||||
|
||||
The full tests read some test data from the LastCraft site. If the site
|
||||
is down or has been modified for a later version then you will get
|
||||
spurious errors. A unit_tests.php failure on the other hand would be
|
||||
very serious. As far as we know we haven't yet managed to check in any
|
||||
unit test failures, so please correct us if you find one.
|
||||
|
||||
Even if all of the tests run please verify that your existing test suites
|
||||
also function as expected. If they don't see the file...
|
||||
|
||||
HELP_MY_TESTS_DONT_WORK_ANYMORE
|
||||
|
||||
This contains information on interface changes. It also points out
|
||||
deprecated interfaces, so you should read this even if all of
|
||||
your current tests appear to run.
|
||||
|
||||
There is a documentation folder which contains the core reference information
|
||||
in English and French, although this information is fairly basic.
|
||||
You can find a tutorial on...
|
||||
|
||||
http://www.lastcraft.com/first_test_tutorial.php
|
||||
|
||||
...to get you started and this material will eventually become included
|
||||
with the project documentation. A French translation exists at...
|
||||
|
||||
http://www.onpk.net/index.php/2005/01/12/254-tutoriel-simpletest-decouvrir-les-tests-unitaires.
|
||||
|
||||
If you download and use, and possibly even extend this tool, please let us
|
||||
know. Any feedback, even bad, is always welcome and we will work to get
|
||||
your suggestions into the next release. Ideally please send your
|
||||
comments to...
|
||||
|
||||
simpletest-support@lists.sourceforge.net
|
||||
|
||||
...so that others can read them too. We usually try to respond within 48
|
||||
hours.
|
||||
|
||||
There is no change log except at Sourceforge. You can visit the
|
||||
release notes to see the completed TODO list after each cycle and also the
|
||||
status of any bugs, but if the bug is recent then it will be fixed in SVN only.
|
||||
The SVN check-ins always have all the tests passing and so SVN snapshots should
|
||||
be pretty usable, although the code may not look so good internally.
|
||||
|
||||
Oh, yes. It is called "Simple" because it should be simple to
|
||||
use. We intend to add a complete set of tools for a test first
|
||||
and "test as you code" type of development. "Simple" does not
|
||||
mean "Lite" in this context.
|
||||
|
||||
Thanks to everyone who has sent comments and offered suggestions. They
|
||||
really are invaluable, but sadly you are too many to mention in full.
|
||||
Thanks to all on the advanced PHP forum on SitePoint, especially Harry
|
||||
Feucks. Early adopters are always an inspiration.
|
||||
|
||||
Marcus Baker, Jason Sweat, Travis Swicegood, Perrick Penet and Edward Z. Yang.
|
||||
--
|
||||
marcus@lastcraft.com
|
||||
1
simpletest/VERSION
Normal file
1
simpletest/VERSION
Normal file
@@ -0,0 +1 @@
|
||||
1.0.1
|
||||
238
simpletest/authentication.php
Normal file
238
simpletest/authentication.php
Normal file
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
/**
|
||||
* Base include file for SimpleTest
|
||||
* @package SimpleTest
|
||||
* @subpackage WebTester
|
||||
* @version $Id: authentication.php 1720 2008-04-07 02:32:43Z lastcraft $
|
||||
*/
|
||||
/**
|
||||
* include http class
|
||||
*/
|
||||
require_once(dirname(__FILE__) . '/http.php');
|
||||
|
||||
/**
|
||||
* Represents a single security realm's identity.
|
||||
* @package SimpleTest
|
||||
* @subpackage WebTester
|
||||
*/
|
||||
class SimpleRealm {
|
||||
var $_type;
|
||||
var $_root;
|
||||
var $_username;
|
||||
var $_password;
|
||||
|
||||
/**
|
||||
* Starts with the initial entry directory.
|
||||
* @param string $type Authentication type for this
|
||||
* realm. Only Basic authentication
|
||||
* is currently supported.
|
||||
* @param SimpleUrl $url Somewhere in realm.
|
||||
* @access public
|
||||
*/
|
||||
function SimpleRealm($type, $url) {
|
||||
$this->_type = $type;
|
||||
$this->_root = $url->getBasePath();
|
||||
$this->_username = false;
|
||||
$this->_password = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds another location to the realm.
|
||||
* @param SimpleUrl $url Somewhere in realm.
|
||||
* @access public
|
||||
*/
|
||||
function stretch($url) {
|
||||
$this->_root = $this->_getCommonPath($this->_root, $url->getPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the common starting path.
|
||||
* @param string $first Path to compare.
|
||||
* @param string $second Path to compare.
|
||||
* @return string Common directories.
|
||||
* @access private
|
||||
*/
|
||||
function _getCommonPath($first, $second) {
|
||||
$first = explode('/', $first);
|
||||
$second = explode('/', $second);
|
||||
for ($i = 0; $i < min(count($first), count($second)); $i++) {
|
||||
if ($first[$i] != $second[$i]) {
|
||||
return implode('/', array_slice($first, 0, $i)) . '/';
|
||||
}
|
||||
}
|
||||
return implode('/', $first) . '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the identity to try within this realm.
|
||||
* @param string $username Username in authentication dialog.
|
||||
* @param string $username Password in authentication dialog.
|
||||
* @access public
|
||||
*/
|
||||
function setIdentity($username, $password) {
|
||||
$this->_username = $username;
|
||||
$this->_password = $password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor for current identity.
|
||||
* @return string Last succesful username.
|
||||
* @access public
|
||||
*/
|
||||
function getUsername() {
|
||||
return $this->_username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor for current identity.
|
||||
* @return string Last succesful password.
|
||||
* @access public
|
||||
*/
|
||||
function getPassword() {
|
||||
return $this->_password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to see if the URL is within the directory
|
||||
* tree of the realm.
|
||||
* @param SimpleUrl $url URL to test.
|
||||
* @return boolean True if subpath.
|
||||
* @access public
|
||||
*/
|
||||
function isWithin($url) {
|
||||
if ($this->_isIn($this->_root, $url->getBasePath())) {
|
||||
return true;
|
||||
}
|
||||
if ($this->_isIn($this->_root, $url->getBasePath() . $url->getPage() . '/')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests to see if one string is a substring of
|
||||
* another.
|
||||
* @param string $part Small bit.
|
||||
* @param string $whole Big bit.
|
||||
* @return boolean True if the small bit is
|
||||
* in the big bit.
|
||||
* @access private
|
||||
*/
|
||||
function _isIn($part, $whole) {
|
||||
return strpos($whole, $part) === 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages security realms.
|
||||
* @package SimpleTest
|
||||
* @subpackage WebTester
|
||||
*/
|
||||
class SimpleAuthenticator {
|
||||
var $_realms;
|
||||
|
||||
/**
|
||||
* Clears the realms.
|
||||
* @access public
|
||||
*/
|
||||
function SimpleAuthenticator() {
|
||||
$this->restartSession();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts with no realms set up.
|
||||
* @access public
|
||||
*/
|
||||
function restartSession() {
|
||||
$this->_realms = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new realm centered the current URL.
|
||||
* Browsers vary wildly on their behaviour in this
|
||||
* regard. Mozilla ignores the realm and presents
|
||||
* only when challenged, wasting bandwidth. IE
|
||||
* just carries on presenting until a new challenge
|
||||
* occours. SimpleTest tries to follow the spirit of
|
||||
* the original standards committee and treats the
|
||||
* base URL as the root of a file tree shaped realm.
|
||||
* @param SimpleUrl $url Base of realm.
|
||||
* @param string $type Authentication type for this
|
||||
* realm. Only Basic authentication
|
||||
* is currently supported.
|
||||
* @param string $realm Name of realm.
|
||||
* @access public
|
||||
*/
|
||||
function addRealm($url, $type, $realm) {
|
||||
$this->_realms[$url->getHost()][$realm] = new SimpleRealm($type, $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current identity to be presented
|
||||
* against that realm.
|
||||
* @param string $host Server hosting realm.
|
||||
* @param string $realm Name of realm.
|
||||
* @param string $username Username for realm.
|
||||
* @param string $password Password for realm.
|
||||
* @access public
|
||||
*/
|
||||
function setIdentityForRealm($host, $realm, $username, $password) {
|
||||
if (isset($this->_realms[$host][$realm])) {
|
||||
$this->_realms[$host][$realm]->setIdentity($username, $password);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the name of the realm by comparing URLs.
|
||||
* @param SimpleUrl $url URL to test.
|
||||
* @return SimpleRealm Name of realm.
|
||||
* @access private
|
||||
*/
|
||||
function _findRealmFromUrl($url) {
|
||||
if (! isset($this->_realms[$url->getHost()])) {
|
||||
return false;
|
||||
}
|
||||
foreach ($this->_realms[$url->getHost()] as $name => $realm) {
|
||||
if ($realm->isWithin($url)) {
|
||||
return $realm;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Presents the appropriate headers for this location.
|
||||
* @param SimpleHttpRequest $request Request to modify.
|
||||
* @param SimpleUrl $url Base of realm.
|
||||
* @access public
|
||||
*/
|
||||
function addHeaders(&$request, $url) {
|
||||
if ($url->getUsername() && $url->getPassword()) {
|
||||
$username = $url->getUsername();
|
||||
$password = $url->getPassword();
|
||||
} elseif ($realm = $this->_findRealmFromUrl($url)) {
|
||||
$username = $realm->getUsername();
|
||||
$password = $realm->getPassword();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
$this->addBasicHeaders($request, $username, $password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Presents the appropriate headers for this
|
||||
* location for basic authentication.
|
||||
* @param SimpleHttpRequest $request Request to modify.
|
||||
* @param string $username Username for realm.
|
||||
* @param string $password Password for realm.
|
||||
* @access public
|
||||
* @static
|
||||
*/
|
||||
function addBasicHeaders(&$request, $username, $password) {
|
||||
if ($username && $password) {
|
||||
$request->addHeaderLine(
|
||||
'Authorization: Basic ' . base64_encode("$username:$password"));
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
87
simpletest/autorun.php
Normal file
87
simpletest/autorun.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
/**
|
||||
* Autorunner which runs all tests cases found in a file
|
||||
* that includes this module.
|
||||
* @package SimpleTest
|
||||
* @version $Id: autorun.php 1721 2008-04-07 19:27:10Z lastcraft $
|
||||
*/
|
||||
require_once dirname(__FILE__) . '/unit_tester.php';
|
||||
require_once dirname(__FILE__) . '/mock_objects.php';
|
||||
require_once dirname(__FILE__) . '/collector.php';
|
||||
require_once dirname(__FILE__) . '/default_reporter.php';
|
||||
|
||||
$GLOBALS['SIMPLETEST_AUTORUNNER_INITIAL_CLASSES'] = get_declared_classes();
|
||||
register_shutdown_function('simpletest_autorun');
|
||||
|
||||
/**
|
||||
* Exit handler to run all recent test cases if no test has
|
||||
* so far been run. Uses the DefaultReporter which can have
|
||||
* it's output controlled with SimpleTest::prefer().
|
||||
*/
|
||||
function simpletest_autorun() {
|
||||
if (tests_have_run()) {
|
||||
return;
|
||||
}
|
||||
$candidates = array_intersect(
|
||||
capture_new_classes(),
|
||||
classes_defined_in_initial_file());
|
||||
$loader = new SimpleFileLoader();
|
||||
$suite = $loader->createSuiteFromClasses(
|
||||
basename(initial_file()),
|
||||
$loader->selectRunnableTests($candidates));
|
||||
$result = $suite->run(new DefaultReporter());
|
||||
if (SimpleReporter::inCli()) {
|
||||
exit($result ? 0 : 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the current test context to see if a test has
|
||||
* ever been run.
|
||||
* @return boolean True if tests have run.
|
||||
*/
|
||||
function tests_have_run() {
|
||||
if ($context = SimpleTest::getContext()) {
|
||||
return (boolean)$context->getTest();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The first autorun file.
|
||||
* @return string Filename of first autorun script.
|
||||
*/
|
||||
function initial_file() {
|
||||
static $file = false;
|
||||
if (! $file) {
|
||||
$file = reset(get_included_files());
|
||||
}
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Just the classes from the first autorun script. May
|
||||
* get a few false positives, as it just does a regex based
|
||||
* on following the word "class".
|
||||
* @return array List of all possible classes in first
|
||||
* autorun script.
|
||||
*/
|
||||
function classes_defined_in_initial_file() {
|
||||
if (preg_match_all('/\bclass\s+(\w+)/i', file_get_contents(initial_file()), $matches)) {
|
||||
return array_map('strtolower', $matches[1]);
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Every class since the first autorun include. This
|
||||
* is safe enough if require_once() is alwyas used.
|
||||
* @return array Class names.
|
||||
*/
|
||||
function capture_new_classes() {
|
||||
global $SIMPLETEST_AUTORUNNER_INITIAL_CLASSES;
|
||||
return array_map('strtolower', array_diff(get_declared_classes(),
|
||||
$SIMPLETEST_AUTORUNNER_INITIAL_CLASSES ?
|
||||
$SIMPLETEST_AUTORUNNER_INITIAL_CLASSES : array()));
|
||||
}
|
||||
?>
|
||||
1098
simpletest/browser.php
Normal file
1098
simpletest/browser.php
Normal file
File diff suppressed because it is too large
Load Diff
122
simpletest/collector.php
Normal file
122
simpletest/collector.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
/**
|
||||
* This file contains the following classes: {@link SimpleCollector},
|
||||
* {@link SimplePatternCollector}.
|
||||
*
|
||||
* @author Travis Swicegood <development@domain51.com>
|
||||
* @package SimpleTest
|
||||
* @subpackage UnitTester
|
||||
* @version $Id: collector.php 1723 2008-04-08 00:34:10Z lastcraft $
|
||||
*/
|
||||
|
||||
/**
|
||||
* The basic collector for {@link GroupTest}
|
||||
*
|
||||
* @see collect(), GroupTest::collect()
|
||||
* @package SimpleTest
|
||||
* @subpackage UnitTester
|
||||
*/
|
||||
class SimpleCollector {
|
||||
|
||||
/**
|
||||
* Strips off any kind of slash at the end so as to normalise the path.
|
||||
* @param string $path Path to normalise.
|
||||
* @return string Path without trailing slash.
|
||||
*/
|
||||
function _removeTrailingSlash($path) {
|
||||
if (substr($path, -1) == DIRECTORY_SEPARATOR) {
|
||||
return substr($path, 0, -1);
|
||||
} elseif (substr($path, -1) == '/') {
|
||||
return substr($path, 0, -1);
|
||||
} else {
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans the directory and adds what it can.
|
||||
* @param object $test Group test with {@link GroupTest::addTestFile()} method.
|
||||
* @param string $path Directory to scan.
|
||||
* @see _attemptToAdd()
|
||||
*/
|
||||
function collect(&$test, $path) {
|
||||
$path = $this->_removeTrailingSlash($path);
|
||||
if ($handle = opendir($path)) {
|
||||
while (($entry = readdir($handle)) !== false) {
|
||||
if ($this->_isHidden($entry)) {
|
||||
continue;
|
||||
}
|
||||
$this->_handle($test, $path . DIRECTORY_SEPARATOR . $entry);
|
||||
}
|
||||
closedir($handle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method determines what should be done with a given file and adds
|
||||
* it via {@link GroupTest::addTestFile()} if necessary.
|
||||
*
|
||||
* This method should be overriden to provide custom matching criteria,
|
||||
* such as pattern matching, recursive matching, etc. For an example, see
|
||||
* {@link SimplePatternCollector::_handle()}.
|
||||
*
|
||||
* @param object $test Group test with {@link GroupTest::addTestFile()} method.
|
||||
* @param string $filename A filename as generated by {@link collect()}
|
||||
* @see collect()
|
||||
* @access protected
|
||||
*/
|
||||
function _handle(&$test, $file) {
|
||||
if (is_dir($file)) {
|
||||
return;
|
||||
}
|
||||
$test->addTestFile($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for hidden files so as to skip them. Currently
|
||||
* only tests for Unix hidden files.
|
||||
* @param string $filename Plain filename.
|
||||
* @return boolean True if hidden file.
|
||||
* @access private
|
||||
*/
|
||||
function _isHidden($filename) {
|
||||
return strncmp($filename, '.', 1) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An extension to {@link SimpleCollector} that only adds files matching a
|
||||
* given pattern.
|
||||
*
|
||||
* @package SimpleTest
|
||||
* @subpackage UnitTester
|
||||
* @see SimpleCollector
|
||||
*/
|
||||
class SimplePatternCollector extends SimpleCollector {
|
||||
var $_pattern;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $pattern Perl compatible regex to test name against
|
||||
* See {@link http://us4.php.net/manual/en/reference.pcre.pattern.syntax.php PHP's PCRE}
|
||||
* for full documentation of valid pattern.s
|
||||
*/
|
||||
function SimplePatternCollector($pattern = '/php$/i') {
|
||||
$this->_pattern = $pattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to add files that match a given pattern.
|
||||
*
|
||||
* @see SimpleCollector::_handle()
|
||||
* @param object $test Group test with {@link GroupTest::addTestFile()} method.
|
||||
* @param string $path Directory to scan.
|
||||
* @access protected
|
||||
*/
|
||||
function _handle(&$test, $filename) {
|
||||
if (preg_match($this->_pattern, $filename)) {
|
||||
parent::_handle($test, $filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
173
simpletest/compatibility.php
Normal file
173
simpletest/compatibility.php
Normal file
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
/**
|
||||
* base include file for SimpleTest
|
||||
* @package SimpleTest
|
||||
* @version $Id: compatibility.php 1723 2008-04-08 00:34:10Z lastcraft $
|
||||
*/
|
||||
|
||||
/**
|
||||
* Static methods for compatibility between different
|
||||
* PHP versions.
|
||||
* @package SimpleTest
|
||||
*/
|
||||
class SimpleTestCompatibility {
|
||||
|
||||
/**
|
||||
* Creates a copy whether in PHP5 or PHP4.
|
||||
* @param object $object Thing to copy.
|
||||
* @return object A copy.
|
||||
* @access public
|
||||
* @static
|
||||
*/
|
||||
function copy($object) {
|
||||
if (version_compare(phpversion(), '5') >= 0) {
|
||||
eval('$copy = clone $object;');
|
||||
return $copy;
|
||||
}
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identity test. Drops back to equality + types for PHP5
|
||||
* objects as the === operator counts as the
|
||||
* stronger reference constraint.
|
||||
* @param mixed $first Test subject.
|
||||
* @param mixed $second Comparison object.
|
||||
* @return boolean True if identical.
|
||||
* @access public
|
||||
* @static
|
||||
*/
|
||||
function isIdentical($first, $second) {
|
||||
if (version_compare(phpversion(), '5') >= 0) {
|
||||
return SimpleTestCompatibility::_isIdenticalType($first, $second);
|
||||
}
|
||||
if ($first != $second) {
|
||||
return false;
|
||||
}
|
||||
return ($first === $second);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive type test.
|
||||
* @param mixed $first Test subject.
|
||||
* @param mixed $second Comparison object.
|
||||
* @return boolean True if same type.
|
||||
* @access private
|
||||
* @static
|
||||
*/
|
||||
function _isIdenticalType($first, $second) {
|
||||
if (gettype($first) != gettype($second)) {
|
||||
return false;
|
||||
}
|
||||
if (is_object($first) && is_object($second)) {
|
||||
if (get_class($first) != get_class($second)) {
|
||||
return false;
|
||||
}
|
||||
return SimpleTestCompatibility::_isArrayOfIdenticalTypes(
|
||||
get_object_vars($first),
|
||||
get_object_vars($second));
|
||||
}
|
||||
if (is_array($first) && is_array($second)) {
|
||||
return SimpleTestCompatibility::_isArrayOfIdenticalTypes($first, $second);
|
||||
}
|
||||
if ($first !== $second) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive type test for each element of an array.
|
||||
* @param mixed $first Test subject.
|
||||
* @param mixed $second Comparison object.
|
||||
* @return boolean True if identical.
|
||||
* @access private
|
||||
* @static
|
||||
*/
|
||||
function _isArrayOfIdenticalTypes($first, $second) {
|
||||
if (array_keys($first) != array_keys($second)) {
|
||||
return false;
|
||||
}
|
||||
foreach (array_keys($first) as $key) {
|
||||
$is_identical = SimpleTestCompatibility::_isIdenticalType(
|
||||
$first[$key],
|
||||
$second[$key]);
|
||||
if (! $is_identical) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for two variables being aliases.
|
||||
* @param mixed $first Test subject.
|
||||
* @param mixed $second Comparison object.
|
||||
* @return boolean True if same.
|
||||
* @access public
|
||||
* @static
|
||||
*/
|
||||
function isReference(&$first, &$second) {
|
||||
if (version_compare(phpversion(), '5', '>=') && is_object($first)) {
|
||||
return ($first === $second);
|
||||
}
|
||||
if (is_object($first) && is_object($second)) {
|
||||
$id = uniqid("test");
|
||||
$first->$id = true;
|
||||
$is_ref = isset($second->$id);
|
||||
unset($first->$id);
|
||||
return $is_ref;
|
||||
}
|
||||
$temp = $first;
|
||||
$first = uniqid("test");
|
||||
$is_ref = ($first === $second);
|
||||
$first = $temp;
|
||||
return $is_ref;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to see if an object is a member of a
|
||||
* class hiearchy.
|
||||
* @param object $object Object to test.
|
||||
* @param string $class Root name of hiearchy.
|
||||
* @return boolean True if class in hiearchy.
|
||||
* @access public
|
||||
* @static
|
||||
*/
|
||||
function isA($object, $class) {
|
||||
if (version_compare(phpversion(), '5') >= 0) {
|
||||
if (! class_exists($class, false)) {
|
||||
if (function_exists('interface_exists')) {
|
||||
if (! interface_exists($class, false)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
eval("\$is_a = \$object instanceof $class;");
|
||||
return $is_a;
|
||||
}
|
||||
if (function_exists('is_a')) {
|
||||
return is_a($object, $class);
|
||||
}
|
||||
return ((strtolower($class) == get_class($object))
|
||||
or (is_subclass_of($object, $class)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a socket timeout for each chunk.
|
||||
* @param resource $handle Socket handle.
|
||||
* @param integer $timeout Limit in seconds.
|
||||
* @access public
|
||||
* @static
|
||||
*/
|
||||
function setTimeout($handle, $timeout) {
|
||||
if (function_exists('stream_set_timeout')) {
|
||||
stream_set_timeout($handle, $timeout, 0);
|
||||
} elseif (function_exists('socket_set_timeout')) {
|
||||
socket_set_timeout($handle, $timeout, 0);
|
||||
} elseif (function_exists('set_socket_timeout')) {
|
||||
set_socket_timeout($handle, $timeout, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
380
simpletest/cookies.php
Normal file
380
simpletest/cookies.php
Normal file
@@ -0,0 +1,380 @@
|
||||
<?php
|
||||
/**
|
||||
* Base include file for SimpleTest
|
||||
* @package SimpleTest
|
||||
* @subpackage WebTester
|
||||
* @version $Id: cookies.php 1723 2008-04-08 00:34:10Z lastcraft $
|
||||
*/
|
||||
|
||||
/**#@+
|
||||
* include other SimpleTest class files
|
||||
*/
|
||||
require_once(dirname(__FILE__) . '/url.php');
|
||||
/**#@-*/
|
||||
|
||||
/**
|
||||
* Cookie data holder. Cookie rules are full of pretty
|
||||
* arbitary stuff. I have used...
|
||||
* http://wp.netscape.com/newsref/std/cookie_spec.html
|
||||
* http://www.cookiecentral.com/faq/
|
||||
* @package SimpleTest
|
||||
* @subpackage WebTester
|
||||
*/
|
||||
class SimpleCookie {
|
||||
var $_host;
|
||||
var $_name;
|
||||
var $_value;
|
||||
var $_path;
|
||||
var $_expiry;
|
||||
var $_is_secure;
|
||||
|
||||
/**
|
||||
* Constructor. Sets the stored values.
|
||||
* @param string $name Cookie key.
|
||||
* @param string $value Value of cookie.
|
||||
* @param string $path Cookie path if not host wide.
|
||||
* @param string $expiry Expiry date as string.
|
||||
* @param boolean $is_secure Currently ignored.
|
||||
*/
|
||||
function SimpleCookie($name, $value = false, $path = false, $expiry = false, $is_secure = false) {
|
||||
$this->_host = false;
|
||||
$this->_name = $name;
|
||||
$this->_value = $value;
|
||||
$this->_path = ($path ? $this->_fixPath($path) : "/");
|
||||
$this->_expiry = false;
|
||||
if (is_string($expiry)) {
|
||||
$this->_expiry = strtotime($expiry);
|
||||
} elseif (is_integer($expiry)) {
|
||||
$this->_expiry = $expiry;
|
||||
}
|
||||
$this->_is_secure = $is_secure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the host. The cookie rules determine
|
||||
* that the first two parts are taken for
|
||||
* certain TLDs and three for others. If the
|
||||
* new host does not match these rules then the
|
||||
* call will fail.
|
||||
* @param string $host New hostname.
|
||||
* @return boolean True if hostname is valid.
|
||||
* @access public
|
||||
*/
|
||||
function setHost($host) {
|
||||
if ($host = $this->_truncateHost($host)) {
|
||||
$this->_host = $host;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor for the truncated host to which this
|
||||
* cookie applies.
|
||||
* @return string Truncated hostname.
|
||||
* @access public
|
||||
*/
|
||||
function getHost() {
|
||||
return $this->_host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for a cookie being valid for a host name.
|
||||
* @param string $host Host to test against.
|
||||
* @return boolean True if the cookie would be valid
|
||||
* here.
|
||||
*/
|
||||
function isValidHost($host) {
|
||||
return ($this->_truncateHost($host) === $this->getHost());
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts just the domain part that determines a
|
||||
* cookie's host validity.
|
||||
* @param string $host Host name to truncate.
|
||||
* @return string Domain or false on a bad host.
|
||||
* @access private
|
||||
*/
|
||||
function _truncateHost($host) {
|
||||
$tlds = SimpleUrl::getAllTopLevelDomains();
|
||||
if (preg_match('/[a-z\-]+\.(' . $tlds . ')$/i', $host, $matches)) {
|
||||
return $matches[0];
|
||||
} elseif (preg_match('/[a-z\-]+\.[a-z\-]+\.[a-z\-]+$/i', $host, $matches)) {
|
||||
return $matches[0];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor for name.
|
||||
* @return string Cookie key.
|
||||
* @access public
|
||||
*/
|
||||
function getName() {
|
||||
return $this->_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor for value. A deleted cookie will
|
||||
* have an empty string for this.
|
||||
* @return string Cookie value.
|
||||
* @access public
|
||||
*/
|
||||
function getValue() {
|
||||
return $this->_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor for path.
|
||||
* @return string Valid cookie path.
|
||||
* @access public
|
||||
*/
|
||||
function getPath() {
|
||||
return $this->_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a path to see if the cookie applies
|
||||
* there. The test path must be longer or
|
||||
* equal to the cookie path.
|
||||
* @param string $path Path to test against.
|
||||
* @return boolean True if cookie valid here.
|
||||
* @access public
|
||||
*/
|
||||
function isValidPath($path) {
|
||||
return (strncmp(
|
||||
$this->_fixPath($path),
|
||||
$this->getPath(),
|
||||
strlen($this->getPath())) == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor for expiry.
|
||||
* @return string Expiry string.
|
||||
* @access public
|
||||
*/
|
||||
function getExpiry() {
|
||||
if (! $this->_expiry) {
|
||||
return false;
|
||||
}
|
||||
return gmdate("D, d M Y H:i:s", $this->_expiry) . " GMT";
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to see if cookie is expired against
|
||||
* the cookie format time or timestamp.
|
||||
* Will give true for a session cookie.
|
||||
* @param integer/string $now Time to test against. Result
|
||||
* will be false if this time
|
||||
* is later than the cookie expiry.
|
||||
* Can be either a timestamp integer
|
||||
* or a cookie format date.
|
||||
* @access public
|
||||
*/
|
||||
function isExpired($now) {
|
||||
if (! $this->_expiry) {
|
||||
return true;
|
||||
}
|
||||
if (is_string($now)) {
|
||||
$now = strtotime($now);
|
||||
}
|
||||
return ($this->_expiry < $now);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ages the cookie by the specified number of
|
||||
* seconds.
|
||||
* @param integer $interval In seconds.
|
||||
* @public
|
||||
*/
|
||||
function agePrematurely($interval) {
|
||||
if ($this->_expiry) {
|
||||
$this->_expiry -= $interval;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor for the secure flag.
|
||||
* @return boolean True if cookie needs SSL.
|
||||
* @access public
|
||||
*/
|
||||
function isSecure() {
|
||||
return $this->_is_secure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a trailing and leading slash to the path
|
||||
* if missing.
|
||||
* @param string $path Path to fix.
|
||||
* @access private
|
||||
*/
|
||||
function _fixPath($path) {
|
||||
if (substr($path, 0, 1) != '/') {
|
||||
$path = '/' . $path;
|
||||
}
|
||||
if (substr($path, -1, 1) != '/') {
|
||||
$path .= '/';
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Repository for cookies. This stuff is a
|
||||
* tiny bit browser dependent.
|
||||
* @package SimpleTest
|
||||
* @subpackage WebTester
|
||||
*/
|
||||
class SimpleCookieJar {
|
||||
var $_cookies;
|
||||
|
||||
/**
|
||||
* Constructor. Jar starts empty.
|
||||
* @access public
|
||||
*/
|
||||
function SimpleCookieJar() {
|
||||
$this->_cookies = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes expired and temporary cookies as if
|
||||
* the browser was closed and re-opened.
|
||||
* @param string/integer $now Time to test expiry against.
|
||||
* @access public
|
||||
*/
|
||||
function restartSession($date = false) {
|
||||
$surviving_cookies = array();
|
||||
for ($i = 0; $i < count($this->_cookies); $i++) {
|
||||
if (! $this->_cookies[$i]->getValue()) {
|
||||
continue;
|
||||
}
|
||||
if (! $this->_cookies[$i]->getExpiry()) {
|
||||
continue;
|
||||
}
|
||||
if ($date && $this->_cookies[$i]->isExpired($date)) {
|
||||
continue;
|
||||
}
|
||||
$surviving_cookies[] = $this->_cookies[$i];
|
||||
}
|
||||
$this->_cookies = $surviving_cookies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ages all cookies in the cookie jar.
|
||||
* @param integer $interval The old session is moved
|
||||
* into the past by this number
|
||||
* of seconds. Cookies now over
|
||||
* age will be removed.
|
||||
* @access public
|
||||
*/
|
||||
function agePrematurely($interval) {
|
||||
for ($i = 0; $i < count($this->_cookies); $i++) {
|
||||
$this->_cookies[$i]->agePrematurely($interval);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an additional cookie. If a cookie has
|
||||
* the same name and path it is replaced.
|
||||
* @param string $name Cookie key.
|
||||
* @param string $value Value of cookie.
|
||||
* @param string $host Host upon which the cookie is valid.
|
||||
* @param string $path Cookie path if not host wide.
|
||||
* @param string $expiry Expiry date.
|
||||
* @access public
|
||||
*/
|
||||
function setCookie($name, $value, $host = false, $path = '/', $expiry = false) {
|
||||
$cookie = new SimpleCookie($name, $value, $path, $expiry);
|
||||
if ($host) {
|
||||
$cookie->setHost($host);
|
||||
}
|
||||
$this->_cookies[$this->_findFirstMatch($cookie)] = $cookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a matching cookie to write over or the
|
||||
* first empty slot if none.
|
||||
* @param SimpleCookie $cookie Cookie to write into jar.
|
||||
* @return integer Available slot.
|
||||
* @access private
|
||||
*/
|
||||
function _findFirstMatch($cookie) {
|
||||
for ($i = 0; $i < count($this->_cookies); $i++) {
|
||||
$is_match = $this->_isMatch(
|
||||
$cookie,
|
||||
$this->_cookies[$i]->getHost(),
|
||||
$this->_cookies[$i]->getPath(),
|
||||
$this->_cookies[$i]->getName());
|
||||
if ($is_match) {
|
||||
return $i;
|
||||
}
|
||||
}
|
||||
return count($this->_cookies);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the most specific cookie value from the
|
||||
* browser cookies. Looks for the longest path that
|
||||
* matches.
|
||||
* @param string $host Host to search.
|
||||
* @param string $path Applicable path.
|
||||
* @param string $name Name of cookie to read.
|
||||
* @return string False if not present, else the
|
||||
* value as a string.
|
||||
* @access public
|
||||
*/
|
||||
function getCookieValue($host, $path, $name) {
|
||||
$longest_path = '';
|
||||
foreach ($this->_cookies as $cookie) {
|
||||
if ($this->_isMatch($cookie, $host, $path, $name)) {
|
||||
if (strlen($cookie->getPath()) > strlen($longest_path)) {
|
||||
$value = $cookie->getValue();
|
||||
$longest_path = $cookie->getPath();
|
||||
}
|
||||
}
|
||||
}
|
||||
return (isset($value) ? $value : false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests cookie for matching against search
|
||||
* criteria.
|
||||
* @param SimpleTest $cookie Cookie to test.
|
||||
* @param string $host Host must match.
|
||||
* @param string $path Cookie path must be shorter than
|
||||
* this path.
|
||||
* @param string $name Name must match.
|
||||
* @return boolean True if matched.
|
||||
* @access private
|
||||
*/
|
||||
function _isMatch($cookie, $host, $path, $name) {
|
||||
if ($cookie->getName() != $name) {
|
||||
return false;
|
||||
}
|
||||
if ($host && $cookie->getHost() && ! $cookie->isValidHost($host)) {
|
||||
return false;
|
||||
}
|
||||
if (! $cookie->isValidPath($path)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses a URL to sift relevant cookies by host and
|
||||
* path. Results are list of strings of form "name=value".
|
||||
* @param SimpleUrl $url Url to select by.
|
||||
* @return array Valid name and value pairs.
|
||||
* @access public
|
||||
*/
|
||||
function selectAsPairs($url) {
|
||||
$pairs = array();
|
||||
foreach ($this->_cookies as $cookie) {
|
||||
if ($this->_isMatch($cookie, $url->getHost(), $url->getPath(), $cookie->getName())) {
|
||||
$pairs[] = $cookie->getName() . '=' . $cookie->getValue();
|
||||
}
|
||||
}
|
||||
return $pairs;
|
||||
}
|
||||
}
|
||||
?>
|
||||
133
simpletest/default_reporter.php
Normal file
133
simpletest/default_reporter.php
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
/**
|
||||
* Optional include file for SimpleTest
|
||||
* @package SimpleTest
|
||||
* @subpackage UnitTester
|
||||
* @version $Id: default_reporter.php 1704 2008-03-25 00:47:04Z lastcraft $
|
||||
*/
|
||||
|
||||
/**#@+
|
||||
* include other SimpleTest class files
|
||||
*/
|
||||
require_once(dirname(__FILE__) . '/simpletest.php');
|
||||
require_once(dirname(__FILE__) . '/scorer.php');
|
||||
require_once(dirname(__FILE__) . '/reporter.php');
|
||||
require_once(dirname(__FILE__) . '/xml.php');
|
||||
/**#@-*/
|
||||
|
||||
/**
|
||||
* Parser for command line arguments. Extracts
|
||||
* the a specific test to run and engages XML
|
||||
* reporting when necessary.
|
||||
* @package SimpleTest
|
||||
* @subpackage UnitTester
|
||||
*/
|
||||
class SimpleCommandLineParser {
|
||||
var $_to_property = array(
|
||||
'case' => '_case', 'c' => '_case',
|
||||
'test' => '_test', 't' => '_test',
|
||||
'xml' => '_xml', 'x' => '_xml');
|
||||
var $_case = '';
|
||||
var $_test = '';
|
||||
var $_xml = false;
|
||||
var $_no_skips = false;
|
||||
|
||||
/**
|
||||
* Parses raw command line arguments into object properties.
|
||||
* @param string $arguments Raw commend line arguments.
|
||||
*/
|
||||
function SimpleCommandLineParser($arguments) {
|
||||
if (! is_array($arguments)) {
|
||||
return;
|
||||
}
|
||||
foreach ($arguments as $i => $argument) {
|
||||
if (preg_match('/^--?(test|case|t|c)=(.+)$/', $argument, $matches)) {
|
||||
$property = $this->_to_property[$matches[1]];
|
||||
$this->$property = $matches[2];
|
||||
} elseif (preg_match('/^--?(test|case|t|c)$/', $argument, $matches)) {
|
||||
$property = $this->_to_property[$matches[1]];
|
||||
if (isset($arguments[$i + 1])) {
|
||||
$this->$property = $arguments[$i + 1];
|
||||
}
|
||||
} elseif (preg_match('/^--?(xml|x)$/', $argument)) {
|
||||
$this->_xml = true;
|
||||
} elseif (preg_match('/^--?(no-skip|no-skips|s)$/', $argument)) {
|
||||
$this->_no_skips = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run only this test.
|
||||
* @return string Test name to run.
|
||||
* @access public
|
||||
*/
|
||||
function getTest() {
|
||||
return $this->_test;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run only this test suite.
|
||||
* @return string Test class name to run.
|
||||
* @access public
|
||||
*/
|
||||
function getTestCase() {
|
||||
return $this->_case;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output should be XML or not.
|
||||
* @return boolean True if XML desired.
|
||||
* @access public
|
||||
*/
|
||||
function isXml() {
|
||||
return $this->_xml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output should suppress skip messages.
|
||||
* @return boolean True for no skips.
|
||||
* @access public
|
||||
*/
|
||||
function noSkips() {
|
||||
return $this->_no_skips;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The default reporter used by SimpleTest's autorun
|
||||
* feature. The actual reporters used are dependency
|
||||
* injected and can be overridden.
|
||||
* @package SimpleTest
|
||||
* @subpackage UnitTester
|
||||
*/
|
||||
class DefaultReporter extends SimpleReporterDecorator {
|
||||
|
||||
/**
|
||||
* Assembles the appopriate reporter for the environment.
|
||||
*/
|
||||
function DefaultReporter() {
|
||||
if (SimpleReporter::inCli()) {
|
||||
global $argv;
|
||||
$parser = new SimpleCommandLineParser($argv);
|
||||
$interfaces = $parser->isXml() ? array('XmlReporter') : array('TextReporter');
|
||||
$reporter = &new SelectiveReporter(
|
||||
SimpleTest::preferred($interfaces),
|
||||
$parser->getTestCase(),
|
||||
$parser->getTest());
|
||||
if ($parser->noSkips()) {
|
||||
$reporter = &new NoSkipsReporter($reporter);
|
||||
}
|
||||
} else {
|
||||
$reporter = &new SelectiveReporter(
|
||||
SimpleTest::preferred('HtmlReporter'),
|
||||
@$_GET['c'],
|
||||
@$_GET['t']);
|
||||
if (@$_GET['skips'] == 'no' || @$_GET['show-skips'] == 'no') {
|
||||
$reporter = &new NoSkipsReporter($reporter);
|
||||
}
|
||||
}
|
||||
$this->SimpleReporterDecorator($reporter);
|
||||
}
|
||||
}
|
||||
?>
|
||||
96
simpletest/detached.php
Normal file
96
simpletest/detached.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
/**
|
||||
* base include file for SimpleTest
|
||||
* @package SimpleTest
|
||||
* @subpackage UnitTester
|
||||
* @version $Id: detached.php 1723 2008-04-08 00:34:10Z lastcraft $
|
||||
*/
|
||||
|
||||
/**#@+
|
||||
* include other SimpleTest class files
|
||||
*/
|
||||
require_once(dirname(__FILE__) . '/xml.php');
|
||||
require_once(dirname(__FILE__) . '/shell_tester.php');
|
||||
/**#@-*/
|
||||
|
||||
/**
|
||||
* Runs an XML formated test in a separate process.
|
||||
* @package SimpleTest
|
||||
* @subpackage UnitTester
|
||||
*/
|
||||
class DetachedTestCase {
|
||||
var $_command;
|
||||
var $_dry_command;
|
||||
var $_size;
|
||||
|
||||
/**
|
||||
* Sets the location of the remote test.
|
||||
* @param string $command Test script.
|
||||
* @param string $dry_command Script for dry run.
|
||||
* @access public
|
||||
*/
|
||||
function DetachedTestCase($command, $dry_command = false) {
|
||||
$this->_command = $command;
|
||||
$this->_dry_command = $dry_command ? $dry_command : $command;
|
||||
$this->_size = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor for the test name for subclasses.
|
||||
* @return string Name of the test.
|
||||
* @access public
|
||||
*/
|
||||
function getLabel() {
|
||||
return $this->_command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the top level test for this class. Currently
|
||||
* reads the data as a single chunk. I'll fix this
|
||||
* once I have added iteration to the browser.
|
||||
* @param SimpleReporter $reporter Target of test results.
|
||||
* @returns boolean True if no failures.
|
||||
* @access public
|
||||
*/
|
||||
function run(&$reporter) {
|
||||
$shell = &new SimpleShell();
|
||||
$shell->execute($this->_command);
|
||||
$parser = &$this->_createParser($reporter);
|
||||
if (! $parser->parse($shell->getOutput())) {
|
||||
trigger_error('Cannot parse incoming XML from [' . $this->_command . ']');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor for the number of subtests.
|
||||
* @return integer Number of test cases.
|
||||
* @access public
|
||||
*/
|
||||
function getSize() {
|
||||
if ($this->_size === false) {
|
||||
$shell = &new SimpleShell();
|
||||
$shell->execute($this->_dry_command);
|
||||
$reporter = &new SimpleReporter();
|
||||
$parser = &$this->_createParser($reporter);
|
||||
if (! $parser->parse($shell->getOutput())) {
|
||||
trigger_error('Cannot parse incoming XML from [' . $this->_dry_command . ']');
|
||||
return false;
|
||||
}
|
||||
$this->_size = $reporter->getTestCaseCount();
|
||||
}
|
||||
return $this->_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the XML parser.
|
||||
* @param SimpleReporter $reporter Target of test results.
|
||||
* @return SimpleTestXmlListener XML reader.
|
||||
* @access protected
|
||||
*/
|
||||
function &_createParser(&$reporter) {
|
||||
return new SimpleTestXmlParser($reporter);
|
||||
}
|
||||
}
|
||||
?>
|
||||
355
simpletest/docs/en/authentication_documentation.html
Normal file
355
simpletest/docs/en/authentication_documentation.html
Normal file
@@ -0,0 +1,355 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>SimpleTest documentation for testing log-in and authentication</title>
|
||||
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
|
||||
</head>
|
||||
<body>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<span class="chosen">Authentication</span>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<h1>Authentication documentation</h1>
|
||||
This page...
|
||||
<ul>
|
||||
<li>
|
||||
Getting through <a href="#basic">Basic HTTP authentication</a>
|
||||
</li>
|
||||
<li>
|
||||
Testing <a href="#cookies">cookie based authentication</a>
|
||||
</li>
|
||||
<li>
|
||||
Managing <a href="#session">browser sessions</a> and timeouts
|
||||
</li>
|
||||
</ul>
|
||||
<div class="content">
|
||||
|
||||
<p>
|
||||
One of the trickiest, and yet most important, areas
|
||||
of testing web sites is the security.
|
||||
Testing these schemes is one of the core goals of
|
||||
the SimpleTest web tester.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="basic"><h2>Basic HTTP authentication</h2></a></p>
|
||||
<p>
|
||||
If you fetch a page protected by basic authentication then
|
||||
rather than receiving content, you will instead get a 401
|
||||
header.
|
||||
We can illustrate this with this test...
|
||||
<pre>
|
||||
class AuthenticationTest extends WebTestCase {<strong>
|
||||
function test401Header() {
|
||||
$this->get('http://www.lastcraft.com/protected/');
|
||||
$this->showHeaders();
|
||||
}</strong>
|
||||
}
|
||||
</pre>
|
||||
This allows us to see the challenge header...
|
||||
<div class="demo">
|
||||
<h1>File test</h1>
|
||||
<pre style="background-color: lightgray; color: black">
|
||||
HTTP/1.1 401 Authorization Required
|
||||
Date: Sat, 18 Sep 2004 19:25:18 GMT
|
||||
Server: Apache/1.3.29 (Unix) PHP/4.3.4
|
||||
WWW-Authenticate: Basic realm="SimpleTest basic authentication"
|
||||
Connection: close
|
||||
Content-Type: text/html; charset=iso-8859-1
|
||||
</pre>
|
||||
<div style="padding: 8px; margin-top: 1em; background-color: green; color: white;">1/1 test cases complete.
|
||||
<strong>0</strong> passes, <strong>0</strong> fails and <strong>0</strong> exceptions.</div>
|
||||
</div>
|
||||
We are trying to get away from visual inspection though, and so SimpleTest
|
||||
allows to make automated assertions against the challenge.
|
||||
Here is a thorough test of our header...
|
||||
<pre>
|
||||
class AuthenticationTest extends WebTestCase {
|
||||
function test401Header() {
|
||||
$this->get('http://www.lastcraft.com/protected/');<strong>
|
||||
$this->assertAuthentication('Basic');
|
||||
$this->assertResponse(401);
|
||||
$this->assertRealm('SimpleTest basic authentication');</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
Any one of these tests would normally do on it's own depending
|
||||
on the amount of detail you want to see.
|
||||
</p>
|
||||
<p>
|
||||
One theme that runs through SimpleTest is the ability to use
|
||||
<span class="new_code">SimpleExpectation</span> objects wherever a simple
|
||||
match is not enough.
|
||||
If you want only an approximate match to the realm for
|
||||
example, you can do this...
|
||||
<pre>
|
||||
class AuthenticationTest extends WebTestCase {
|
||||
function test401Header() {
|
||||
$this->get('http://www.lastcraft.com/protected/');
|
||||
$this->assertRealm(<strong>new PatternExpectation('/simpletest/i')</strong>);
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
Most of the time we are not interested in testing the
|
||||
authentication itself, but want to get past it to test
|
||||
the pages underneath.
|
||||
As soon as the challenge has been issued we can reply with
|
||||
an authentication response...
|
||||
<pre>
|
||||
class AuthenticationTest extends WebTestCase {
|
||||
function testCanAuthenticate() {
|
||||
$this->get('http://www.lastcraft.com/protected/');<strong>
|
||||
$this->authenticate('Me', 'Secret');</strong>
|
||||
$this->assertTitle(...);
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
The username and password will now be sent with every
|
||||
subsequent request to that directory and subdirectories.
|
||||
You will have to authenticate again if you step outside
|
||||
the authenticated directory, but SimpleTest is smart enough
|
||||
to merge subdirectories into a common realm.
|
||||
</p>
|
||||
<p>
|
||||
You can shortcut this step further by encoding the log in
|
||||
details straight into the URL...
|
||||
<pre>
|
||||
class AuthenticationTest extends WebTestCase {
|
||||
function testCanReadAuthenticatedPages() {
|
||||
$this->get('http://<strong>Me:Secret@</strong>www.lastcraft.com/protected/');
|
||||
$this->assertTitle(...);
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
If your username or password has special characters, then you
|
||||
will have to URL encode them or the request will not be parsed
|
||||
correctly.
|
||||
Also this header will not be sent on subsequent requests if
|
||||
you request a page with a fully qualified URL.
|
||||
If you navigate with relative URLs though, the authentication
|
||||
information will be preserved.
|
||||
</p>
|
||||
<p>
|
||||
Only basic authentication is currently supported and this is
|
||||
only really secure in tandem with HTTPS connections.
|
||||
This is usually enough to protect test server from prying eyes,
|
||||
however.
|
||||
Digest authentication and NTLM authentication may be added
|
||||
in the future.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="cookies"><h2>Cookies</h2></a></p>
|
||||
<p>
|
||||
Basic authentication doesn't give enough control over the
|
||||
user interface for web developers.
|
||||
More likely this functionality will be coded directly into
|
||||
the web architecture using cookies and complicated timeouts.
|
||||
</p>
|
||||
<p>
|
||||
Starting with a simple log-in form...
|
||||
<pre>
|
||||
<form>
|
||||
Username:
|
||||
<input type="text" name="u" value="" /><br />
|
||||
Password:
|
||||
<input type="password" name="p" value="" /><br />
|
||||
<input type="submit" value="Log in" />
|
||||
</form>
|
||||
</pre>
|
||||
Which looks like...
|
||||
</p>
|
||||
<p>
|
||||
<form class="demo">
|
||||
Username:
|
||||
<input type="text" name="u" value=""><br>
|
||||
Password:
|
||||
<input type="password" name="p" value=""><br>
|
||||
<input type="submit" value="Log in">
|
||||
</form>
|
||||
</p>
|
||||
<p>
|
||||
Let's suppose that in fetching this page a cookie has been
|
||||
set with a session ID.
|
||||
We are not going to fill the form in yet, just test that
|
||||
we are tracking the user.
|
||||
Here is the test...
|
||||
<pre>
|
||||
class LogInTest extends WebTestCase {
|
||||
function testSessionCookieSetBeforeForm() {
|
||||
$this->get('http://www.my-site.com/login.php');<strong>
|
||||
$this->assertCookie('SID');</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
All we are doing is confirming that the cookie is set.
|
||||
As the value is likely to be rather cryptic it's not
|
||||
really worth testing this with...
|
||||
<pre>
|
||||
class LogInTest extends WebTestCase {
|
||||
function testSessionCookieIsCorrectPattern() {
|
||||
$this->get('http://www.my-site.com/login.php');
|
||||
$this->assertCookie('SID', <strong>new PatternExpectation('/[a-f0-9]{32}/i')</strong>);
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
The rest of the test would be the same as any other form,
|
||||
but we might want to confirm that we still have the same
|
||||
cookie after log-in as before we entered.
|
||||
We wouldn't want to lose track of this after all.
|
||||
Here is a possible test for this...
|
||||
<pre>
|
||||
class LogInTest extends WebTestCase {
|
||||
...
|
||||
function testSessionCookieSameAfterLogIn() {
|
||||
$this->get('http://www.my-site.com/login.php');<strong>
|
||||
$session = $this->getCookie('SID');
|
||||
$this->setField('u', 'Me');
|
||||
$this->setField('p', 'Secret');
|
||||
$this->click('Log in');
|
||||
$this->assertText('Welcome Me');
|
||||
$this->assertCookie('SID', $session);</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
This confirms that the session identifier is maintained
|
||||
afer log-in.
|
||||
</p>
|
||||
<p>
|
||||
We could even attempt to spoof our own system by setting
|
||||
arbitrary cookies to gain access...
|
||||
<pre>
|
||||
class LogInTest extends WebTestCase {
|
||||
...
|
||||
function testSessionCookieSameAfterLogIn() {
|
||||
$this->get('http://www.my-site.com/login.php');<strong>
|
||||
$this->setCookie('SID', 'Some other session');
|
||||
$this->get('http://www.my-site.com/restricted.php');</strong>
|
||||
$this->assertText('Access denied');
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
Is your site protected from this attack?
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="session"><h2>Browser sessions</h2></a></p>
|
||||
<p>
|
||||
If you are testing an authentication system a critical piece
|
||||
of behaviour is what happens when a user logs back in.
|
||||
We would like to simulate closing and reopening a browser...
|
||||
<pre>
|
||||
class LogInTest extends WebTestCase {
|
||||
...
|
||||
function testLoseAuthenticationAfterBrowserClose() {
|
||||
$this->get('http://www.my-site.com/login.php');
|
||||
$this->setField('u', 'Me');
|
||||
$this->setField('p', 'Secret');
|
||||
$this->click('Log in');
|
||||
$this->assertText('Welcome Me');<strong>
|
||||
|
||||
$this->restart();
|
||||
$this->get('http://www.my-site.com/restricted.php');
|
||||
$this->assertText('Access denied');</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
The <span class="new_code">WebTestCase::restart()</span> method will
|
||||
preserve cookies that have unexpired timeouts, but throw away
|
||||
those that are temporary or expired.
|
||||
You can optionally specify the time and date that the restart
|
||||
happened.
|
||||
</p>
|
||||
<p>
|
||||
Expiring cookies can be a problem.
|
||||
After all, if you have a cookie that expires after an hour,
|
||||
you don't want to stall the test for an hour while the
|
||||
cookie passes it's timeout.
|
||||
</p>
|
||||
<p>
|
||||
To push the cookies over the hour limit you can age them
|
||||
before you restart the session...
|
||||
<pre>
|
||||
class LogInTest extends WebTestCase {
|
||||
...
|
||||
function testLoseAuthenticationAfterOneHour() {
|
||||
$this->get('http://www.my-site.com/login.php');
|
||||
$this->setField('u', 'Me');
|
||||
$this->setField('p', 'Secret');
|
||||
$this->click('Log in');
|
||||
$this->assertText('Welcome Me');
|
||||
<strong>
|
||||
$this->ageCookies(3600);</strong>
|
||||
$this->restart();
|
||||
$this->get('http://www.my-site.com/restricted.php');
|
||||
$this->assertText('Access denied');
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
After the restart it will appear that cookies are an
|
||||
hour older and any that pass their expiry will have
|
||||
disappeared.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
References and related information...
|
||||
<ul>
|
||||
<li>
|
||||
SimpleTest project page on <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
|
||||
</li>
|
||||
<li>
|
||||
SimpleTest download page on <a href="http://www.lastcraft.com/simple_test.php">LastCraft</a>.
|
||||
</li>
|
||||
<li>
|
||||
The <a href="http://simpletest.org/api/">developer's API for SimpleTest</a>
|
||||
gives full detail on the classes and assertions available.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<span class="chosen">Authentication</span>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<div class="copyright">
|
||||
Copyright<br>Marcus Baker 2006
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
447
simpletest/docs/en/browser_documentation.html
Normal file
447
simpletest/docs/en/browser_documentation.html
Normal file
@@ -0,0 +1,447 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>SimpleTest documentation for the scriptable web browser component</title>
|
||||
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
|
||||
</head>
|
||||
<body>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<span class="chosen">Scriptable browser</span>
|
||||
</div></div>
|
||||
<h1>PHP Scriptable Web Browser</h1>
|
||||
This page...
|
||||
<ul>
|
||||
<li>
|
||||
Using the bundled <a href="#scripting">web browser in scripts</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#debug">Debugging</a> failed pages
|
||||
</li>
|
||||
<li>
|
||||
Complex <a href="#unit">tests with multiple web browsers</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="content">
|
||||
|
||||
<p>
|
||||
SimpleTest's web browser component can be used not just
|
||||
outside of the <span class="new_code">WebTestCase</span> class, but also
|
||||
independently of the SimpleTest framework itself.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="scripting"><h2>The Scriptable Browser</h2></a></p>
|
||||
<p>
|
||||
You can use the web browser in PHP scripts to confirm
|
||||
services are up and running, or to extract information
|
||||
from them at a regular basis.
|
||||
For example, here is a small script to extract the current number of
|
||||
open PHP 5 bugs from the <a href="http://www.php.net/">PHP web site</a>...
|
||||
<pre>
|
||||
<strong><?php
|
||||
require_once('simpletest/browser.php');
|
||||
|
||||
$browser = &new SimpleBrowser();
|
||||
$browser->get('http://php.net/');
|
||||
$browser->click('reporting bugs');
|
||||
$browser->click('statistics');
|
||||
$page = $browser->click('PHP 5 bugs only');
|
||||
preg_match('/status=Open.*?by=Any.*?(\d+)<\/a>/', $page, $matches);
|
||||
print $matches[1];
|
||||
?></strong>
|
||||
</pre>
|
||||
There are simpler methods to do this particular example in PHP
|
||||
of course.
|
||||
For example you can just use the PHP <span class="new_code">file()</span>
|
||||
command against what here is a pretty fixed page.
|
||||
However, using the web browser for scripts allows authentication,
|
||||
correct handling of cookies, automatic loading of frames, redirects,
|
||||
form submission and the ability to examine the page headers.
|
||||
Such methods are fragile against a site that is constantly
|
||||
evolving and you would want a more direct way of accessing
|
||||
data in a permanent set up, but for simple tasks this can provide
|
||||
a very rapid solution.
|
||||
</p>
|
||||
<p>
|
||||
All of the navigation methods used in the
|
||||
<a href="web_tester_documentation.html">WebTestCase</a>
|
||||
are present in the <span class="new_code">SimpleBrowser</span> class, but
|
||||
the assertions are replaced with simpler accessors.
|
||||
Here is a full list of the page navigation methods...
|
||||
<table><tbody>
|
||||
<tr>
|
||||
<td><span class="new_code">addHeader($header)</span></td>
|
||||
<td>Adds a header to every fetch</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">useProxy($proxy, $username, $password)</span></td>
|
||||
<td>Use this proxy from now on</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">head($url, $parameters)</span></td>
|
||||
<td>Perform a HEAD request</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">get($url, $parameters)</span></td>
|
||||
<td>Fetch a page with GET</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">post($url, $parameters)</span></td>
|
||||
<td>Fetch a page with POST</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">clickLink($label)</span></td>
|
||||
<td>Follows a link by label</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">clickLinkById($id)</span></td>
|
||||
<td>Follows a link by attribute</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getUrl()</span></td>
|
||||
<td>Current URL of page or frame</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getTitle()</span></td>
|
||||
<td>Page title</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getContent()</span></td>
|
||||
<td>Raw page or frame</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getContentAsText()</span></td>
|
||||
<td>HTML removed except for alt text</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">retry()</span></td>
|
||||
<td>Repeat the last request</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">back()</span></td>
|
||||
<td>Use the browser back button</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">forward()</span></td>
|
||||
<td>Use the browser forward button</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">authenticate($username, $password)</span></td>
|
||||
<td>Retry page or frame after a 401 response</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">restart($date)</span></td>
|
||||
<td>Restarts the browser for a new session</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">ageCookies($interval)</span></td>
|
||||
<td>Ages the cookies by the specified time</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">setCookie($name, $value)</span></td>
|
||||
<td>Sets an additional cookie</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getCookieValue($host, $path, $name)</span></td>
|
||||
<td>Reads the most specific cookie</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getCurrentCookieValue($name)</span></td>
|
||||
<td>Reads cookie for the current context</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
The methods <span class="new_code">SimpleBrowser::useProxy()</span> and
|
||||
<span class="new_code">SimpleBrowser::addHeader()</span> are special.
|
||||
Once called they continue to apply to all subsequent fetches.
|
||||
</p>
|
||||
<p>
|
||||
Navigating forms is similar to the
|
||||
<a href="form_testing_documentation.html">WebTestCase form navigation</a>...
|
||||
<table><tbody>
|
||||
<tr>
|
||||
<td><span class="new_code">setField($name, $value)</span></td>
|
||||
<td>Sets all form fields with that name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">setFieldById($id, $value)</span></td>
|
||||
<td>Sets all form fields with that id</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getField($name)</span></td>
|
||||
<td>Accessor for a form element value</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getFieldById($id)</span></td>
|
||||
<td>Accessor for a form element value</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">clickSubmit($label)</span></td>
|
||||
<td>Submits form by button label</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">clickSubmitByName($name)</span></td>
|
||||
<td>Submits form by button attribute</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">clickSubmitById($id)</span></td>
|
||||
<td>Submits form by button attribute</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">clickImage($label, $x, $y)</span></td>
|
||||
<td>Clicks an input tag of type image by title or alt text</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">clickImageByName($name, $x, $y)</span></td>
|
||||
<td>Clicks an input tag of type image by name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">clickImageById($id, $x, $y)</span></td>
|
||||
<td>Clicks an input tag of type image by ID attribute</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">submitFormById($id)</span></td>
|
||||
<td>Submits by the form tag attribute</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
At the moment there aren't any methods to list available forms
|
||||
and fields.
|
||||
This will probably be added to later versions of SimpleTest.
|
||||
</p>
|
||||
<p>
|
||||
Within a page, individual frames can be selected.
|
||||
If no selection is made then all the frames are merged together
|
||||
in one large conceptual page.
|
||||
The content of the current page will be a concatenation of all of the
|
||||
frames in the order that they were specified in the "frameset"
|
||||
tags.
|
||||
<table><tbody>
|
||||
<tr>
|
||||
<td><span class="new_code">getFrames()</span></td>
|
||||
<td>A dump of the current frame structure</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getFrameFocus()</span></td>
|
||||
<td>Current frame label or index</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">setFrameFocusByIndex($choice)</span></td>
|
||||
<td>Select a frame numbered from 1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">setFrameFocus($name)</span></td>
|
||||
<td>Select frame by label</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">clearFrameFocus()</span></td>
|
||||
<td>Treat all the frames as a single page</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
When focused on a single frame, the content will come from
|
||||
that frame only.
|
||||
This includes links to click and forms to submit.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="debug"><h2>What went wrong?</h2></a></p>
|
||||
<p>
|
||||
All of this functionality is great when we actually manage to fetch pages,
|
||||
but that doesn't always happen.
|
||||
To help figure out what went wrong, the browser has some methods to
|
||||
aid in debugging...
|
||||
<table><tbody>
|
||||
<tr>
|
||||
<td><span class="new_code">setConnectionTimeout($timeout)</span></td>
|
||||
<td>Close the socket on overrun</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getRequest()</span></td>
|
||||
<td>Raw request header of page or frame</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getHeaders()</span></td>
|
||||
<td>Raw response header of page or frame</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getTransportError()</span></td>
|
||||
<td>Any socket level errors in the last fetch</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getResponseCode()</span></td>
|
||||
<td>HTTP response of page or frame</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getMimeType()</span></td>
|
||||
<td>Mime type of page or frame</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getAuthentication()</span></td>
|
||||
<td>Authentication type in 401 challenge header</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getRealm()</span></td>
|
||||
<td>Authentication realm in 401 challenge header</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">setMaximumRedirects($max)</span></td>
|
||||
<td>Number of redirects before page is loaded anyway</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">setMaximumNestedFrames($max)</span></td>
|
||||
<td>Protection against recursive framesets</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">ignoreFrames()</span></td>
|
||||
<td>Disables frames support</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">useFrames()</span></td>
|
||||
<td>Enables frames support</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">ignoreCookies()</span></td>
|
||||
<td>Disables sending and receiving of cookies</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">useCookies()</span></td>
|
||||
<td>Enables cookie support</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
The methods <span class="new_code">SimpleBrowser::setConnectionTimeout()</span>
|
||||
<span class="new_code">SimpleBrowser::setMaximumRedirects()</span>,
|
||||
<span class="new_code">SimpleBrowser::setMaximumNestedFrames()</span>,
|
||||
<span class="new_code">SimpleBrowser::ignoreFrames()</span>,
|
||||
<span class="new_code">SimpleBrowser::useFrames()</span>,
|
||||
<span class="new_code">SimpleBrowser::ignoreCookies()</span> and
|
||||
<span class="new_code">SimpleBrowser::useCokies()</span> continue to apply
|
||||
to every subsequent request.
|
||||
The other methods are frames aware.
|
||||
This means that if you have an individual frame that is not
|
||||
loading, navigate to it using <span class="new_code">SimpleBrowser::setFrameFocus()</span>
|
||||
and you can then use <span class="new_code">SimpleBrowser::getRequest()</span>, etc to
|
||||
see what happened.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="unit"><h2>Complex unit tests with multiple browsers</h2></a></p>
|
||||
<p>
|
||||
Anything that could be done in a
|
||||
<a href="web_tester_documentation.html">WebTestCase</a> can
|
||||
now be done in a <a href="unit_tester_documentation.html">UnitTestCase</a>.
|
||||
This means that we can freely mix domain object testing with the
|
||||
web interface...
|
||||
<pre>
|
||||
<strong>class TestOfRegistration extends UnitTestCase {
|
||||
function testNewUserAddedToAuthenticator() {</strong>
|
||||
$browser = &new SimpleBrowser();
|
||||
$browser->get('http://my-site.com/register.php');
|
||||
$browser->setField('email', 'me@here');
|
||||
$browser->setField('password', 'Secret');
|
||||
$browser->click('Register');
|
||||
<strong>
|
||||
$authenticator = &new Authenticator();
|
||||
$member = &$authenticator->findByEmail('me@here');
|
||||
$this->assertEqual($member->getPassword(), 'Secret');
|
||||
}
|
||||
}</strong>
|
||||
</pre>
|
||||
While this may be a useful temporary expediency, I am not a fan
|
||||
of this type of testing.
|
||||
The testing has cut across application layers, make it twice as
|
||||
likely it will need refactoring when the code changes.
|
||||
</p>
|
||||
<p>
|
||||
A more useful case of where using the browser directly can be helpful
|
||||
is where the <span class="new_code">WebTestCase</span> cannot cope.
|
||||
An example is where two browsers are needed at the same time.
|
||||
</p>
|
||||
<p>
|
||||
For example, say we want to disallow multiple simultaneous
|
||||
usage of a site with the same username.
|
||||
This test case will do the job...
|
||||
<pre>
|
||||
class TestOfSecurity extends UnitTestCase {
|
||||
function testNoMultipleLoginsFromSameUser() {<strong>
|
||||
$first = &new SimpleBrowser();
|
||||
$first->get('http://my-site.com/login.php');
|
||||
$first->setField('name', 'Me');
|
||||
$first->setField('password', 'Secret');
|
||||
$first->click('Enter');
|
||||
$this->assertEqual($first->getTitle(), 'Welcome');
|
||||
|
||||
$second = &new SimpleBrowser();
|
||||
$second->get('http://my-site.com/login.php');
|
||||
$second->setField('name', 'Me');
|
||||
$second->setField('password', 'Secret');
|
||||
$second->click('Enter');
|
||||
$this->assertEqual($second->getTitle(), 'Access Denied');</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
You can also use the <span class="new_code">SimpleBrowser</span> class
|
||||
directly when you want to write test cases using a different
|
||||
test tool than SimpleTest.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
References and related information...
|
||||
<ul>
|
||||
<li>
|
||||
SimpleTest project page on <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
|
||||
</li>
|
||||
<li>
|
||||
SimpleTest download page on <a href="http://www.lastcraft.com/simple_test.php">LastCraft</a>.
|
||||
</li>
|
||||
<li>
|
||||
The <a href="http://simpletest.org/api/">developer's API for SimpleTest</a>
|
||||
gives full detail on the classes and assertions available.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<span class="chosen">Scriptable browser</span>
|
||||
</div></div>
|
||||
<div class="copyright">
|
||||
Copyright<br>Marcus Baker 2006
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
121
simpletest/docs/en/docs.css
Normal file
121
simpletest/docs/en/docs.css
Normal file
@@ -0,0 +1,121 @@
|
||||
body {
|
||||
padding-left: 3%;
|
||||
padding-right: 3%;
|
||||
}
|
||||
h1, h2, h3 {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
pre {
|
||||
font-family: "courier new", courier, typewriter, monospace;
|
||||
font-size: 90%;
|
||||
border: 1px solid;
|
||||
border-color: #999966;
|
||||
background-color: #ffffcc;
|
||||
padding: 5px;
|
||||
margin-left: 20px;
|
||||
margin-right: 40px;
|
||||
}
|
||||
.code, .new_code, pre.new_code {
|
||||
font-family: "courier new", courier, typewriter, monospace;
|
||||
font-weight: bold;
|
||||
}
|
||||
div.copyright {
|
||||
font-size: 80%;
|
||||
color: gray;
|
||||
}
|
||||
div.copyright a {
|
||||
margin-top: 1em;
|
||||
color: gray;
|
||||
}
|
||||
ul.api {
|
||||
border: 2px outset;
|
||||
border-color: gray;
|
||||
background-color: white;
|
||||
margin: 5px;
|
||||
margin-left: 5%;
|
||||
margin-right: 5%;
|
||||
}
|
||||
ul.api li {
|
||||
margin-top: 0.2em;
|
||||
margin-bottom: 0.2em;
|
||||
list-style: none;
|
||||
text-indent: -3em;
|
||||
padding-left: 1em;
|
||||
}
|
||||
div.demo {
|
||||
border: 4px ridge;
|
||||
border-color: gray;
|
||||
padding: 10px;
|
||||
margin: 5px;
|
||||
margin-left: 20px;
|
||||
margin-right: 40px;
|
||||
background-color: white;
|
||||
}
|
||||
div.demo span.fail {
|
||||
color: red;
|
||||
}
|
||||
div.demo span.pass {
|
||||
color: green;
|
||||
}
|
||||
div.demo h1 {
|
||||
font-size: 12pt;
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
}
|
||||
div.menu {
|
||||
text-align: center;
|
||||
}
|
||||
table {
|
||||
border: 2px outset;
|
||||
border-color: gray;
|
||||
background-color: white;
|
||||
margin: 5px;
|
||||
margin-left: 5%;
|
||||
margin-right: 5%;
|
||||
}
|
||||
td {
|
||||
font-size: 90%;
|
||||
}
|
||||
.shell {
|
||||
color: white;
|
||||
}
|
||||
pre.shell {
|
||||
border: 4px ridge;
|
||||
border-color: gray;
|
||||
padding: 10px;
|
||||
margin: 5px;
|
||||
margin-left: 20px;
|
||||
margin-right: 40px;
|
||||
background-color: #000100;
|
||||
color: #99ff99;
|
||||
font-size: 90%;
|
||||
}
|
||||
pre.file {
|
||||
color: black;
|
||||
border: 1px solid;
|
||||
border-color: black;
|
||||
padding: 10px;
|
||||
margin: 5px;
|
||||
margin-left: 20px;
|
||||
margin-right: 40px;
|
||||
background-color: white;
|
||||
font-size: 90%;
|
||||
}
|
||||
form.demo {
|
||||
background-color: lightgray;
|
||||
border: 4px outset;
|
||||
border-color: lightgray;
|
||||
padding: 10px;
|
||||
margin-right: 40%;
|
||||
}
|
||||
dl, dd {
|
||||
margin: 10px;
|
||||
margin-left: 30px;
|
||||
}
|
||||
em {
|
||||
font-weight: bold;
|
||||
font-family: "courier new", courier, typewriter, monospace;
|
||||
}
|
||||
422
simpletest/docs/en/expectation_documentation.html
Normal file
422
simpletest/docs/en/expectation_documentation.html
Normal file
@@ -0,0 +1,422 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>
|
||||
Extending the SimpleTest unit tester with additional expectation classes
|
||||
</title>
|
||||
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
|
||||
</head>
|
||||
<body>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<span class="chosen">Expectations</span>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<h1>Expectation documentation</h1>
|
||||
This page...
|
||||
<ul>
|
||||
<li>
|
||||
Using expectations for
|
||||
<a href="#mock">more precise testing with mock objects</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#behaviour">Changing mock object behaviour</a> with expectations
|
||||
</li>
|
||||
<li>
|
||||
<a href="#extending">Extending the expectations</a>
|
||||
</li>
|
||||
<li>
|
||||
Underneath SimpleTest <a href="#unit">uses expectation classes</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="content">
|
||||
<p><a class="target" name="mock"><h2>More control over mock objects</h2></a></p>
|
||||
<p>
|
||||
The default behaviour of the
|
||||
<a href="mock_objects_documentation.html">mock objects</a>
|
||||
in
|
||||
<a href="http://sourceforge.net/projects/simpletest/">SimpleTest</a>
|
||||
is either an identical match on the argument or to allow any argument at all.
|
||||
For almost all tests this is sufficient.
|
||||
Sometimes, though, you want to weaken a test case.
|
||||
</p>
|
||||
<p>
|
||||
One place where a test can be too tightly coupled is with
|
||||
text matching.
|
||||
Suppose we have a component that outputs a helpful error
|
||||
message when something goes wrong.
|
||||
You want to test that the correct error was sent, but the actual
|
||||
text may be rather long.
|
||||
If you test for the text exactly, then every time the exact wording
|
||||
of the message changes, you will have to go back and edit the test suite.
|
||||
</p>
|
||||
<p>
|
||||
For example, suppose we have a news service that has failed
|
||||
to connect to its remote source.
|
||||
<pre>
|
||||
<strong>class NewsService {
|
||||
...
|
||||
function publish(&$writer) {
|
||||
if (! $this->isConnected()) {
|
||||
$writer->write('Cannot connect to news service "' .
|
||||
$this->_name . '" at this time. ' .
|
||||
'Please try again later.');
|
||||
}
|
||||
...
|
||||
}
|
||||
}</strong>
|
||||
</pre>
|
||||
Here it is sending its content to a
|
||||
<span class="new_code">Writer</span> class.
|
||||
We could test this behaviour with a
|
||||
<span class="new_code">MockWriter</span> like so...
|
||||
<pre>
|
||||
class TestOfNewsService extends UnitTestCase {
|
||||
...
|
||||
function testConnectionFailure() {<strong>
|
||||
$writer = &new MockWriter();
|
||||
$writer->expectOnce('write', array(
|
||||
'Cannot connect to news service ' .
|
||||
'"BBC News" at this time. ' .
|
||||
'Please try again later.'));
|
||||
|
||||
$service = &new NewsService('BBC News');
|
||||
$service->publish($writer);</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
This is a good example of a brittle test.
|
||||
If we decide to add additional instructions, such as
|
||||
suggesting an alternative news source, we will break
|
||||
our tests even though no underlying functionality
|
||||
has been altered.
|
||||
</p>
|
||||
<p>
|
||||
To get around this, we would like to do a regular expression
|
||||
test rather than an exact match.
|
||||
We can actually do this with...
|
||||
<pre>
|
||||
class TestOfNewsService extends UnitTestCase {
|
||||
...
|
||||
function testConnectionFailure() {
|
||||
$writer = &new MockWriter();<strong>
|
||||
$writer->expectOnce(
|
||||
'write',
|
||||
array(new PatternExpectation('/cannot connect/i')));</strong>
|
||||
|
||||
$service = &new NewsService('BBC News');
|
||||
$service->publish($writer);
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
Instead of passing in the expected parameter to the
|
||||
<span class="new_code">MockWriter</span> we pass an
|
||||
expectation class called
|
||||
<span class="new_code">WantedPatternExpectation</span>.
|
||||
The mock object is smart enough to recognise this as special
|
||||
and to treat it differently.
|
||||
Rather than simply comparing the incoming argument to this
|
||||
object, it uses the expectation object itself to
|
||||
perform the test.
|
||||
</p>
|
||||
<p>
|
||||
The <span class="new_code">WantedPatternExpectation</span> takes
|
||||
the regular expression to match in its constructor.
|
||||
Whenever a comparison is made by the <span class="new_code">MockWriter</span>
|
||||
against this expectation class, it will do a
|
||||
<span class="new_code">preg_match()</span> with this pattern.
|
||||
With our test case above, as long as "cannot connect"
|
||||
appears in the text of the string, the mock will issue a pass
|
||||
to the unit tester.
|
||||
The rest of the text does not matter.
|
||||
</p>
|
||||
<p>
|
||||
The possible expectation classes are...
|
||||
<table><tbody>
|
||||
<tr>
|
||||
<td><span class="new_code">AnythingExpectation</span></td>
|
||||
<td>Will always match</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">EqualExpectation</span></td>
|
||||
<td>An equality, rather than the stronger identity comparison</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">NotEqualExpectation</span></td>
|
||||
<td>An inequality comparison</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">IndenticalExpectation</span></td>
|
||||
<td>The default mock object check which must match exactly</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">NotIndenticalExpectation</span></td>
|
||||
<td>Inverts the mock object logic</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">WithinMarginExpectation</span></td>
|
||||
<td>Compares a value to within a margin</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">OutsideMarginExpectation</span></td>
|
||||
<td>Checks that a value is out side the margin</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">PatternExpectation</span></td>
|
||||
<td>Uses a Perl Regex to match a string</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">NoPatternExpectation</span></td>
|
||||
<td>Passes only if failing a Perl Regex</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">IsAExpectation</span></td>
|
||||
<td>Checks the type or class name only</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">NotAExpectation</span></td>
|
||||
<td>Opposite of the <span class="new_code">IsAExpectation</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">MethodExistsExpectation</span></td>
|
||||
<td>Checks a method is available on an object</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
Most take the expected value in the constructor.
|
||||
The exceptions are the pattern matchers, which take a regular expression,
|
||||
and the <span class="new_code">IsAExpectation</span> and <span class="new_code">NotAExpectation</span> which takes a type
|
||||
or class name as a string.
|
||||
</p>
|
||||
<p>
|
||||
Some examples...
|
||||
</p>
|
||||
<p>
|
||||
<pre>
|
||||
$mock->expectOnce('method', array(new IdenticalExpectation(14)));
|
||||
</pre>
|
||||
This is the same as <span class="new_code">$mock->expectOnce('method', array(14))</span>.
|
||||
<pre>
|
||||
$mock->expectOnce('method', array(new EqualExpectation(14)));
|
||||
</pre>
|
||||
This is different from the previous version in that the string
|
||||
<span class="new_code">"14"</span> as a parameter will also pass.
|
||||
Sometimes the additional type checks of SimpleTest are too restrictive.
|
||||
<pre>
|
||||
$mock->expectOnce('method', array(new AnythingExpectation(14)));
|
||||
</pre>
|
||||
This is the same as <span class="new_code">$mock->expectOnce('method', array('*'))</span>.
|
||||
<pre>
|
||||
$mock->expectOnce('method', array(new IdenticalExpectation('*')));
|
||||
</pre>
|
||||
This is handy if you want to assert a literal <span class="new_code">"*"</span>.
|
||||
<pre>
|
||||
new NotIdenticalExpectation(14)
|
||||
</pre>
|
||||
This matches on anything other than integer 14.
|
||||
Even the string <span class="new_code">"14"</span> would pass.
|
||||
<pre>
|
||||
new WithinMarginExpectation(14.0, 0.001)
|
||||
</pre>
|
||||
This will accept any value from 13.999 to 14.001 inclusive.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="behaviour"><h2>Using expectations to control stubs</h2></a></p>
|
||||
<p>
|
||||
The expectation classes can be used not just for sending assertions
|
||||
from mock objects, but also for selecting behaviour for the
|
||||
<a href="mock_objects_documentation.html">mock objects</a>.
|
||||
Anywhere a list of arguments is given, a list of expectation objects
|
||||
can be inserted instead.
|
||||
</p>
|
||||
<p>
|
||||
Suppose we want a mock authorisation server to simulate a successful login,
|
||||
but only if it receives a valid session object.
|
||||
We can do this as follows...
|
||||
<pre>
|
||||
Mock::generate('Authorisation');
|
||||
<strong>
|
||||
$authorisation = new MockAuthorisation();
|
||||
$authorisation->setReturnValue(
|
||||
'isAllowed',
|
||||
true,
|
||||
array(new IsAExpectation('Session', 'Must be a session')));
|
||||
$authorisation->setReturnValue('isAllowed', false);</strong>
|
||||
</pre>
|
||||
We have set the default mock behaviour to return false when
|
||||
<span class="new_code">isAllowed</span> is called.
|
||||
When we call the method with a single parameter that
|
||||
is a <span class="new_code">Session</span> object, it will return true.
|
||||
We have also added a second parameter as a message.
|
||||
This will be displayed as part of the mock object
|
||||
failure message if this expectation is the cause of
|
||||
a failure.
|
||||
</p>
|
||||
<p>
|
||||
This kind of sophistication is rarely useful, but is included for
|
||||
completeness.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="extending"><h2>Creating your own expectations</h2></a></p>
|
||||
<p>
|
||||
The expectation classes have a very simple structure.
|
||||
So simple that it is easy to create your own versions for
|
||||
commonly used test logic.
|
||||
</p>
|
||||
<p>
|
||||
As an example here is the creation of a class to test for
|
||||
valid IP addresses.
|
||||
In order to work correctly with the stubs and mocks the new
|
||||
expectation class should extend
|
||||
<span class="new_code">SimpleExpectation</span>...
|
||||
<pre>
|
||||
<strong>class ValidIp extends SimpleExpectation {
|
||||
|
||||
function test($ip) {
|
||||
return (ip2long($ip) != -1);
|
||||
}
|
||||
|
||||
function testMessage($ip) {
|
||||
return "Address [$ip] should be a valid IP address";
|
||||
}
|
||||
}</strong>
|
||||
</pre>
|
||||
There are only two methods to implement.
|
||||
The <span class="new_code">test()</span> method should
|
||||
evaluate to true if the expectation is to pass, and
|
||||
false otherwise.
|
||||
The <span class="new_code">testMessage()</span> method
|
||||
should simply return some helpful text explaining the test
|
||||
that was carried out.
|
||||
</p>
|
||||
<p>
|
||||
This class can now be used in place of the earlier expectation
|
||||
classes.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="unit"><h2>Under the bonnet of the unit tester</h2></a></p>
|
||||
<p>
|
||||
The <a href="http://sourceforge.net/projects/simpletest/">SimpleTest unit testing framework</a>
|
||||
also uses the expectation classes internally for the
|
||||
<a href="unit_test_documentation.html">UnitTestCase class</a>.
|
||||
We can also take advantage of these mechanisms to reuse our
|
||||
homebrew expectation classes within the test suites directly.
|
||||
</p>
|
||||
<p>
|
||||
The most crude way of doing this is to use the
|
||||
<span class="new_code">SimpleTest::assert()</span> method to
|
||||
test against it directly...
|
||||
<pre>
|
||||
<strong>class TestOfNetworking extends UnitTestCase {
|
||||
...
|
||||
function testGetValidIp() {
|
||||
$server = &new Server();
|
||||
$this->assert(
|
||||
new ValidIp(),
|
||||
$server->getIp(),
|
||||
'Server IP address->%s');
|
||||
}
|
||||
}</strong>
|
||||
</pre>
|
||||
This is a little untidy compared with our usual
|
||||
<span class="new_code">assert...()</span> syntax.
|
||||
</p>
|
||||
<p>
|
||||
For such a simple case we would normally create a
|
||||
separate assertion method on our test case rather
|
||||
than bother using the expectation class.
|
||||
If we pretend that our expectation is a little more
|
||||
complicated for a moment, so that we want to reuse it,
|
||||
we get...
|
||||
<pre>
|
||||
class TestOfNetworking extends UnitTestCase {
|
||||
...<strong>
|
||||
function assertValidIp($ip, $message = '%s') {
|
||||
$this->assert(new ValidIp(), $ip, $message);
|
||||
}</strong>
|
||||
|
||||
function testGetValidIp() {
|
||||
$server = &new Server();<strong>
|
||||
$this->assertValidIp(
|
||||
$server->getIp(),
|
||||
'Server IP address->%s');</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
It is unlikely we would ever need this degree of control
|
||||
over the testing machinery.
|
||||
It is rare to need the expectations for more than pattern
|
||||
matching.
|
||||
Also, complex expectation classes could make the tests
|
||||
harder to read and debug.
|
||||
These mechanisms are really of most use to authors of systems
|
||||
that will extend the test framework to create their own tool set.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
References and related information...
|
||||
<ul>
|
||||
<li>
|
||||
SimpleTest project page on <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
|
||||
</li>
|
||||
<li>
|
||||
SimpleTest download page on <a href="http://www.lastcraft.com/simple_test.php">LastCraft</a>.
|
||||
</li>
|
||||
<li>
|
||||
The expectations mimic the constraints in <a href="http://www.jmock.org/">JMock</a>.
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://simpletest.org/api/">Full API for SimpleTest</a>
|
||||
from the PHPDoc.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<span class="chosen">Expectations</span>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<div class="copyright">
|
||||
Copyright<br>Marcus Baker 2006
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
342
simpletest/docs/en/form_testing_documentation.html
Normal file
342
simpletest/docs/en/form_testing_documentation.html
Normal file
@@ -0,0 +1,342 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>Simple Test documentation for testing HTML forms</title>
|
||||
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
|
||||
</head>
|
||||
<body>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<span class="chosen">Testing forms</span>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<h1>Form testing documentation</h1>
|
||||
This page...
|
||||
<ul>
|
||||
<li>
|
||||
Changing form values and successfully
|
||||
<a href="#submit">Submitting a simple form</a>
|
||||
</li>
|
||||
<li>
|
||||
Handling <a href="#multiple">widgets with multiple values</a>
|
||||
by setting lists.
|
||||
</li>
|
||||
<li>
|
||||
Bypassing javascript to <a href="#hidden-field">set a hidden field</a>.
|
||||
</li>
|
||||
<li>
|
||||
<a href="#raw">Raw posting</a> when you don't have a button
|
||||
to click.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="content">
|
||||
<p><a class="target" name="submit"><h2>Submitting a simple form</h2></a></p>
|
||||
<p>
|
||||
When a page is fetched by the <span class="new_code">WebTestCase</span>
|
||||
using <span class="new_code">get()</span> or
|
||||
<span class="new_code">post()</span> the page content is
|
||||
automatically parsed.
|
||||
This results in any form controls that are inside <form> tags
|
||||
being available from within the test case.
|
||||
For example, if we have this snippet of HTML...
|
||||
<pre>
|
||||
<form>
|
||||
<input type="text" name="a" value="A default" />
|
||||
<input type="submit" value="Go" />
|
||||
</form>
|
||||
</pre>
|
||||
Which looks like this...
|
||||
</p>
|
||||
<p>
|
||||
<form class="demo">
|
||||
<input type="text" name="a" value="A default">
|
||||
<input type="submit" value="Go">
|
||||
</form>
|
||||
</p>
|
||||
<p>
|
||||
We can navigate to this code, via the
|
||||
<a href="http://www.lastcraft.com/form_testing_documentation.php">LastCraft</a>
|
||||
site, with the following test...
|
||||
<pre>
|
||||
class SimpleFormTests extends WebTestCase {
|
||||
<strong>
|
||||
function testDefaultValue() {
|
||||
$this->get('http://www.lastcraft.com/form_testing_documentation.php');
|
||||
$this->assertField('a', 'A default');
|
||||
}</strong>
|
||||
}
|
||||
</pre>
|
||||
Immediately after loading the page all of the HTML controls are set at
|
||||
their default values just as they would appear in the web browser.
|
||||
The assertion tests that a HTML widget exists in the page with the
|
||||
name "a" and that it is currently set to the value
|
||||
"A default".
|
||||
As usual, we could use a pattern expectation instead if a fixed
|
||||
string.
|
||||
</p>
|
||||
<p>
|
||||
We could submit the form straight away, but first we'll change
|
||||
the value of the text field and only then submit it...
|
||||
<pre>
|
||||
class SimpleFormTests extends WebTestCase {
|
||||
|
||||
function testDefaultValue() {
|
||||
$this->get('http://www.my-site.com/');
|
||||
$this->assertField('a', 'A default');<strong>
|
||||
$this->setField('a', 'New value');
|
||||
$this->click('Go');</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
Because we didn't specify a method attribute on the form tag, and
|
||||
didn't specify an action either, the test case will follow
|
||||
the usual browser behaviour of submitting the form data as a <em>GET</em>
|
||||
request back to the same location.
|
||||
SimpleTest tries to emulate typical browser behaviour as much as possible,
|
||||
rather than attempting to catch missing attributes on tags.
|
||||
This is because the target of the testing framework is the PHP application
|
||||
logic, not syntax or other errors in the HTML code.
|
||||
For HTML errors, other tools such as
|
||||
<a href="http://www.w3.org/People/Raggett/tidy/">HTMLTidy</a> should be used.
|
||||
</p>
|
||||
<p>
|
||||
If a field is not present in any form, or if an option is unavailable,
|
||||
then <span class="new_code">WebTestCase::setField()</span> will return
|
||||
<span class="new_code">false</span>.
|
||||
For example, suppose we wish to verify that a "Superuser"
|
||||
option is not present in this form...
|
||||
<pre>
|
||||
<strong>Select type of user to add:</strong>
|
||||
<select name="type">
|
||||
<option>Subscriber</option>
|
||||
<option>Author</option>
|
||||
<option>Administrator</option>
|
||||
</select>
|
||||
</pre>
|
||||
Which looks like...
|
||||
</p>
|
||||
<p>
|
||||
<form class="demo">
|
||||
<strong>Select type of user to add:</strong>
|
||||
<select name="type">
|
||||
<option>Subscriber</option>
|
||||
<option>Author</option>
|
||||
<option>Administrator</option>
|
||||
</select>
|
||||
</form>
|
||||
</p>
|
||||
<p>
|
||||
The following test will confirm it...
|
||||
<pre>
|
||||
class SimpleFormTests extends WebTestCase {
|
||||
...
|
||||
function testNoSuperuserChoiceAvailable() {<strong>
|
||||
$this->get('http://www.lastcraft.com/form_testing_documentation.php');
|
||||
$this->assertFalse($this->setField('type', 'Superuser'));</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
The selection will not be changed on a failure to set
|
||||
a widget value.
|
||||
</p>
|
||||
<p>
|
||||
Here is the full list of widgets currently supported...
|
||||
<ul>
|
||||
<li>Text fields, including hidden and password fields.</li>
|
||||
<li>Submit buttons including the button tag, although not yet reset buttons</li>
|
||||
<li>Text area. This includes text wrapping behaviour.</li>
|
||||
<li>Checkboxes, including multiple checkboxes in the same form.</li>
|
||||
<li>Drop down selections, including multiple selects.</li>
|
||||
<li>Radio buttons.</li>
|
||||
<li>Images.</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
The browser emulation offered by SimpleTest mimics
|
||||
the actions which can be perform by a user on a
|
||||
standard HTML page. Javascript is not supported, and
|
||||
it's unlikely that support will be added any time
|
||||
soon.
|
||||
</p>
|
||||
<p>
|
||||
Of particular note is that the Javascript idiom of
|
||||
passing form results by setting a hidden field cannot
|
||||
be performed using the normal SimpleTest
|
||||
commands. See below for a way to test such forms.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="multiple"><h2>Fields with multiple values</h2></a></p>
|
||||
<p>
|
||||
SimpleTest can cope with two types of multivalue controls: Multiple
|
||||
selection drop downs, and multiple checkboxes with the same name
|
||||
within a form.
|
||||
The multivalue nature of these means that setting and testing
|
||||
are slightly different.
|
||||
Using checkboxes as an example...
|
||||
<pre>
|
||||
<form class="demo">
|
||||
<strong>Create privileges allowed:</strong>
|
||||
<input type="checkbox" name="crud" value="c" checked><br>
|
||||
<strong>Retrieve privileges allowed:</strong>
|
||||
<input type="checkbox" name="crud" value="r" checked><br>
|
||||
<strong>Update privileges allowed:</strong>
|
||||
<input type="checkbox" name="crud" value="u" checked><br>
|
||||
<strong>Destroy privileges allowed:</strong>
|
||||
<input type="checkbox" name="crud" value="d" checked><br>
|
||||
<input type="submit" value="Enable Privileges">
|
||||
</form>
|
||||
</pre>
|
||||
Which renders as...
|
||||
</p>
|
||||
<p>
|
||||
<form class="demo">
|
||||
<strong>Create privileges allowed:</strong>
|
||||
<input type="checkbox" name="crud" value="c" checked><br>
|
||||
<strong>Retrieve privileges allowed:</strong>
|
||||
<input type="checkbox" name="crud" value="r" checked><br>
|
||||
<strong>Update privileges allowed:</strong>
|
||||
<input type="checkbox" name="crud" value="u" checked><br>
|
||||
<strong>Destroy privileges allowed:</strong>
|
||||
<input type="checkbox" name="crud" value="d" checked><br>
|
||||
<input type="submit" value="Enable Privileges">
|
||||
</form>
|
||||
</p>
|
||||
<p>
|
||||
If we wish to disable all but the retrieval privileges and
|
||||
submit this information we can do it like this...
|
||||
<pre>
|
||||
class SimpleFormTests extends WebTestCase {
|
||||
...<strong>
|
||||
function testDisableNastyPrivileges() {
|
||||
$this->get('http://www.lastcraft.com/form_testing_documentation.php');
|
||||
$this->assertField('crud', array('c', 'r', 'u', 'd'));
|
||||
$this->setField('crud', array('r'));
|
||||
$this->click('Enable Privileges');
|
||||
}</strong>
|
||||
}
|
||||
</pre>
|
||||
Instead of setting the field to a single value, we give it a list
|
||||
of values.
|
||||
We do the same when testing expected values.
|
||||
We can then write other test code to confirm the effect of this, perhaps
|
||||
by logging in as that user and attempting an update.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="hidden-field"><h2>Forms which use javascript to set a hidden field</h2></a></p>
|
||||
<p>
|
||||
If you want to test a form which relies on javascript to set a hidden
|
||||
field, you can't just call setField().
|
||||
The following code will <em>not</em> work:
|
||||
<pre>
|
||||
class SimpleFormTests extends WebTestCase {
|
||||
function testMyJavascriptForm() {
|
||||
<strong>// This does *not* work</strong>
|
||||
$this->setField('a_hidden_field', '123');
|
||||
$this->clickSubmit('OK');
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
Instead, you need to pass the additional form parameters to the
|
||||
clickSubmit() method:
|
||||
<pre>
|
||||
class SimpleFormTests extends WebTestCase {
|
||||
function testMyJavascriptForm() {
|
||||
// Pass the hidden field value as an additional POST variable
|
||||
<strong>$this->clickSubmit('OK', array('a_hidden_field'=>'123'));</strong>
|
||||
}
|
||||
|
||||
}
|
||||
</pre>
|
||||
</p>
|
||||
<p>
|
||||
Bear in mind that in doing this you're effectively stubbing out a
|
||||
part of your software (the javascript code in the form), and
|
||||
perhaps you might be better off using something like
|
||||
<a href="http://selenium.openqa.org/">Selenium</a> to ensure a complete
|
||||
acceptance test.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="raw"><h2>Raw posting</h2></a></p>
|
||||
<p>
|
||||
If you want to test a form handler, but have not yet written
|
||||
or do not have access to the form itself, you can create a
|
||||
form submission by hand.
|
||||
<pre>
|
||||
class SimpleFormTests extends WebTestCase {
|
||||
...<strong>
|
||||
function testAttemptedHack() {
|
||||
$this->post(
|
||||
'http://www.my-site.com/add_user.php',
|
||||
array('type' => 'superuser'));
|
||||
$this->assertNoText('user created');
|
||||
}</strong>
|
||||
}
|
||||
</pre>
|
||||
By adding data to the <span class="new_code">WebTestCase::post()</span>
|
||||
method, we are attempting to fetch the page as a form submission.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
References and related information...
|
||||
<ul>
|
||||
<li>
|
||||
SimpleTest project page on <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
|
||||
</li>
|
||||
<li>
|
||||
SimpleTest download page on <a href="http://www.lastcraft.com/simple_test.php">LastCraft</a>.
|
||||
</li>
|
||||
<li>
|
||||
The <a href="http://simpletest.org/api/">developer's API for SimpleTest</a>
|
||||
gives full detail on the classes and assertions available.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<span class="chosen">Testing forms</span>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<div class="copyright">
|
||||
Copyright<br>Marcus Baker 2006
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
386
simpletest/docs/en/group_test_documentation.html
Normal file
386
simpletest/docs/en/group_test_documentation.html
Normal file
@@ -0,0 +1,386 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>SimpleTest for PHP test suites</title>
|
||||
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
|
||||
</head>
|
||||
<body>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<span class="chosen">Group tests</span>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<h1>Test suite documentation</h1>
|
||||
This page...
|
||||
<ul>
|
||||
<li>
|
||||
Different ways to <a href="#group">group tests</a> together.
|
||||
</li>
|
||||
<li>
|
||||
Combining group tests into <a href="#higher">larger groups</a>.
|
||||
</li>
|
||||
<li>
|
||||
Integrating <a href="#legacy">legacy test cases</a> from other
|
||||
types of PHPUnit.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="content">
|
||||
<p><a class="target" name="group"><h2>Grouping tests into suites</h2></a></p>
|
||||
<p>
|
||||
To run test cases as part of a group, the test cases should really
|
||||
be placed in files without the runner code...
|
||||
<pre>
|
||||
<strong><?php
|
||||
require_once('../classes/io.php');
|
||||
|
||||
class FileTester extends UnitTestCase {
|
||||
...
|
||||
}
|
||||
|
||||
class SocketTester extends UnitTestCase {
|
||||
...
|
||||
}
|
||||
?></strong>
|
||||
</pre>
|
||||
As many cases as needed can appear in a single file.
|
||||
They should include any code they need, such as the library
|
||||
being tested, but none of the simple test libraries.
|
||||
</p>
|
||||
<p>
|
||||
If you have extended any test cases, you can include them
|
||||
as well. In PHP 4...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('../classes/io.php');
|
||||
<strong>
|
||||
class MyFileTestCase extends UnitTestCase {
|
||||
...
|
||||
}
|
||||
SimpleTest::ignore('MyFileTestCase');</strong>
|
||||
|
||||
class FileTester extends MyFileTestCase { ... }
|
||||
|
||||
class SocketTester extends UnitTestCase { ... }
|
||||
?>
|
||||
</pre>
|
||||
The <span class="new_code">FileTester</span> class does
|
||||
not contain any actual tests, but is a base class for other
|
||||
test cases.
|
||||
For this reason we use the
|
||||
<span class="new_code">SimpleTestOptions::ignore()</span> directive
|
||||
to tell the upcoming group test to ignore it.
|
||||
This directive can appear anywhere in the file and works
|
||||
when a whole file of test cases is loaded (see below).
|
||||
</p>
|
||||
<p>
|
||||
If you are using PHP 5, you do not need this special directive at all.
|
||||
Simply mark any test cases that should not be run as abstract...
|
||||
<pre>
|
||||
<strong>abstract</strong> class MyFileTestCase extends UnitTestCase {
|
||||
...
|
||||
}
|
||||
|
||||
class FileTester extends MyFileTestCase { ... }
|
||||
|
||||
class SocketTester extends UnitTestCase { ... }
|
||||
</pre>
|
||||
</p>
|
||||
<p>
|
||||
We will call this sample <em>file_test.php</em>.
|
||||
Next we create a group test file, called say <em>my_group_test.php</em>.
|
||||
You will think of a better name I am sure.
|
||||
</p>
|
||||
<p>
|
||||
We will add the test file using a safe method...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/unit_tester.php');
|
||||
require_once('simpletest/reporter.php');<strong>
|
||||
require_once('file_test.php');
|
||||
|
||||
$test = &new TestSuite('All file tests');
|
||||
$test->addTestCase(new FileTestCase());
|
||||
$test->run(new HtmlReporter());</strong>
|
||||
?>
|
||||
</pre>
|
||||
This instantiates the test case before the test suite is
|
||||
run.
|
||||
This could get a little expensive with a large number of test
|
||||
cases, and can be surprising behaviour.
|
||||
</p>
|
||||
<p>
|
||||
The main problem is that for every test case
|
||||
that we add we will have
|
||||
to <span class="new_code">require_once()</span> the test code
|
||||
file and manually instantiate each and every test case.
|
||||
</p>
|
||||
<p>
|
||||
We can save a lot of typing with...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/unit_tester.php');
|
||||
require_once('simpletest/reporter.php');
|
||||
|
||||
$test = &new TestSuite('All file tests');<strong>
|
||||
$test->addTestFile('file_test.php');</strong>
|
||||
$test->run(new HtmlReporter());
|
||||
?&gt;
|
||||
</pre>
|
||||
What happens here is that the <span class="new_code">TestSuite</span>
|
||||
class has done the <span class="new_code">require_once()</span>
|
||||
for us.
|
||||
It then checks to see if any new test case classes
|
||||
have been created by the new file and automatically adds
|
||||
them to the group test.
|
||||
Now all we have to do is add each new file.
|
||||
</p>
|
||||
<p>
|
||||
No only that, but you can guarantee that the constructor is run
|
||||
just before the first test method and, in PHP 5, the destructor
|
||||
is run just after the last test method.
|
||||
</p>
|
||||
<p>
|
||||
There are two things that could go wrong and which require care...
|
||||
<ol>
|
||||
<li>
|
||||
The file could already have been parsed by PHP, and so no
|
||||
new classes will have been added. You should make
|
||||
sure that the test cases are only included in this file
|
||||
and no others (Note : with the new <cite>autorun</cite>
|
||||
functionnality, this problem has now been solved).
|
||||
</li>
|
||||
<li>
|
||||
New test case extension classes that get included will be
|
||||
placed in the group test and run also.
|
||||
You will need to add a <span class="new_code">SimpleTestOptions::ignore()</span>
|
||||
directive for these classes, or make sure that they are included
|
||||
before the <span class="new_code">TestSuite::addTestFile()</span>
|
||||
line, or make sure that they are abstract classes.
|
||||
</li>
|
||||
</ol>
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="higher"><h2>Composite suites</h2></a></p>
|
||||
<p>
|
||||
The above method places all of the test cases into one large group.
|
||||
For larger projects though this may not be flexible enough; you
|
||||
may want to group the tests in all sorts of ways.
|
||||
</p>
|
||||
<p>
|
||||
To get a more flexible group test we can subclass
|
||||
<span class="new_code">TestSuite</span> and then instantiate it as needed...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/unit_tester.php');
|
||||
require_once('simpletest/reporter.php');
|
||||
<strong>
|
||||
class FileTestSuite extends TestSuite {
|
||||
function FileTestSuite() {
|
||||
$this->TestSuite('All file tests');
|
||||
$this->addTestFile('file_test.php');
|
||||
}
|
||||
}</strong>
|
||||
?>
|
||||
</pre>
|
||||
This effectively names the test in the constructor and then
|
||||
adds our test cases and a single group below.
|
||||
Of course we can add more than one group at this point.
|
||||
We can now invoke the tests from a separate runner file...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('file_test_suite.php');
|
||||
<strong>
|
||||
$test = &new FileTestSuite();
|
||||
$test->run(new HtmlReporter());</strong>
|
||||
?>
|
||||
</pre>
|
||||
...or we can group them into even larger group tests.
|
||||
We can even mix groups and test cases freely as long as
|
||||
we are careful about double includes...
|
||||
<pre>
|
||||
<?php
|
||||
<strong>
|
||||
$test = &new BigTestSuite('Big group');
|
||||
$test->addTestFile('file_test_suite.php');
|
||||
$test->addTestFile('some_test_case.php');</strong>
|
||||
$test->run(new HtmlReporter());
|
||||
?>
|
||||
</pre>
|
||||
In the event of a double include, ony the first instance
|
||||
of the test case will be run.
|
||||
</p>
|
||||
<p>
|
||||
If we still wish to run the original group test, and we
|
||||
don't want all of these little runner files, we can
|
||||
put the test runner code around guard bars when we create
|
||||
each group.
|
||||
<pre>
|
||||
<?php
|
||||
class FileTestSuite extends TestSuite {
|
||||
function FileTestSuite() {
|
||||
$this->TestSuite('All file tests');
|
||||
$test->addTestFile('file_test.php');
|
||||
}
|
||||
}
|
||||
<strong>
|
||||
if (! defined('RUNNER')) {
|
||||
define('RUNNER', true);</strong>
|
||||
$test = &new FileTestSuite();
|
||||
$test->run(new HtmlReporter());
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
This approach requires the guard to be set when including
|
||||
the group test file, but this is still less hassle than
|
||||
lots of separate runner files.
|
||||
You include the same guard on the top level tests to make sure
|
||||
that <span class="new_code">run()</span> will run once only
|
||||
from the top level script that has been invoked.
|
||||
<pre>
|
||||
<?php<strong>
|
||||
define('RUNNER', true);</strong>
|
||||
require_once('file_test_suite.php');
|
||||
|
||||
$test = &new BigTestSuite('Big group');
|
||||
$test->addTestCase(new FileTestSuite());
|
||||
$test->addTestCase(...);
|
||||
$test->run(new HtmlReporter());
|
||||
?>
|
||||
</pre>
|
||||
As with the normal test cases, a <span class="new_code">TestSuite</span> can
|
||||
be loaded with the <span class="new_code">TestSuite::addTestFile()</span> method.
|
||||
<pre>
|
||||
<?php
|
||||
define('RUNNER', true);
|
||||
|
||||
$test = &new BigTestSuite('Big group');<strong>
|
||||
$test->addTestFile('file_test_suite.php');
|
||||
$test->addTestFile(...);</strong>
|
||||
$test->run(new HtmlReporter());
|
||||
?>
|
||||
</pre>
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="legacy"><h2>Integrating legacy test cases</h2></a></p>
|
||||
<p>
|
||||
If you already have unit tests for your code or are extending external
|
||||
classes that have tests, it is unlikely that all of the test cases
|
||||
are in SimpleTest format.
|
||||
Fortunately it is possible to incorporate test cases from other
|
||||
unit testers directly into SimpleTest group tests.
|
||||
</p>
|
||||
<p>
|
||||
Say we have the following
|
||||
<a href="http://sourceforge.net/projects/phpunit">PhpUnit</a>
|
||||
test case in the file <em>config_test.php</em>...
|
||||
<pre>
|
||||
<strong>class ConfigFileTest extends TestCase {
|
||||
function ConfigFileTest() {
|
||||
$this->TestCase('Config file test');
|
||||
}
|
||||
|
||||
function testContents() {
|
||||
$config = new ConfigFile('test.conf');
|
||||
$this->assertRegexp('/me/', $config->getValue('username'));
|
||||
}
|
||||
}</strong>
|
||||
</pre>
|
||||
The group test can recognise this as long as we include
|
||||
the appropriate adapter class before we add the test
|
||||
file...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/unit_tester.php');
|
||||
require_once('simpletest/reporter.php');<strong>
|
||||
require_once('simpletest/adapters/phpunit_test_case.php');</strong>
|
||||
|
||||
$test = &new TestSuite('All file tests');<strong>
|
||||
$test->addTestFile('config_test.php');</strong>
|
||||
$test->run(new HtmlReporter());
|
||||
?>
|
||||
</pre>
|
||||
There are only two adapters, the other is for the
|
||||
<a href="http://pear.php.net/manual/en/package.php.phpunit.php">PEAR</a>
|
||||
1.0 unit tester...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/unit_tester.php');
|
||||
require_once('simpletest/reporter.php');<strong>
|
||||
require_once('simpletest/adapters/pear_test_case.php');</strong>
|
||||
|
||||
$test = &new TestSuite('All file tests');<strong>
|
||||
$test->addTestFile('some_pear_test_cases.php');</strong>
|
||||
$test->run(new HtmlReporter());
|
||||
?>
|
||||
</pre>
|
||||
The PEAR test cases can be freely mixed with SimpleTest
|
||||
ones even in the same test file,
|
||||
but you cannot use SimpleTest assertions in the legacy
|
||||
test case versions.
|
||||
This is done as a check that you are not accidently making
|
||||
your test cases completely dependent on SimpleTest.
|
||||
You may want to do a PEAR release of your library for example,
|
||||
which would mean shipping it with valid PEAR::PhpUnit test
|
||||
cases.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
References and related information...
|
||||
<ul>
|
||||
<li>
|
||||
SimpleTest project page on <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
|
||||
</li>
|
||||
<li>
|
||||
SimpleTest download page on <a href="http://www.lastcraft.com/simple_test.php">LastCraft</a>.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<span class="chosen">Group tests</span>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<div class="copyright">
|
||||
Copyright<br>Marcus Baker 2006
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
538
simpletest/docs/en/index.html
Normal file
538
simpletest/docs/en/index.html
Normal file
@@ -0,0 +1,538 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>
|
||||
Download the Simple Test testing framework -
|
||||
Unit tests and mock objects for PHP
|
||||
</title>
|
||||
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
|
||||
</head>
|
||||
<body>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<span class="chosen">SimpleTest</span>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<h1>Simple Test for PHP</h1>
|
||||
This page...
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#unit">Using unit tester</a>
|
||||
with an example.
|
||||
</li>
|
||||
<li>
|
||||
<a href="#group">Grouping tests</a>
|
||||
for testing with one click.
|
||||
</li>
|
||||
<li>
|
||||
<a href="#mock">Using mock objects</a>
|
||||
to ease testing and gain tighter control.
|
||||
</li>
|
||||
<li>
|
||||
<a href="#web">Testing web pages</a>
|
||||
at the browser level.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="content">
|
||||
|
||||
|
||||
<p>
|
||||
The following assumes that you are familiar with the concept
|
||||
of unit testing as well as the PHP web development language.
|
||||
It is a guide for the impatient new user of
|
||||
<a href="https://sourceforge.net/project/showfiles.php?group_id=76550">SimpleTest</a>.
|
||||
For fuller documentation, especially if you are new
|
||||
to unit testing see the ongoing
|
||||
<a href="unit_test_documentation.html">documentation</a>, and for
|
||||
example test cases see the
|
||||
<a href="http://www.lastcraft.com/first_test_tutorial.php">unit testing tutorial</a>.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="unit"><h2>Using the tester quickly</h2></a></p>
|
||||
<p>
|
||||
Amongst software testing tools, a unit tester is the one
|
||||
closest to the developer.
|
||||
In the context of agile development the test code sits right
|
||||
next to the source code as both are written simultaneously.
|
||||
In this context SimpleTest aims to be a complete PHP developer
|
||||
test solution and is called "Simple" because it
|
||||
should be easy to use and extend.
|
||||
It wasn't a good choice of name really.
|
||||
It includes all of the typical functions you would expect from
|
||||
<a href="http://www.junit.org/">JUnit</a> and the
|
||||
<a href="http://sourceforge.net/projects/phpunit/">PHPUnit</a>
|
||||
ports, and includes
|
||||
<a href="http://www.mockobjects.com">mock objects</a>.
|
||||
</p>
|
||||
<p>
|
||||
What makes this tool immediately useful to the PHP developer is the internal
|
||||
web browser.
|
||||
This allows tests that navigate web sites, fill in forms and test pages.
|
||||
Being able to write these test in PHP means that it is easy to write
|
||||
integrated tests.
|
||||
An example might be confirming that a user was written to a database
|
||||
after a signing up through the web site.
|
||||
</p>
|
||||
<p>
|
||||
The quickest way to demonstrate SimpleTest is with an example.
|
||||
</p>
|
||||
<p>
|
||||
Let us suppose we are testing a simple file logging class called
|
||||
<span class="new_code">Log</span> in <em>classes/log.php</em>.
|
||||
We start by creating a test script which we will call
|
||||
<em>tests/log_test.php</em> and populate it as follows...
|
||||
<pre>
|
||||
<?php
|
||||
<strong>require_once('simpletest/autorun.php');</strong>
|
||||
require_once('../classes/log.php');
|
||||
|
||||
class TestOfLogging extends <strong>UnitTestCase</strong> {
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
Here the <em>simpletest</em> folder is either local or in the path.
|
||||
You would have to edit these locations depending on where you
|
||||
unpacked the toolset.
|
||||
The "autorun.php" file does more than just include the
|
||||
SimpleTest files, it also runs our test for us.
|
||||
</p>
|
||||
<p>
|
||||
The <span class="new_code">TestOfLogging</span> is our first test case and it's
|
||||
currently empty.
|
||||
Each test case is a class that extends one of the SimpleTet base classes
|
||||
and we can have as many of these in the file as we want.
|
||||
</p>
|
||||
<p>
|
||||
With three lines of scaffolding, and our <span class="new_code">Log</span> class
|
||||
include, we have a test suite.
|
||||
No tests though.
|
||||
</p>
|
||||
<p>
|
||||
For our first test, we'll assume that the <span class="new_code">Log</span> class
|
||||
takes the file name to write to in the constructor, and we have
|
||||
a temporary folder in which to place this file...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/autorun.php');
|
||||
require_once('../classes/log.php');
|
||||
|
||||
class TestOfLogging extends UnitTestCase {
|
||||
function <strong>testLogCreatesNewFileOnFirstMessage()</strong> {
|
||||
@unlink('/temp/test.log');
|
||||
$log = new Log('/temp/test.log');
|
||||
<strong>$this->assertFalse(file_exists('/temp/test.log'));</strong>
|
||||
$log->message('Should write this to a file');
|
||||
<strong>$this->assertTrue(file_exists('/temp/test.log'));</strong>
|
||||
}
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
When a test case runs, it will search for any method that
|
||||
starts with the string "test"
|
||||
and execute that method.
|
||||
If the method starts "test", it's a test.
|
||||
Note the very long name <span class="new_code">testLogCreatesNewFileOnFirstMessage()</span>.
|
||||
This is considered good style and makes the test output more readable.
|
||||
</p>
|
||||
<p>
|
||||
We would normally have more than one test method in a test case,
|
||||
but that's for later.
|
||||
</p>
|
||||
<p>
|
||||
Assertions within the test methods trigger messages to the
|
||||
test framework which displays the result immediately.
|
||||
This immediate response is important, not just in the event
|
||||
of the code causing a crash, but also so that
|
||||
<span class="new_code">print</span> statements can display
|
||||
their debugging content right next to the assertion concerned.
|
||||
</p>
|
||||
<p>
|
||||
To see these results we have to actually run the tests.
|
||||
No other code is necessary - we can just open the page
|
||||
with our browser.
|
||||
</p>
|
||||
<p>
|
||||
On failure the display looks like this...
|
||||
<div class="demo">
|
||||
<h1>TestOfLogging</h1>
|
||||
<span class="fail">Fail</span>: testLogCreatesNewFileOnFirstMessage->True assertion failed.<br>
|
||||
<div style="padding: 8px; margin-top: 1em; background-color: red; color: white;">1/1 test cases complete.
|
||||
<strong>1</strong> passes and <strong>1</strong> fails.</div>
|
||||
</div>
|
||||
...and if it passes like this...
|
||||
<div class="demo">
|
||||
<h1>TestOfLogging</h1>
|
||||
<div style="padding: 8px; margin-top: 1em; background-color: green; color: white;">1/1 test cases complete.
|
||||
<strong>2</strong> passes and <strong>0</strong> fails.</div>
|
||||
</div>
|
||||
And if you get this...
|
||||
<div class="demo">
|
||||
<b>Fatal error</b>: Failed opening required '../classes/log.php' (include_path='') in <b>/home/marcus/projects/lastcraft/tutorial_tests/Log/tests/log_test.php</b> on line <b>7</b>
|
||||
</div>
|
||||
it means you're missing the <em>classes/Log.php</em> file that could look like...
|
||||
<pre>
|
||||
<?php<strong>
|
||||
class Log {
|
||||
function Log($file_path) {
|
||||
}
|
||||
|
||||
function message() {
|
||||
}
|
||||
}</strong>
|
||||
?>
|
||||
</pre>
|
||||
It's fun to write the code after the test.
|
||||
More than fun even -
|
||||
this system is called "Test Driven Development".
|
||||
</p>
|
||||
<p>
|
||||
For more information about <span class="new_code">UnitTestCase</span>, see
|
||||
the <a href="unit_test_documentation.html">unit test documentation</a>.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="group"><h2>Building test suites</h2></a></p>
|
||||
<p>
|
||||
It is unlikely in a real application that we will only ever run
|
||||
one test case.
|
||||
This means that we need a way of grouping cases into a test
|
||||
script that can, if need be, run every test for the application.
|
||||
</p>
|
||||
<p>
|
||||
Our first step is to create a new file called <em>tests/all_tests.php</em>
|
||||
and insert the following code...
|
||||
<pre>
|
||||
<?php
|
||||
<strong>require_once('simpletest/autorun.php');</strong>
|
||||
|
||||
class AllTests extends <strong>TestSuite</strong> {
|
||||
function AllTests() {
|
||||
$this->TestSuite(<strong>'All tests'</strong>);
|
||||
<strong>$this->addFile('log_test.php');</strong>
|
||||
}
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
The "autorun" include allows our upcoming test suite
|
||||
to be run just by invoking this script.
|
||||
</p>
|
||||
<p>
|
||||
The <span class="new_code">TestSuite</span> subclass must chain it's constructor.
|
||||
This limitation will be removed in future versions.
|
||||
</p>
|
||||
<p>
|
||||
The method <span class="new_code">TestSuite::addFile()</span>
|
||||
will include the test case file and read any new classes
|
||||
that are descended from <span class="new_code">SimpleTestCase</span>.
|
||||
<span class="new_code">UnitTestCase</span> is just one example of a class derived from
|
||||
<span class="new_code">SimpleTestCase</span>, and you can create your own.
|
||||
<span class="new_code">TestSuite::addFile()</span> can include other test suites.
|
||||
</p>
|
||||
<p>
|
||||
The class will not be instantiated yet.
|
||||
When the test suite runs it will construct each instance once
|
||||
it reaches that test, then destroy it straight after.
|
||||
This means that the constructor is run just before each run
|
||||
of that test case, and the destructor is run before the next test case starts.
|
||||
</p>
|
||||
<p>
|
||||
It is common to group test case code into superclasses which are not
|
||||
supposed to run, but become the base classes of other tests.
|
||||
For "autorun" to work properly the test case file should not blindly run
|
||||
any other test case extensions that do not actually run tests.
|
||||
This could result in extra test cases being counted during the test
|
||||
run.
|
||||
Hardly a major problem, but to avoid this inconvenience simply mark your
|
||||
base class as <span class="new_code">abstract</span>.
|
||||
SimpleTest won't run abstract classes.
|
||||
If you are still using PHP4, then
|
||||
a <span class="new_code">SimpleTestOptions::ignore()</span> directive
|
||||
somewhere in the test case file will have the same effect.
|
||||
</p>
|
||||
<p>
|
||||
Also, the test case file should not have been included
|
||||
elsewhere or no cases will be added to this group test.
|
||||
This would be a more serious error as if the test case classes are
|
||||
already loaded by PHP the <span class="new_code">TestSuite::addFile()</span>
|
||||
method will not detect them.
|
||||
</p>
|
||||
<p>
|
||||
To display the results it is necessary only to invoke
|
||||
<em>tests/all_tests.php</em> from the web server or the command line.
|
||||
</p>
|
||||
<p>
|
||||
For more information about building test suites,
|
||||
see the <a href="group_test_documentation.html">test suite documentation</a>.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="mock"><h2>Using mock objects</h2></a></p>
|
||||
<p>
|
||||
Let's move further into the future and do something really complicated.
|
||||
</p>
|
||||
<p>
|
||||
Assume that our logging class is tested and completed.
|
||||
Assume also that we are testing another class that is
|
||||
required to write log messages, say a
|
||||
<span class="new_code">SessionPool</span>.
|
||||
We want to test a method that will probably end up looking
|
||||
like this...
|
||||
<pre><strong>
|
||||
class SessionPool {
|
||||
...
|
||||
function logIn($username) {
|
||||
...
|
||||
$this->_log->message("User $username logged in.");
|
||||
...
|
||||
}
|
||||
...
|
||||
}
|
||||
</strong>
|
||||
</pre>
|
||||
In the spirit of reuse, we are using our
|
||||
<span class="new_code">Log</span> class.
|
||||
A conventional test case might look like this...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/autorun.php');
|
||||
require_once('../classes/log.php');
|
||||
<strong>require_once('../classes/session_pool.php');</strong>
|
||||
|
||||
class <strong>TestOfSessionLogging</strong> extends UnitTestCase {
|
||||
|
||||
function setUp() {
|
||||
<strong>@unlink('/temp/test.log');</strong>
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
<strong>@unlink('/temp/test.log');</strong>
|
||||
}
|
||||
|
||||
function testLoggingInIsLogged() {
|
||||
<strong>$log = new Log('/temp/test.log');
|
||||
$session_pool = &new SessionPool($log);
|
||||
$session_pool->logIn('fred');</strong>
|
||||
$messages = file('/temp/test.log');
|
||||
$this->assertEqual($messages[0], "User fred logged in.<strong>\n</strong>");
|
||||
}
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
We'll explain the <span class="new_code">setUp()</span> and <span class="new_code">tearDown()</span>
|
||||
methods later.
|
||||
</p>
|
||||
<p>
|
||||
This test case design is not all bad, but it could be improved.
|
||||
We are spending time fiddling with log files which are
|
||||
not part of our test.
|
||||
We have created close ties with the <span class="new_code">Log</span> class and
|
||||
this test.
|
||||
What if we don't use files any more, but use ths
|
||||
<em>syslog</em> library instead?
|
||||
It means that our <span class="new_code">TestOfSessionLogging</span> test will
|
||||
fail, even thouh it's not testing Logging.
|
||||
</p>
|
||||
<p>
|
||||
It's fragile in smaller ways too.
|
||||
Did you notice the extra carriage return in the message?
|
||||
Was that added by the logger?
|
||||
What if it also added a time stamp or other data?
|
||||
</p>
|
||||
<p>
|
||||
The only part that we really want to test is that a particular
|
||||
message was sent to the logger.
|
||||
We can reduce coupling if we pass in a fake logging class
|
||||
that simply records the message calls for testing, but
|
||||
takes no action.
|
||||
It would have to look exactly like our original though.
|
||||
</p>
|
||||
<p>
|
||||
If the fake object doesn't write to a file then we save on deleting
|
||||
the file before and after each test. We could save even more
|
||||
test code if the fake object would kindly run the assertion for us.
|
||||
<p>
|
||||
</p>
|
||||
Too good to be true?
|
||||
We can create such an object easily...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/autorun.php');
|
||||
require_once('../classes/log.php');
|
||||
require_once('../classes/session_pool.php');
|
||||
|
||||
<strong>Mock::generate('Log');</strong>
|
||||
|
||||
class TestOfSessionLogging extends UnitTestCase {
|
||||
|
||||
function testLoggingInIsLogged() {<strong>
|
||||
$log = &new MockLog();
|
||||
$log->expectOnce('message', array('User fred logged in.'));</strong>
|
||||
$session_pool = &new SessionPool(<strong>$log</strong>);
|
||||
$session_pool->logIn('fred');
|
||||
}
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
The <span class="new_code">Mock::generate()</span> call code generated a new class
|
||||
called <span class="new_code">MockLog</span>.
|
||||
This looks like an identical clone, except that we can wire test code
|
||||
to it.
|
||||
That's what <span class="new_code">expectOnce()</span> does.
|
||||
It says that if <span class="new_code">message()</span> is ever called on me, it had
|
||||
better be with the parameter "User fred logged in.".
|
||||
</p>
|
||||
<p>
|
||||
The test will be triggered when the call to
|
||||
<span class="new_code">message()</span> is invoked on the
|
||||
<span class="new_code">MockLog</span> object by <span class="new_code">SessionPool::logIn()</span> code.
|
||||
The mock call will trigger a parameter comparison and then send the
|
||||
resulting pass or fail event to the test display.
|
||||
Wildcards can be included here too, so you don't have to test every parameter of
|
||||
a call when you only want to test one.
|
||||
</p>
|
||||
<p>
|
||||
If the mock reaches the end of the test case without the
|
||||
method being called, the <span class="new_code">expectOnce()</span>
|
||||
expectation will trigger a test failure.
|
||||
In other words the mocks can detect the absence of
|
||||
behaviour as well as the presence.
|
||||
</p>
|
||||
<p>
|
||||
The mock objects in the SimpleTest suite can have arbitrary
|
||||
return values set, sequences of returns, return values
|
||||
selected according to the incoming arguments, sequences of
|
||||
parameter expectations and limits on the number of times
|
||||
a method is to be invoked.
|
||||
</p>
|
||||
<p>
|
||||
For more information about mocking and stubbing, see the
|
||||
<a href="mock_objects_documentation.html">mock objects documentation</a>.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="web"><h2>Web page testing</h2></a></p>
|
||||
<p>
|
||||
One of the requirements of web sites is that they produce web
|
||||
pages.
|
||||
If you are building a project top-down and you want to fully
|
||||
integrate testing along the way then you will want a way of
|
||||
automatically navigating a site and examining output for
|
||||
correctness.
|
||||
This is the job of a web tester.
|
||||
</p>
|
||||
<p>
|
||||
The web testing in SimpleTest is fairly primitive, as there is
|
||||
no JavaScript.
|
||||
Most other browser operations are simulated.
|
||||
</p>
|
||||
<p>
|
||||
To give an idea here is a trivial example where a home
|
||||
page is fetched, from which we navigate to an "about"
|
||||
page and then test some client determined content.
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/autorun.php');
|
||||
<strong>require_once('simpletest/web_tester.php');</strong>
|
||||
|
||||
class TestOfAbout extends <strong>WebTestCase</strong> {
|
||||
function testOurAboutPageGivesFreeReignToOurEgo() {
|
||||
<strong>$this->get('http://test-server/index.php');
|
||||
$this->click('About');
|
||||
$this->assertTitle('About why we are so great');
|
||||
$this->assertText('We are really great');</strong>
|
||||
}
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
With this code as an acceptance test, you can ensure that
|
||||
the content always meets the specifications of both the
|
||||
developers, and the other project stakeholders.
|
||||
</p>
|
||||
<p>
|
||||
You can navigate forms too...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/autorun.php');
|
||||
require_once('simpletest/web_tester.php');
|
||||
|
||||
class TestOfRankings extends WebTestCase {
|
||||
function testWeAreTopOfGoogle() {
|
||||
$this->get('http://google.com/');
|
||||
$this->setField('q', 'simpletest');
|
||||
$this->click("I'm Feeling Lucky");
|
||||
$this->assertTitle('SimpleTest - Unit Testing for PHP');
|
||||
}
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
...although this could violate Google's(tm) terms and conditions.
|
||||
</p>
|
||||
<p>
|
||||
For more information about web testing, see the
|
||||
<a href="browser_documentation.html">scriptable
|
||||
browser documentation</a> and the
|
||||
<a href="web_tester_documentation.html">WebTestCase</a>.
|
||||
</p>
|
||||
<p>
|
||||
<a href="http://sourceforge.net/projects/simpletest/"><img src="http://sourceforge.net/sflogo.php?group_id=76550&type=5" width="210" height="62" border="0" alt="SourceForge.net Logo"></a>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
References and related information...
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://sourceforge.net/project/showfiles.php?group_id=76550&release_id=153280">Download PHP Simple Test</a>
|
||||
from <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
|
||||
</li>
|
||||
<li>
|
||||
The <a href="http://simpletest.org/api/">developer's API for SimpleTest</a>
|
||||
gives full detail on the classes and assertions available.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<span class="chosen">SimpleTest</span>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<div class="copyright">
|
||||
Copyright<br>Marcus Baker 2006
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
757
simpletest/docs/en/mock_objects_documentation.html
Normal file
757
simpletest/docs/en/mock_objects_documentation.html
Normal file
@@ -0,0 +1,757 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>SimpleTest for PHP mock objects documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
|
||||
</head>
|
||||
<body>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<span class="chosen">Mock objects</span>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<h1>Mock objects documentation</h1>
|
||||
This page...
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#what">What are mock objects?</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#creation">Creating mock objects</a>.
|
||||
</li>
|
||||
<li>
|
||||
<a href="#stub">Mocks as actors</a> or stubs.
|
||||
</li>
|
||||
<li>
|
||||
<a href="#expectations">Mocks as critics</a> with expectations.
|
||||
</li>
|
||||
<li>
|
||||
<a href="#approaches">Other approaches</a> including mock libraries.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="content">
|
||||
<p><a class="target" name="what"><h2>What are mock objects?</h2></a></p>
|
||||
<p>
|
||||
Mock objects have two roles during a test case: actor and critic.
|
||||
</p>
|
||||
<p>
|
||||
The actor behaviour is to simulate objects that are difficult to
|
||||
set up or time consuming to set up for a test.
|
||||
The classic example is a database connection.
|
||||
Setting up a test database at the start of each test would slow
|
||||
testing to a crawl and would require the installation of the
|
||||
database engine and test data on the test machine.
|
||||
If we can simulate the connection and return data of our
|
||||
choosing we not only win on the pragmatics of testing, but can
|
||||
also feed our code spurious data to see how it responds.
|
||||
We can simulate databases being down or other extremes
|
||||
without having to create a broken database for real.
|
||||
In other words, we get greater control of the test environment.
|
||||
</p>
|
||||
<p>
|
||||
If mock objects only behaved as actors they would simply be
|
||||
known as server stubs.
|
||||
This was originally a pattern named by Robert Binder (Testing
|
||||
object-oriented systems: models, patterns, and tools,
|
||||
Addison-Wesley) in 1999.
|
||||
</p>
|
||||
<p>
|
||||
A server stub is a simulation of an object or component.
|
||||
It should exactly replace a component in a system for test
|
||||
or prototyping purposes, but remain lightweight.
|
||||
This allows tests to run more quickly, or if the simulated
|
||||
class has not been written, to run at all.
|
||||
</p>
|
||||
<p>
|
||||
However, the mock objects not only play a part (by supplying chosen
|
||||
return values on demand) they are also sensitive to the
|
||||
messages sent to them (via expectations).
|
||||
By setting expected parameters for a method call they act
|
||||
as a guard that the calls upon them are made correctly.
|
||||
If expectations are not met they save us the effort of
|
||||
writing a failed test assertion by performing that duty on our
|
||||
behalf.
|
||||
</p>
|
||||
<p>
|
||||
In the case of an imaginary database connection they can
|
||||
test that the query, say SQL, was correctly formed by
|
||||
the object that is using the connection.
|
||||
Set them up with fairly tight expectations and you will
|
||||
hardly need manual assertions at all.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="creation"><h2>Creating mock objects</h2></a></p>
|
||||
<p>
|
||||
In the same way that we create server stubs, all we need is an
|
||||
existing class, say a database connection that looks like this...
|
||||
<pre>
|
||||
<strong>class DatabaseConnection {
|
||||
function DatabaseConnection() {
|
||||
}
|
||||
|
||||
function query() {
|
||||
}
|
||||
|
||||
function selectQuery() {
|
||||
}
|
||||
}</strong>
|
||||
</pre>
|
||||
The class does not need to have been implemented yet.
|
||||
To create a mock version of the class we need to include the
|
||||
mock object library and run the generator...
|
||||
<pre>
|
||||
<strong>require_once('simpletest/unit_tester.php');
|
||||
require_once('simpletest/mock_objects.php');
|
||||
require_once('database_connection.php');
|
||||
|
||||
Mock::generate('DatabaseConnection');</strong>
|
||||
</pre>
|
||||
This generates a clone class called
|
||||
<span class="new_code">MockDatabaseConnection</span>.
|
||||
We can now create instances of the new class within
|
||||
our test case...
|
||||
<pre>
|
||||
require_once('simpletest/unit_tester.php');
|
||||
require_once('simpletest/mock_objects.php');
|
||||
require_once('database_connection.php');
|
||||
|
||||
Mock::generate('DatabaseConnection');
|
||||
<strong>
|
||||
class MyTestCase extends UnitTestCase {
|
||||
|
||||
function testSomething() {
|
||||
$connection = &new MockDatabaseConnection();
|
||||
}
|
||||
}</strong>
|
||||
</pre>
|
||||
Unlike the generated stubs the mock constructor needs a reference
|
||||
to the test case so that it can dispatch passes and failures while
|
||||
checking its expectations.
|
||||
This means that mock objects can only be used within test cases.
|
||||
Despite this their extra power means that stubs are hardly ever used
|
||||
if mocks are available.
|
||||
</p>
|
||||
<p>
|
||||
<a class="target" name="stub"><h2>Mocks as actors</h2></a>
|
||||
</p>
|
||||
<p>
|
||||
The mock version of a class has all the methods of the original,
|
||||
so that operations like
|
||||
<span class="new_code">$connection->query()</span> are still
|
||||
legal.
|
||||
The return value will be <span class="new_code">null</span>,
|
||||
but we can change that with...
|
||||
<pre>
|
||||
<strong>$connection->setReturnValue('query', 37)</strong>
|
||||
</pre>
|
||||
Now every time we call
|
||||
<span class="new_code">$connection->query()</span> we get
|
||||
the result of 37.
|
||||
We can set the return value to anything, say a hash of
|
||||
imaginary database results or a list of persistent objects.
|
||||
Parameters are irrelevant here, we always get the same
|
||||
values back each time once they have been set up this way.
|
||||
That may not sound like a convincing replica of a
|
||||
database connection, but for the half a dozen lines of
|
||||
a test method it is usually all you need.
|
||||
</p>
|
||||
<p>
|
||||
We can also add extra methods to the mock when generating it
|
||||
and choose our own class name...
|
||||
<pre>
|
||||
<strong>Mock::generate('DatabaseConnection', 'MyMockDatabaseConnection', array('setOptions'));</strong>
|
||||
</pre>
|
||||
Here the mock will behave as if the <span class="new_code">setOptions()</span>
|
||||
existed in the original class.
|
||||
This is handy if a class has used the PHP <span class="new_code">overload()</span>
|
||||
mechanism to add dynamic methods.
|
||||
You can create a special mock to simulate this situation.
|
||||
</p>
|
||||
<p>
|
||||
Things aren't always that simple though.
|
||||
One common problem is iterators, where constantly returning
|
||||
the same value could cause an endless loop in the object
|
||||
being tested.
|
||||
For these we need to set up sequences of values.
|
||||
Let's say we have a simple iterator that looks like this...
|
||||
<pre>
|
||||
class Iterator {
|
||||
function Iterator() {
|
||||
}
|
||||
|
||||
function next() {
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
This is about the simplest iterator you could have.
|
||||
Assuming that this iterator only returns text until it
|
||||
reaches the end, when it returns false, we can simulate it
|
||||
with...
|
||||
<pre>
|
||||
Mock::generate('Iterator');
|
||||
|
||||
class IteratorTest extends UnitTestCase() {
|
||||
|
||||
function testASequence() {<strong>
|
||||
$iterator = &new MockIterator();
|
||||
$iterator->setReturnValue('next', false);
|
||||
$iterator->setReturnValueAt(0, 'next', 'First string');
|
||||
$iterator->setReturnValueAt(1, 'next', 'Second string');</strong>
|
||||
...
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
When <span class="new_code">next()</span> is called on the
|
||||
mock iterator it will first return "First string",
|
||||
on the second call "Second string" will be returned
|
||||
and on any other call <span class="new_code">false</span> will
|
||||
be returned.
|
||||
The sequenced return values take precedence over the constant
|
||||
return value.
|
||||
The constant one is a kind of default if you like.
|
||||
</p>
|
||||
<p>
|
||||
Another tricky situation is an overloaded
|
||||
<span class="new_code">get()</span> operation.
|
||||
An example of this is an information holder with name/value pairs.
|
||||
Say we have a configuration class like...
|
||||
<pre>
|
||||
class Configuration {
|
||||
function Configuration() {
|
||||
}
|
||||
|
||||
function getValue($key) {
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
This is a classic situation for using mock objects as
|
||||
actual configuration will vary from machine to machine,
|
||||
hardly helping the reliability of our tests if we use it
|
||||
directly.
|
||||
The problem though is that all the data comes through the
|
||||
<span class="new_code">getValue()</span> method and yet
|
||||
we want different results for different keys.
|
||||
Luckily the mocks have a filter system...
|
||||
<pre>
|
||||
<strong>$config = &new MockConfiguration();
|
||||
$config->setReturnValue('getValue', 'primary', array('db_host'));
|
||||
$config->setReturnValue('getValue', 'admin', array('db_user'));
|
||||
$config->setReturnValue('getValue', 'secret', array('db_password'));</strong>
|
||||
</pre>
|
||||
The extra parameter is a list of arguments to attempt
|
||||
to match.
|
||||
In this case we are trying to match only one argument which
|
||||
is the look up key.
|
||||
Now when the mock object has the
|
||||
<span class="new_code">getValue()</span> method invoked
|
||||
like this...
|
||||
<pre>
|
||||
$config->getValue('db_user')
|
||||
</pre>
|
||||
...it will return "admin".
|
||||
It finds this by attempting to match the calling arguments
|
||||
to its list of returns one after another until
|
||||
a complete match is found.
|
||||
</p>
|
||||
<p>
|
||||
You can set a default argument argument like so...
|
||||
<pre><strong>
|
||||
$config->setReturnValue('getValue', false, array('*'));</strong>
|
||||
</pre>
|
||||
This is not the same as setting the return value without
|
||||
any argument requirements like this...
|
||||
<pre><strong>
|
||||
$config->setReturnValue('getValue', false);</strong>
|
||||
</pre>
|
||||
In the first case it will accept any single argument,
|
||||
but exactly one is required.
|
||||
In the second case any number of arguments will do and
|
||||
it acts as a catchall after all other matches.
|
||||
Note that if we add further single parameter options after
|
||||
the wildcard in the first case, they will be ignored as the wildcard
|
||||
will match first.
|
||||
With complex parameter lists the ordering could be important
|
||||
or else desired matches could be masked by earlier wildcard
|
||||
ones.
|
||||
Declare the most specific matches first if you are not sure.
|
||||
</p>
|
||||
<p>
|
||||
There are times when you want a specific object to be
|
||||
dished out by the mock rather than a copy.
|
||||
The PHP4 copy semantics force us to use a different method
|
||||
for this.
|
||||
You might be simulating a container for example...
|
||||
<pre>
|
||||
class Thing {
|
||||
}
|
||||
|
||||
class Vector {
|
||||
function Vector() {
|
||||
}
|
||||
|
||||
function get($index) {
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
In this case you can set a reference into the mock's
|
||||
return list...
|
||||
<pre>
|
||||
$thing = &new Thing();<strong>
|
||||
$vector = &new MockVector();
|
||||
$vector->setReturnReference('get', $thing, array(12));</strong>
|
||||
</pre>
|
||||
With this arrangement you know that every time
|
||||
<span class="new_code">$vector->get(12)</span> is
|
||||
called it will return the same
|
||||
<span class="new_code">$thing</span> each time.
|
||||
This is compatible with PHP5 as well.
|
||||
</p>
|
||||
<p>
|
||||
These three factors, timing, parameters and whether to copy,
|
||||
can be combined orthogonally.
|
||||
For example...
|
||||
<pre>
|
||||
$complex = &new MockComplexThing();
|
||||
$stuff = &new Stuff();<strong>
|
||||
$complex->setReturnReferenceAt(3, 'get', $stuff, array('*', 1));</strong>
|
||||
</pre>
|
||||
This will return the <span class="new_code">$stuff</span> only on the third
|
||||
call and only if two parameters were set the second of
|
||||
which must be the integer 1.
|
||||
That should cover most simple prototyping situations.
|
||||
</p>
|
||||
<p>
|
||||
A final tricky case is one object creating another, known
|
||||
as a factory pattern.
|
||||
Suppose that on a successful query to our imaginary
|
||||
database, a result set is returned as an iterator with
|
||||
each call to <span class="new_code">next()</span> giving
|
||||
one row until false.
|
||||
This sounds like a simulation nightmare, but in fact it can all
|
||||
be mocked using the mechanics above.
|
||||
</p>
|
||||
<p>
|
||||
Here's how...
|
||||
<pre>
|
||||
Mock::generate('DatabaseConnection');
|
||||
Mock::generate('ResultIterator');
|
||||
|
||||
class DatabaseTest extends UnitTestCase {
|
||||
|
||||
function testUserFinder() {<strong>
|
||||
$result = &new MockResultIterator();
|
||||
$result->setReturnValue('next', false);
|
||||
$result->setReturnValueAt(0, 'next', array(1, 'tom'));
|
||||
$result->setReturnValueAt(1, 'next', array(3, 'dick'));
|
||||
$result->setReturnValueAt(2, 'next', array(6, 'harry'));
|
||||
|
||||
$connection = &new MockDatabaseConnection();
|
||||
$connection->setReturnValue('query', false);
|
||||
$connection->setReturnReference(
|
||||
'query',
|
||||
$result,
|
||||
array('select id, name from users'));</strong>
|
||||
|
||||
$finder = &new UserFinder($connection);
|
||||
$this->assertIdentical(
|
||||
$finder->findNames(),
|
||||
array('tom', 'dick', 'harry'));
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
Now only if our
|
||||
<span class="new_code">$connection</span> is called with the correct
|
||||
<span class="new_code">query()</span> will the
|
||||
<span class="new_code">$result</span> be returned that is
|
||||
itself exhausted after the third call to <span class="new_code">next()</span>.
|
||||
This should be enough
|
||||
information for our <span class="new_code">UserFinder</span> class,
|
||||
the class actually
|
||||
being tested here, to come up with goods.
|
||||
A very precise test and not a real database in sight.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="expectations"><h2>Mocks as critics</h2></a></p>
|
||||
<p>
|
||||
Although the server stubs approach insulates your tests from
|
||||
real world disruption, it is only half the benefit.
|
||||
You can have the class under test receiving the required
|
||||
messages, but is your new class sending correct ones?
|
||||
Testing this can get messy without a mock objects library.
|
||||
</p>
|
||||
<p>
|
||||
By way of example, suppose we have a
|
||||
<span class="new_code">SessionPool</span> class that we
|
||||
want to add logging to.
|
||||
Rather than grow the original class into something more
|
||||
complicated, we want to add this behaviour with a decorator (GOF).
|
||||
The <span class="new_code">SessionPool</span> code currently looks
|
||||
like this...
|
||||
<pre>
|
||||
<strong>class SessionPool {
|
||||
function SessionPool() {
|
||||
...
|
||||
}
|
||||
|
||||
function &findSession($cookie) {
|
||||
...
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
class Session {
|
||||
...
|
||||
}</strong>
|
||||
</pre>
|
||||
While our logging code looks like this...
|
||||
<pre>
|
||||
<strong>
|
||||
class Log {
|
||||
function Log() {
|
||||
...
|
||||
}
|
||||
|
||||
function message() {
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
class LoggingSessionPool {
|
||||
function LoggingSessionPool(&$session_pool, &$log) {
|
||||
...
|
||||
}
|
||||
|
||||
function &findSession($cookie) {
|
||||
...
|
||||
}
|
||||
...
|
||||
}</strong>
|
||||
</pre>
|
||||
Out of all of this, the only class we want to test here
|
||||
is the <span class="new_code">LoggingSessionPool</span>.
|
||||
In particular we would like to check that the
|
||||
<span class="new_code">findSession()</span> method is
|
||||
called with the correct session ID in the cookie and that
|
||||
it sent the message "Starting session $cookie"
|
||||
to the logger.
|
||||
</p>
|
||||
<p>
|
||||
Despite the fact that we are testing only a few lines of
|
||||
production code, here is what we would have to do in a
|
||||
conventional test case:
|
||||
<ol>
|
||||
<li>Create a log object.</li>
|
||||
<li>Set a directory to place the log file.</li>
|
||||
<li>Set the directory permissions so we can write the log.</li>
|
||||
<li>Create a <span class="new_code">SessionPool</span> object.</li>
|
||||
<li>Hand start a session, which probably does lot's of things.</li>
|
||||
<li>Invoke <span class="new_code">findSession()</span>.</li>
|
||||
<li>Read the new Session ID (hope there is an accessor!).</li>
|
||||
<li>Raise a test assertion to confirm that the ID matches the cookie.</li>
|
||||
<li>Read the last line of the log file.</li>
|
||||
<li>Pattern match out the extra logging timestamps, etc.</li>
|
||||
<li>Assert that the session message is contained in the text.</li>
|
||||
</ol>
|
||||
It is hardly surprising that developers hate writing tests
|
||||
when they are this much drudgery.
|
||||
To make things worse, every time the logging format changes or
|
||||
the method of creating new sessions changes, we have to rewrite
|
||||
parts of this test even though this test does not officially
|
||||
test those parts of the system.
|
||||
We are creating headaches for the writers of these other classes.
|
||||
</p>
|
||||
<p>
|
||||
Instead, here is the complete test method using mock object magic...
|
||||
<pre>
|
||||
Mock::generate('Session');
|
||||
Mock::generate('SessionPool');
|
||||
Mock::generate('Log');
|
||||
|
||||
class LoggingSessionPoolTest extends UnitTestCase {
|
||||
...
|
||||
function testFindSessionLogging() {<strong>
|
||||
$session = &new MockSession();
|
||||
$pool = &new MockSessionPool();
|
||||
$pool->setReturnReference('findSession', $session);
|
||||
$pool->expectOnce('findSession', array('abc'));
|
||||
|
||||
$log = &new MockLog();
|
||||
$log->expectOnce('message', array('Starting session abc'));
|
||||
|
||||
$logging_pool = &new LoggingSessionPool($pool, $log);
|
||||
$this->assertReference($logging_pool->findSession('abc'), $session);</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
We start by creating a dummy session.
|
||||
We don't have to be too fussy about this as the check
|
||||
for which session we want is done elsewhere.
|
||||
We only need to check that it was the same one that came
|
||||
from the session pool.
|
||||
</p>
|
||||
<p>
|
||||
<span class="new_code">findSession()</span> is a factory
|
||||
method the simulation of which is described <a href="#stub">above</a>.
|
||||
The point of departure comes with the first
|
||||
<span class="new_code">expectOnce()</span> call.
|
||||
This line states that whenever
|
||||
<span class="new_code">findSession()</span> is invoked on the
|
||||
mock, it will test the incoming arguments.
|
||||
If it receives the single argument of a string "abc"
|
||||
then a test pass is sent to the unit tester, otherwise a fail is
|
||||
generated.
|
||||
This was the part where we checked that the right session was asked for.
|
||||
The argument list follows the same format as the one for setting
|
||||
return values.
|
||||
You can have wildcards and sequences and the order of
|
||||
evaluation is the same.
|
||||
</p>
|
||||
<p>
|
||||
We use the same pattern to set up the mock logger.
|
||||
We tell it that it should have
|
||||
<span class="new_code">message()</span> invoked
|
||||
once only with the argument "Starting session abc".
|
||||
By testing the calling arguments, rather than the logger output,
|
||||
we insulate the test from any display changes in the logger.
|
||||
</p>
|
||||
<p>
|
||||
We start to run our tests when we create the new
|
||||
<span class="new_code">LoggingSessionPool</span> and feed
|
||||
it our preset mock objects.
|
||||
Everything is now under our control.
|
||||
</p>
|
||||
<p>
|
||||
This is still quite a bit of test code, but the code is very
|
||||
strict.
|
||||
If it still seems rather daunting there is a lot less of it
|
||||
than if we tried this without mocks and this particular test,
|
||||
interactions rather than output, is always more work to set
|
||||
up.
|
||||
More often you will be testing more complex situations without
|
||||
needing this level or precision.
|
||||
Also some of this can be refactored into a test case
|
||||
<span class="new_code">setUp()</span> method.
|
||||
</p>
|
||||
<p>
|
||||
Here is the full list of expectations you can set on a mock object
|
||||
in <a href="http://www.lastcraft.com/simple_test.php">SimpleTest</a>...
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Expectation</th>
|
||||
<th>Needs <span class="new_code">tally()</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><span class="new_code">expect($method, $args)</span></td>
|
||||
<td style="text-align: center">No</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">expectAt($timing, $method, $args)</span></td>
|
||||
<td style="text-align: center">No</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">expectCallCount($method, $count)</span></td>
|
||||
<td style="text-align: center">Yes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">expectMaximumCallCount($method, $count)</span></td>
|
||||
<td style="text-align: center">No</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">expectMinimumCallCount($method, $count)</span></td>
|
||||
<td style="text-align: center">Yes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">expectNever($method)</span></td>
|
||||
<td style="text-align: center">No</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">expectOnce($method, $args)</span></td>
|
||||
<td style="text-align: center">Yes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">expectAtLeastOnce($method, $args)</span></td>
|
||||
<td style="text-align: center">Yes</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
Where the parameters are...
|
||||
<dl>
|
||||
<dt class="new_code">$method</dt>
|
||||
<dd>The method name, as a string, to apply the condition to.</dd>
|
||||
<dt class="new_code">$args</dt>
|
||||
<dd>
|
||||
The arguments as a list. Wildcards can be included in the same
|
||||
manner as for <span class="new_code">setReturn()</span>.
|
||||
This argument is optional for <span class="new_code">expectOnce()</span>
|
||||
and <span class="new_code">expectAtLeastOnce()</span>.
|
||||
</dd>
|
||||
<dt class="new_code">$timing</dt>
|
||||
<dd>
|
||||
The only point in time to test the condition.
|
||||
The first call starts at zero.
|
||||
</dd>
|
||||
<dt class="new_code">$count</dt>
|
||||
<dd>The number of calls expected.</dd>
|
||||
</dl>
|
||||
The method <span class="new_code">expectMaximumCallCount()</span>
|
||||
is slightly different in that it will only ever generate a failure.
|
||||
It is silent if the limit is never reached.
|
||||
</p>
|
||||
<p>
|
||||
Also if you have juste one call in your test, make sure you're using
|
||||
<span class="new_code">expectOnce</span>.<br>
|
||||
Using <span class="new_code">$mocked->expectAt(0, 'method', 'args);</span>
|
||||
on its own will not be catched :
|
||||
checking the arguments and the overall call count
|
||||
are currently independant.
|
||||
</p>
|
||||
<p>
|
||||
Like the assertions within test cases, all of the expectations
|
||||
can take a message override as an extra parameter.
|
||||
Also the original failure message can be embedded in the output
|
||||
as "%s".
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="approaches"><h2>Other approaches</h2></a></p>
|
||||
<p>
|
||||
There are three approaches to creating mocks including the one
|
||||
that SimpleTest employs.
|
||||
Coding them by hand using a base class, generating them to
|
||||
a file and dynamically generating them on the fly.
|
||||
</p>
|
||||
<p>
|
||||
Mock objects generated with <a href="simple_test.html">SimpleTest</a>
|
||||
are dynamic.
|
||||
They are created at run time in memory, using
|
||||
<span class="new_code">eval()</span>, rather than written
|
||||
out to a file.
|
||||
This makes the mocks easy to create, a one liner,
|
||||
especially compared with hand
|
||||
crafting them in a parallel class hierarchy.
|
||||
The problem is that the behaviour is usually set up in the tests
|
||||
themselves.
|
||||
If the original objects change the mock versions
|
||||
that the tests rely on can get out of sync.
|
||||
This can happen with the parallel hierarchy approach as well,
|
||||
but is far more quickly detected.
|
||||
</p>
|
||||
<p>
|
||||
The solution, of course, is to add some real integration
|
||||
tests.
|
||||
You don't need very many and the convenience gained
|
||||
from the mocks more than outweighs the small amount of
|
||||
extra testing.
|
||||
You cannot trust code that was only tested with mocks.
|
||||
</p>
|
||||
<p>
|
||||
If you are still determined to build static libraries of mocks
|
||||
because you want to simulate very specific behaviour, you can
|
||||
achieve the same effect using the SimpleTest class generator.
|
||||
In your library file, say <em>mocks/connection.php</em> for a
|
||||
database connection, create a mock and inherit to override
|
||||
special methods or add presets...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/mock_objects.php');
|
||||
require_once('../classes/connection.php');
|
||||
<strong>
|
||||
Mock::generate('Connection', 'BasicMockConnection');
|
||||
class MockConnection extends BasicMockConnection {
|
||||
function MockConnection() {
|
||||
$this->BasicMockConnection();
|
||||
$this->setReturn('query', false);
|
||||
}
|
||||
}</strong>
|
||||
?>
|
||||
</pre>
|
||||
The generate call tells the class generator to create
|
||||
a class called <span class="new_code">BasicMockConnection</span>
|
||||
rather than the usual <span class="new_code">MockConnection</span>.
|
||||
We then inherit from this to get our version of
|
||||
<span class="new_code">MockConnection</span>.
|
||||
By intercepting in this way we can add behaviour, here setting
|
||||
the default value of <span class="new_code">query()</span> to be false.
|
||||
By using the default name we make sure that the mock class
|
||||
generator will not recreate a different one when invoked elsewhere in the
|
||||
tests.
|
||||
It never creates a class if it already exists.
|
||||
As long as the above file is included first then all tests
|
||||
that generated <span class="new_code">MockConnection</span> should
|
||||
now be using our one instead.
|
||||
If we don't get the order right and the mock library
|
||||
creates one first then the class creation will simply fail.
|
||||
</p>
|
||||
<p>
|
||||
Use this trick if you find you have a lot of common mock behaviour
|
||||
or you are getting frequent integration problems at later
|
||||
stages of testing.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
References and related information...
|
||||
<ul>
|
||||
<li>
|
||||
The original
|
||||
<a href="http://www.mockobjects.com/">Mock objects</a> paper.
|
||||
</li>
|
||||
<li>
|
||||
SimpleTest project page on <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
|
||||
</li>
|
||||
<li>
|
||||
SimpleTest home page on <a href="http://www.lastcraft.com/simple_test.php">LastCraft</a>.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<span class="chosen">Mock objects</span>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<div class="copyright">
|
||||
Copyright<br>Marcus Baker 2006
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
486
simpletest/docs/en/overview.html
Normal file
486
simpletest/docs/en/overview.html
Normal file
@@ -0,0 +1,486 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>
|
||||
Overview and feature list for the SimpleTest PHP unit tester and web tester
|
||||
</title>
|
||||
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
|
||||
</head>
|
||||
<body>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<span class="chosen">Overview</span>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<h1>Overview of SimpleTest</h1>
|
||||
This page...
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#summary">Quick summary</a>
|
||||
of the SimpleTest tool for PHP.
|
||||
</li>
|
||||
<li>
|
||||
<a href="#features">List of features</a>,
|
||||
both current ones and those planned.
|
||||
</li>
|
||||
<li>
|
||||
There are plenty of <a href="#resources">unit testing resources</a>
|
||||
on the web.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="content">
|
||||
<p><a class="target" name="summary"><h2>What is SimpleTest?</h2></a></p>
|
||||
<p>
|
||||
The heart of SimpleTest is a testing framework built around
|
||||
test case classes.
|
||||
These are written as extensions of base test case classes,
|
||||
each extended with methods that actually contain test code.
|
||||
Top level test scripts then invoke the <span class="new_code">run()</span>
|
||||
methods on every one of these test cases in order.
|
||||
Each test method is written to invoke various assertions that
|
||||
the developer expects to be true such as
|
||||
<span class="new_code">assertEqual()</span>.
|
||||
If the expectation is correct, then a successful result is dispatched to the
|
||||
observing test reporter, but any failure triggers an alert
|
||||
and a description of the mismatch.
|
||||
</p>
|
||||
<p>
|
||||
A <a href="unit_test_documentation.html">test case</a> looks like this...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/autorun.php');
|
||||
|
||||
class <strong>MyTestCase</strong> extends UnitTestCase {
|
||||
<strong>
|
||||
function testCreatedLogFile() {
|
||||
$log = &new Log('my.log');
|
||||
$log->message('Hello');
|
||||
$this->assertTrue(file_exists('my.log'));
|
||||
}</strong>
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
</p>
|
||||
<p>
|
||||
These tools are designed for the developer.
|
||||
Tests are written in the PHP language itself more or less
|
||||
as the application itself is built.
|
||||
The advantage of using PHP itself as the testing language is that
|
||||
there are no new languages to learn, testing can start straight away,
|
||||
and the developer can test any part of the code.
|
||||
Basically, all parts that can be accessed by the application code can also be
|
||||
accessed by the test code, if they are in the same programming language.
|
||||
</p>
|
||||
<p>
|
||||
The simplest type of test case is the
|
||||
<a href="unit_tester_documentation.html">UnitTestCase</a>.
|
||||
This class of test case includes standard tests for equality,
|
||||
references and pattern matching.
|
||||
All these test the typical expectations of what you would
|
||||
expect the result of a function or method to be.
|
||||
This is by far the most common type of test in the daily
|
||||
routine of development, making up about 95% of test cases.
|
||||
</p>
|
||||
<p>
|
||||
The top level task of a web application though is not to
|
||||
produce correct output from its methods and objects, but
|
||||
to generate web pages.
|
||||
The <a href="web_tester_documentation.html">WebTestCase</a> class tests web
|
||||
pages.
|
||||
It simulates a web browser requesting a page, complete with
|
||||
cookies, proxies, secure connections, authentication, forms, frames and most
|
||||
navigation elements.
|
||||
With this type of test case, the developer can assert that
|
||||
information is present in the page and that forms and
|
||||
sessions are handled correctly.
|
||||
</p>
|
||||
<p>
|
||||
A <a href="web_tester_documentation.html">WebTestCase</a> looks like this...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/autorun.php');
|
||||
require_once('simpletest/web_tester.php');
|
||||
|
||||
class <strong>MySiteTest</strong> extends WebTestCase {
|
||||
<strong>
|
||||
function testHomePage() {
|
||||
$this->get('http://www.my-site.com/index.php');
|
||||
$this->assertTitle('My Home Page');
|
||||
$this->clickLink('Contact');
|
||||
$this->assertTitle('Contact me');
|
||||
$this->assertPattern('/Email me at/');
|
||||
}</strong>
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="features"><h2>Feature list</h2></a></p>
|
||||
<p>
|
||||
The following is a very rough outline of past and future features
|
||||
and their expected point of release.
|
||||
I am afraid it is liable to change without warning, as meeting the
|
||||
milestones rather depends on time available.
|
||||
Green stuff has been coded, but not necessarily released yet.
|
||||
If you have a pressing need for a green but unreleased feature
|
||||
then you should check-out the code from Sourceforge SVN directly.
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Feature</th>
|
||||
<th>Description</th>
|
||||
<th>Release</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Unit test case</td>
|
||||
<td>Core test case class and assertions</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Html display</td>
|
||||
<td>Simplest possible display</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Autoloading of test cases</td>
|
||||
<td>
|
||||
Reading a file with test cases and loading them into a
|
||||
group test automatically
|
||||
</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Mock objects</td>
|
||||
<td>
|
||||
Objects capable of simulating other objects removing
|
||||
test dependencies
|
||||
</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Web test case</td>
|
||||
<td>Allows link following and title tag matching</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Partial mocks</td>
|
||||
<td>
|
||||
Mocking parts of a class for testing less than a class
|
||||
or for complex simulations
|
||||
</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Web cookie handling</td>
|
||||
<td>Correct handling of cookies when fetching pages</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Following redirects</td>
|
||||
<td>Page fetching automatically follows 300 redirects</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Form parsing</td>
|
||||
<td>Ability to submit simple forms and read default form values</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Command line interface</td>
|
||||
<td>Test display without the need of a web browser</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Exposure of expectation classes</td>
|
||||
<td>Can create precise tests with mocks as well as test cases</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>XML output and parsing</td>
|
||||
<td>
|
||||
Allows multi host testing and the integration of acceptance
|
||||
testing extensions
|
||||
</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Browser component</td>
|
||||
<td>
|
||||
Exposure of lower level web browser interface for more
|
||||
detailed test cases
|
||||
</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HTTP authentication</td>
|
||||
<td>
|
||||
Fetching protected web pages with basic authentication
|
||||
only
|
||||
</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SSL support</td>
|
||||
<td>Can connect to https: pages</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Proxy support</td>
|
||||
<td>Can connect via. common proxies</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Frames support</td>
|
||||
<td>Handling of frames in web test cases</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>File upload testing</td>
|
||||
<td>Can simulate the input type file tag</td>
|
||||
<td style="color: green;">1.0.1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Mocking interfaces</td>
|
||||
<td>
|
||||
Can generate mock objects to interfaces as well as classes
|
||||
and class interfaces are carried for type hints
|
||||
</td>
|
||||
<td style="color: green;">1.0.1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Testing exceptions</td>
|
||||
<td>Similar to testing PHP errors</td>
|
||||
<td style="color: green;">1.0.1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HTML label support</td>
|
||||
<td>Can access all controls using the visual label</td>
|
||||
<td style="color: green;">1.0.1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Base tag support</td>
|
||||
<td>Respects page base tag when clicking</td>
|
||||
<td style="color: green;">1.0.1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>PHP 5 E_STRICT compliant</td>
|
||||
<td>PHP 5 only version that works with the E_STRICT error level</td>
|
||||
<td style="color: red;">1.1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>BDD style fixtures</td>
|
||||
<td>Can import fixtures using a mixin like given() method</td>
|
||||
<td style="color: red;">1.5</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Reporting machinery enhancements</td>
|
||||
<td>Improved message passing for better cooperation with IDEs</td>
|
||||
<td style="color: red;">1.5</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Fluent mock interface</td>
|
||||
<td>More flexible and concise mock objects</td>
|
||||
<td style="color: red;">1.6</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Localisation</td>
|
||||
<td>Messages abstracted and code generated</td>
|
||||
<td style="color: red;">1.6</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CSS selectors</td>
|
||||
<td>HTML content can be examined using CSS selectors</td>
|
||||
<td style="color: red;">1.7</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HTML table assertions</td>
|
||||
<td>Can match HTML or table elements to expectations</td>
|
||||
<td style="color: red;">1.7</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Unified acceptance testing model</td>
|
||||
<td>Content searchable through selectors combined with expectations</td>
|
||||
<td style="color: red;">1.7</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>DatabaseTestCase</td>
|
||||
<td>SQL selectors and DB drivers</td>
|
||||
<td style="color: red;">1.7</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>IFrame support</td>
|
||||
<td>Reads IFrame content that can be refreshed</td>
|
||||
<td style="color: red;">1.8</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Alternate HTML parsers</td>
|
||||
<td>Can detect compiled parsers for performance improvements</td>
|
||||
<td style="color: red;">1.8</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Integrated Selenium support</td>
|
||||
<td>Easy to use built in Selenium driver and tutorial</td>
|
||||
<td style="color: red;">1.9</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Code coverage</td>
|
||||
<td>Reports using the bundled tool when using XDebug</td>
|
||||
<td style="color: red;">1.9</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Deprecation of old methods</td>
|
||||
<td>Simpler interface for SimpleTest2</td>
|
||||
<td style="color: red;">2.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Javascript suport</td>
|
||||
<td>Use of PECL module to add Javascript to the native browser</td>
|
||||
<td style="color: red;">3.0</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
PHP5 migraton will start straight after the version 1.0.1 series,
|
||||
whereupon only PHP 5.1+ will be supported.
|
||||
SimpleTest is currently compatible with PHP 5, but will not
|
||||
make use of all of the new features until version 1.1.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="resources"><h2>Web resources for testing</h2></a></p>
|
||||
<p>
|
||||
Process is at least as important as tools.
|
||||
The type of process that makes the heaviest use of a developer's
|
||||
testing tool is of course
|
||||
<a href="http://www.extremeprogramming.org/">Extreme Programming</a>.
|
||||
This is one of the
|
||||
<a href="http://www.agilealliance.com/articles/index">Agile Methodologies</a>
|
||||
which combine various practices to "flatten the cost curve" of software development.
|
||||
More extreme still is <a href="http://www.testdriven.com/modules/news/">Test Driven Development</a>,
|
||||
where you very strictly adhere to the rule of no coding until you have a test.
|
||||
If you're more of a planner, or believe that experience trumps evolution,
|
||||
you may prefer the
|
||||
<a href="http://www.therationaledge.com/content/dec_01/f_spiritOfTheRUP_pk.html">RUP</a> approach.
|
||||
I haven't tried it, but even I can see that you will need test tools (see figure 9).
|
||||
</p>
|
||||
<p>
|
||||
Most unit testers clone <a href="http://www.junit.org/">JUnit</a> to some degree,
|
||||
as far as the interface at least. There is a wealth of information on the
|
||||
JUnit site including the
|
||||
<a href="http://junit.sourceforge.net/doc/faq/faq.htm">FAQ</a>
|
||||
which contains plenty of general advice on testing.
|
||||
Once you get bitten by the bug you will certainly appreciate the phrase
|
||||
<a href="http://junit.sourceforge.net/doc/testinfected/testing.htm">test infected</a>
|
||||
coined by Eric Gamma.
|
||||
If you are still reviewing which unit tester to use you can find pretty complete
|
||||
lists from
|
||||
<a href="http://en.wikipedia.org/wiki/List_of_unit_testing_frameworks">Wikipedia</a>,
|
||||
<a href="http://www.testingfaqs.org/t-unit.html">Software testing FAQ</a>,
|
||||
and <a href="http://www.opensourcetesting.org/functional.php">Open source testing</a>.
|
||||
</p>
|
||||
<p>
|
||||
There is still very little material on using mock objects, which is a shame
|
||||
as unit testing without them is a lot more work.
|
||||
The <a href="http://www.sidewize.com/company/mockobjects.pdf">original mock objects paper</a>
|
||||
is very Java focused, but still worth a read.
|
||||
The most authoritive sources are probably
|
||||
<a href="http://mockobjects.com">the original mock objects site</a> and
|
||||
<a href="http://jmock.org/">JMock</a>.
|
||||
Java centric, but tucked away in PDFs they contain some deep knowledge on using mocks from the
|
||||
extended experience of the concept inventors.
|
||||
As a new technology there are plenty of discussions and debate on how to use mocks,
|
||||
often on Wikis such as
|
||||
<a href="http://xpdeveloper.com/cgi-bin/oldwiki.cgi?MockObjects">Extreme Tuesday</a>
|
||||
or <a href="http://www.mockobjects.com/MocksObjectsPaper.html">www.mockobjects.com</a>
|
||||
or <a href="http://c2.com/cgi/wiki?MockObject">the original C2 Wiki</a>.
|
||||
Injecting mocks into a class is the main area of debate for which this
|
||||
<a href="http://www-106.ibm.com/developerworks/java/library/j-mocktest.html">paper on IBM</a>
|
||||
makes a good starting point.
|
||||
</p>
|
||||
<p>
|
||||
There are plenty of web testing tools, but the scriptable ones
|
||||
are mostly are written in Java and
|
||||
tutorials and advice are rather thin on the ground.
|
||||
The only hope is to look at the documentation for
|
||||
<a href="http://httpunit.sourceforge.net/">HTTPUnit</a>,
|
||||
<a href="http://htmlunit.sourceforge.net/">HTMLUnit</a>
|
||||
or <a href="http://jwebunit.sourceforge.net/">JWebUnit</a> and hope for clues.
|
||||
There are some XML driven test frameworks, but again most
|
||||
require Java to run.
|
||||
</p>
|
||||
<p>
|
||||
Most significant is a new generation of tools that run directly in the web browser
|
||||
are now available.
|
||||
These include
|
||||
<a href="http://www.openqa.org/selenium/">Selenium</a> and
|
||||
<a href="http://wtr.rubyforge.org/">Watir</a>.
|
||||
They are non-trivial to set up and slow to run, but can essentially test anything.
|
||||
As SimpleTest does not support JavaScript you would probably
|
||||
have to look at these tools anyway if you have highly dynamic
|
||||
pages.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
References and related information...
|
||||
<ul>
|
||||
<li>
|
||||
<a href="unit_test_documentation.html">Documentation for SimpleTest</a>.
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://www.lastcraft.com/first_test_tutorial.php">How to write PHP test cases</a>
|
||||
is a fairly advanced tutorial.
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://simpletest.org/api/">SimpleTest API</a> from phpdoc.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<span class="chosen">Overview</span>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<div class="copyright">
|
||||
Copyright<br>Marcus Baker 2006
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
445
simpletest/docs/en/partial_mocks_documentation.html
Normal file
445
simpletest/docs/en/partial_mocks_documentation.html
Normal file
@@ -0,0 +1,445 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>SimpleTest for PHP partial mocks documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
|
||||
</head>
|
||||
<body>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<span class="chosen">Partial mocks</span>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<h1>Partial mock objects documentation</h1>
|
||||
This page...
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#inject">The mock injection problem</a>.
|
||||
</li>
|
||||
<li>
|
||||
Moving creation to a <a href="#creation">protected factory</a> method.
|
||||
</li>
|
||||
<li>
|
||||
<a href="#partial">Partial mocks</a> generate subclasses.
|
||||
</li>
|
||||
<li>
|
||||
Partial mocks <a href="#less">test less than a class</a>.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="content">
|
||||
|
||||
<p>
|
||||
A partial mock is simply a pattern to alleviate a specific problem
|
||||
in testing with mock objects,
|
||||
that of getting mock objects into tight corners.
|
||||
It's quite a limited tool and possibly not even a good idea.
|
||||
It is included with SimpleTest because I have found it useful
|
||||
on more than one occasion and has saved a lot of work at that point.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="inject"><h2>The mock injection problem</h2></a></p>
|
||||
<p>
|
||||
When one object uses another it is very simple to just pass a mock
|
||||
version in already set up with its expectations.
|
||||
Things are rather tricker if one object creates another and the
|
||||
creator is the one you want to test.
|
||||
This means that the created object should be mocked, but we can
|
||||
hardly tell our class under test to create a mock instead.
|
||||
The tested class doesn't even know it is running inside a test
|
||||
after all.
|
||||
</p>
|
||||
<p>
|
||||
For example, suppose we are building a telnet client and it
|
||||
needs to create a network socket to pass its messages.
|
||||
The connection method might look something like...
|
||||
<pre>
|
||||
<strong><?php
|
||||
require_once('socket.php');
|
||||
|
||||
class Telnet {
|
||||
...
|
||||
function &connect($ip, $port, $username, $password) {
|
||||
$socket = &new Socket($ip, $port);
|
||||
$socket->read( ... );
|
||||
...
|
||||
}
|
||||
}
|
||||
?></strong>
|
||||
</pre>
|
||||
We would really like to have a mock object version of the socket
|
||||
here, what can we do?
|
||||
</p>
|
||||
<p>
|
||||
The first solution is to pass the socket in as a parameter,
|
||||
forcing the creation up a level.
|
||||
Having the client handle this is actually a very good approach
|
||||
if you can manage it and should lead to factoring the creation from
|
||||
the doing.
|
||||
In fact, this is one way in which testing with mock objects actually
|
||||
forces you to code more tightly focused solutions.
|
||||
They improve your programming.
|
||||
</p>
|
||||
<p>
|
||||
Here this would be...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('socket.php');
|
||||
|
||||
class Telnet {
|
||||
...
|
||||
<strong>function &connect(&$socket, $username, $password) {
|
||||
$socket->read( ... );
|
||||
...
|
||||
}</strong>
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
This means that the test code is typical for a test involving
|
||||
mock objects.
|
||||
<pre>
|
||||
class TelnetTest extends UnitTestCase {
|
||||
...
|
||||
function testConnection() {<strong>
|
||||
$socket = &new MockSocket($this);
|
||||
...
|
||||
$telnet = &new Telnet();
|
||||
$telnet->connect($socket, 'Me', 'Secret');
|
||||
...</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
It is pretty obvious though that one level is all you can go.
|
||||
You would hardly want your top level application creating
|
||||
every low level file, socket and database connection ever
|
||||
needed.
|
||||
It wouldn't know the constructor parameters anyway.
|
||||
</p>
|
||||
<p>
|
||||
The next simplest compromise is to have the created object passed
|
||||
in as an optional parameter...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('socket.php');
|
||||
|
||||
class Telnet {
|
||||
...<strong>
|
||||
function &connect($ip, $port, $username, $password, $socket = false) {
|
||||
if (!$socket) {
|
||||
$socket = &new Socket($ip, $port);
|
||||
}
|
||||
$socket->read( ... );</strong>
|
||||
...
|
||||
return $socket;
|
||||
}
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
For a quick solution this is usually good enough.
|
||||
The test now looks almost the same as if the parameter
|
||||
was formally passed...
|
||||
<pre>
|
||||
class TelnetTest extends UnitTestCase {
|
||||
...
|
||||
function testConnection() {<strong>
|
||||
$socket = &new MockSocket($this);
|
||||
...
|
||||
$telnet = &new Telnet();
|
||||
$telnet->connect('127.0.0.1', 21, 'Me', 'Secret', &$socket);
|
||||
...</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
The problem with this approach is its untidiness.
|
||||
There is test code in the main class and parameters passed
|
||||
in the test case that are never used.
|
||||
This is a quick and dirty approach, but nevertheless effective
|
||||
in most situations.
|
||||
</p>
|
||||
<p>
|
||||
The next method is to pass in a factory object to do the creation...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('socket.php');
|
||||
|
||||
class Telnet {<strong>
|
||||
function Telnet(&$network) {
|
||||
$this->_network = &$network;
|
||||
}</strong>
|
||||
...
|
||||
function &connect($ip, $port, $username, $password) {<strong>
|
||||
$socket = &$this->_network->createSocket($ip, $port);
|
||||
$socket->read( ... );</strong>
|
||||
...
|
||||
return $socket;
|
||||
}
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
This is probably the most highly factored answer as creation
|
||||
is now moved into a small specialist class.
|
||||
The networking factory can now be tested separately, but mocked
|
||||
easily when we are testing the telnet class...
|
||||
<pre>
|
||||
class TelnetTest extends UnitTestCase {
|
||||
...
|
||||
function testConnection() {<strong>
|
||||
$socket = &new MockSocket($this);
|
||||
...
|
||||
$network = &new MockNetwork($this);
|
||||
$network->setReturnReference('createSocket', $socket);
|
||||
$telnet = &new Telnet($network);
|
||||
$telnet->connect('127.0.0.1', 21, 'Me', 'Secret');
|
||||
...</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
The downside is that we are adding a lot more classes to the
|
||||
library.
|
||||
Also we are passing a lot of factories around which will
|
||||
make the code a little less intuitive.
|
||||
The most flexible solution, but the most complex.
|
||||
</p>
|
||||
<p>
|
||||
Is there a middle ground?
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="creation"><h2>Protected factory method</h2></a></p>
|
||||
<p>
|
||||
There is a way we can circumvent the problem without creating
|
||||
any new application classes, but it involves creating a subclass
|
||||
when we do the actual testing.
|
||||
Firstly we move the socket creation into its own method...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('socket.php');
|
||||
|
||||
class Telnet {
|
||||
...
|
||||
function &connect($ip, $port, $username, $password) {<strong>
|
||||
$socket = &$this->_createSocket($ip, $port);</strong>
|
||||
$socket->read( ... );
|
||||
...
|
||||
}<strong>
|
||||
|
||||
function &_createSocket($ip, $port) {
|
||||
return new Socket($ip, $port);
|
||||
}</strong>
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
This is the only change we make to the application code.
|
||||
</p>
|
||||
<p>
|
||||
For the test case we have to create a subclass so that
|
||||
we can intercept the socket creation...
|
||||
<pre>
|
||||
<strong>class TelnetTestVersion extends Telnet {
|
||||
var $_mock;
|
||||
|
||||
function TelnetTestVersion(&$mock) {
|
||||
$this->_mock = &$mock;
|
||||
$this->Telnet();
|
||||
}
|
||||
|
||||
function &_createSocket() {
|
||||
return $this->_mock;
|
||||
}
|
||||
}</strong>
|
||||
</pre>
|
||||
Here I have passed the mock in the constructor, but a
|
||||
setter would have done just as well.
|
||||
Note that the mock was set into the object variable
|
||||
before the constructor was chained.
|
||||
This is necessary in case the constructor calls
|
||||
<span class="new_code">connect()</span>.
|
||||
Otherwise it could get a null value from
|
||||
<span class="new_code">_createSocket()</span>.
|
||||
</p>
|
||||
<p>
|
||||
After the completion of all of this extra work the
|
||||
actual test case is fairly easy.
|
||||
We just test our new class instead...
|
||||
<pre>
|
||||
class TelnetTest extends UnitTestCase {
|
||||
...
|
||||
function testConnection() {<strong>
|
||||
$socket = &new MockSocket($this);
|
||||
...
|
||||
$telnet = &new TelnetTestVersion($socket);
|
||||
$telnet->connect('127.0.0.1', 21, 'Me', 'Secret');
|
||||
...</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
The new class is very simple of course.
|
||||
It just sets up a return value, rather like a mock.
|
||||
It would be nice if it also checked the incoming parameters
|
||||
as well.
|
||||
Just like a mock.
|
||||
It seems we are likely to do this often, can
|
||||
we automate the subclass creation?
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="partial"><h2>A partial mock</h2></a></p>
|
||||
<p>
|
||||
Of course the answer is "yes" or I would have stopped writing
|
||||
this by now!
|
||||
The previous test case was a lot of work, but we can
|
||||
generate the subclass using a similar approach to the mock objects.
|
||||
</p>
|
||||
<p>
|
||||
Here is the partial mock version of the test...
|
||||
<pre>
|
||||
<strong>Mock::generatePartial(
|
||||
'Telnet',
|
||||
'TelnetTestVersion',
|
||||
array('_createSocket'));</strong>
|
||||
|
||||
class TelnetTest extends UnitTestCase {
|
||||
...
|
||||
function testConnection() {<strong>
|
||||
$socket = &new MockSocket($this);
|
||||
...
|
||||
$telnet = &new TelnetTestVersion($this);
|
||||
$telnet->setReturnReference('_createSocket', $socket);
|
||||
$telnet->Telnet();
|
||||
$telnet->connect('127.0.0.1', 21, 'Me', 'Secret');
|
||||
...</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
The partial mock is a subclass of the original with
|
||||
selected methods "knocked out" with test
|
||||
versions.
|
||||
The <span class="new_code">generatePartial()</span> call
|
||||
takes three parameters: the class to be subclassed,
|
||||
the new test class name and a list of methods to mock.
|
||||
</p>
|
||||
<p>
|
||||
Instantiating the resulting objects is slightly tricky.
|
||||
The only constructor parameter of a partial mock is
|
||||
the unit tester reference.
|
||||
As with the normal mock objects this is needed for sending
|
||||
test results in response to checked expectations.
|
||||
</p>
|
||||
<p>
|
||||
The original constructor is not run yet.
|
||||
This is necessary in case the constructor is going to
|
||||
make use of the as yet unset mocked methods.
|
||||
We set any return values at this point and then run the
|
||||
constructor with its normal parameters.
|
||||
This three step construction of "new", followed
|
||||
by setting up the methods, followed by running the constructor
|
||||
proper is what distinguishes the partial mock code.
|
||||
</p>
|
||||
<p>
|
||||
Apart from construction, all of the mocked methods have
|
||||
the same features as mock objects and all of the unmocked
|
||||
methods behave as before.
|
||||
We can set expectations very easily...
|
||||
<pre>
|
||||
class TelnetTest extends UnitTestCase {
|
||||
...
|
||||
function testConnection() {
|
||||
$socket = &new MockSocket($this);
|
||||
...
|
||||
$telnet = &new TelnetTestVersion($this);
|
||||
$telnet->setReturnReference('_createSocket', $socket);<strong>
|
||||
$telnet->expectOnce('_createSocket', array('127.0.0.1', 21));</strong>
|
||||
$telnet->Telnet();
|
||||
$telnet->connect('127.0.0.1', 21, 'Me', 'Secret');
|
||||
...<strong>
|
||||
$telnet->tally();</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="less"><h2>Testing less than a class</h2></a></p>
|
||||
<p>
|
||||
The mocked out methods don't have to be factory methods,
|
||||
they could be any sort of method.
|
||||
In this way partial mocks allow us to take control of any part of
|
||||
a class except the constructor.
|
||||
We could even go as far as to mock every method
|
||||
except one we actually want to test.
|
||||
</p>
|
||||
<p>
|
||||
This last situation is all rather hypothetical, as I haven't
|
||||
tried it.
|
||||
I am open to the possibility, but a little worried that
|
||||
forcing object granularity may be better for the code quality.
|
||||
I personally use partial mocks as a way of overriding creation
|
||||
or for occasional testing of the TemplateMethod pattern.
|
||||
</p>
|
||||
<p>
|
||||
It's all going to come down to the coding standards of your
|
||||
project to decide which mechanism you use.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
References and related information...
|
||||
<ul>
|
||||
<li>
|
||||
SimpleTest project page on <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://simpletest.org/api/">Full API for SimpleTest</a>
|
||||
from the PHPDoc.
|
||||
</li>
|
||||
<li>
|
||||
The protected factory is described in
|
||||
<a href="http://www-106.ibm.com/developerworks/java/library/j-mocktest.html">this paper from IBM</a>.
|
||||
This is the only formal comment I have seen on this problem.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<span class="chosen">Partial mocks</span>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<div class="copyright">
|
||||
Copyright<br>Marcus Baker 2006
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
519
simpletest/docs/en/reporter_documentation.html
Normal file
519
simpletest/docs/en/reporter_documentation.html
Normal file
@@ -0,0 +1,519 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>SimpleTest for PHP test runner and display documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
|
||||
</head>
|
||||
<body>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<span class="chosen">Reporting</span>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<h1>Test reporter documentation</h1>
|
||||
This page...
|
||||
<ul>
|
||||
<li>
|
||||
Displaying <a href="#html">results in HTML</a>
|
||||
</li>
|
||||
<li>
|
||||
Displaying and <a href="#other">reporting results</a>
|
||||
in other formats
|
||||
</li>
|
||||
<li>
|
||||
Using <a href="#cli">SimpleTest from the command line</a>
|
||||
</li>
|
||||
<li>
|
||||
Using <a href="#xml">Using XML</a> for remote testing
|
||||
</li>
|
||||
</ul>
|
||||
<div class="content">
|
||||
|
||||
<p>
|
||||
SimpleTest pretty much follows the MVC pattern
|
||||
(Model-View-Controller).
|
||||
The reporter classes are the view and the model is your
|
||||
test cases and their hiearchy.
|
||||
The controller is mostly hidden from the user of
|
||||
SimpleTest unless you want to change how the test cases
|
||||
are actually run, in which case it is possible to
|
||||
override the runner objects from within the test case.
|
||||
As usual with MVC, the controller is mostly undefined
|
||||
and there are other places to control the test run.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="html"><h2>Reporting results in HTML</h2></a></p>
|
||||
<p>
|
||||
The default test display is minimal in the extreme.
|
||||
It reports success and failure with the conventional red and
|
||||
green bars and shows a breadcrumb trail of test groups
|
||||
for every failed assertion.
|
||||
Here's a fail...
|
||||
<div class="demo">
|
||||
<h1>File test</h1>
|
||||
<span class="fail">Fail</span>: createnewfile->True assertion failed.<br>
|
||||
<div style="padding: 8px; margin-top: 1em; background-color: red; color: white;">1/1 test cases complete.
|
||||
<strong>0</strong> passes, <strong>1</strong> fails and <strong>0</strong> exceptions.</div>
|
||||
</div>
|
||||
And here all tests passed...
|
||||
<div class="demo">
|
||||
<h1>File test</h1>
|
||||
<div style="padding: 8px; margin-top: 1em; background-color: green; color: white;">1/1 test cases complete.
|
||||
<strong>1</strong> passes, <strong>0</strong> fails and <strong>0</strong> exceptions.</div>
|
||||
</div>
|
||||
The good news is that there are several points in the display
|
||||
hiearchy for subclassing.
|
||||
</p>
|
||||
<p>
|
||||
For web page based displays there is the
|
||||
<span class="new_code">HtmlReporter</span> class with the following
|
||||
signature...
|
||||
<pre>
|
||||
class HtmlReporter extends SimpleReporter {
|
||||
public HtmlReporter($encoding) { ... }
|
||||
public makeDry(boolean $is_dry) { ... }
|
||||
public void paintHeader(string $test_name) { ... }
|
||||
public void sendNoCacheHeaders() { ... }
|
||||
public void paintFooter(string $test_name) { ... }
|
||||
public void paintGroupStart(string $test_name, integer $size) { ... }
|
||||
public void paintGroupEnd(string $test_name) { ... }
|
||||
public void paintCaseStart(string $test_name) { ... }
|
||||
public void paintCaseEnd(string $test_name) { ... }
|
||||
public void paintMethodStart(string $test_name) { ... }
|
||||
public void paintMethodEnd(string $test_name) { ... }
|
||||
public void paintFail(string $message) { ... }
|
||||
public void paintPass(string $message) { ... }
|
||||
public void paintError(string $message) { ... }
|
||||
public void paintException(string $message) { ... }
|
||||
public void paintMessage(string $message) { ... }
|
||||
public void paintFormattedMessage(string $message) { ... }
|
||||
protected string _getCss() { ... }
|
||||
public array getTestList() { ... }
|
||||
public integer getPassCount() { ... }
|
||||
public integer getFailCount() { ... }
|
||||
public integer getExceptionCount() { ... }
|
||||
public integer getTestCaseCount() { ... }
|
||||
public integer getTestCaseProgress() { ... }
|
||||
}
|
||||
</pre>
|
||||
Here is what some of these methods mean. First the display methods
|
||||
that you will probably want to override...
|
||||
<ul class="api">
|
||||
<li>
|
||||
<span class="new_code">HtmlReporter(string $encoding)</span><br>
|
||||
is the constructor.
|
||||
Note that the unit test sets up the link to the display
|
||||
rather than the other way around.
|
||||
The display is a mostly passive receiver of test events.
|
||||
This allows easy adaption of the display for other test
|
||||
systems beside unit tests, such as monitoring servers.
|
||||
The encoding is the character encoding you wish to
|
||||
display the test output in.
|
||||
In order to correctly render debug output when
|
||||
using the web tester, this should match the encoding
|
||||
of the site you are trying to test.
|
||||
The available character set strings are described in
|
||||
the PHP <a href="http://www.php.net/manual/en/function.htmlentities.php">html_entities()</a>
|
||||
function.
|
||||
</li>
|
||||
<li>
|
||||
<span class="new_code">void paintHeader(string $test_name)</span><br>
|
||||
is called once at the very start of the test when the first
|
||||
start event arrives.
|
||||
The first start event is usually delivered by the top level group
|
||||
test and so this is where <span class="new_code">$test_name</span>
|
||||
comes from.
|
||||
It paints the page titles, CSS, body tag, etc.
|
||||
It returns nothing (<span class="new_code">void</span>).
|
||||
</li>
|
||||
<li>
|
||||
<span class="new_code">void paintFooter(string $test_name)</span><br>
|
||||
Called at the very end of the test to close any tags opened
|
||||
by the page header.
|
||||
By default it also displays the red/green bar and the final
|
||||
count of results.
|
||||
Actually the end of the test happens when a test end event
|
||||
comes in with the same name as the one that started it all
|
||||
at the same level.
|
||||
The tests nest you see.
|
||||
Closing the last test finishes the display.
|
||||
</li>
|
||||
<li>
|
||||
<span class="new_code">void paintMethodStart(string $test_name)</span><br>
|
||||
is called at the start of each test method.
|
||||
The name normally comes from method name.
|
||||
The other test start events behave the same way except
|
||||
that the group test one tells the reporter how large
|
||||
it is in number of held test cases.
|
||||
This is so that the reporter can display a progress bar
|
||||
as the runner churns through the test cases.
|
||||
</li>
|
||||
<li>
|
||||
<span class="new_code">void paintMethodEnd(string $test_name)</span><br>
|
||||
backs out of the test started with the same name.
|
||||
</li>
|
||||
<li>
|
||||
<span class="new_code">void paintFail(string $message)</span><br>
|
||||
paints a failure.
|
||||
By default it just displays the word fail, a breadcrumbs trail
|
||||
showing the current test nesting and the message issued by
|
||||
the assertion.
|
||||
</li>
|
||||
<li>
|
||||
<span class="new_code">void paintPass(string $message)</span><br>
|
||||
by default does nothing.
|
||||
</li>
|
||||
<li>
|
||||
<span class="new_code">string _getCss()</span><br>
|
||||
Returns the CSS styles as a string for the page header
|
||||
method.
|
||||
Additional styles have to be appended here if you are
|
||||
not overriding the page header.
|
||||
You will want to use this method in an overriden page header
|
||||
if you want to include the original CSS.
|
||||
</li>
|
||||
</ul>
|
||||
There are also some accessors to get information on the current
|
||||
state of the test suite.
|
||||
Use these to enrich the display...
|
||||
<ul class="api">
|
||||
<li>
|
||||
<span class="new_code">array getTestList()</span><br>
|
||||
is the first convenience method for subclasses.
|
||||
Lists the current nesting of the tests as a list
|
||||
of test names.
|
||||
The first, most deeply nested test, is first in the
|
||||
list and the current test method will be last.
|
||||
</li>
|
||||
<li>
|
||||
<span class="new_code">integer getPassCount()</span><br>
|
||||
returns the number of passes chalked up so far.
|
||||
Needed for the display at the end.
|
||||
</li>
|
||||
<li>
|
||||
<span class="new_code">integer getFailCount()</span><br>
|
||||
is likewise the number of fails so far.
|
||||
</li>
|
||||
<li>
|
||||
<span class="new_code">integer getExceptionCount()</span><br>
|
||||
is likewise the number of errors so far.
|
||||
</li>
|
||||
<li>
|
||||
<span class="new_code">integer getTestCaseCount()</span><br>
|
||||
is the total number of test cases in the test run.
|
||||
This includes the grouping tests themselves.
|
||||
</li>
|
||||
<li>
|
||||
<span class="new_code">integer getTestCaseProgress()</span><br>
|
||||
is the number of test cases completed so far.
|
||||
</li>
|
||||
</ul>
|
||||
One simple modification is to get the HtmlReporter to display
|
||||
the passes as well as the failures and errors...
|
||||
<pre>
|
||||
<strong>class ShowPasses extends HtmlReporter {
|
||||
|
||||
function paintPass($message) {
|
||||
parent::paintPass($message);
|
||||
print "&<span class=\"pass\">Pass</span>: ";
|
||||
$breadcrumb = $this->getTestList();
|
||||
array_shift($breadcrumb);
|
||||
print implode("-&gt;", $breadcrumb);
|
||||
print "-&gt;$message<br />\n";
|
||||
}
|
||||
|
||||
function _getCss() {
|
||||
return parent::_getCss() . ' .pass { color: green; }';
|
||||
}
|
||||
}</strong>
|
||||
</pre>
|
||||
</p>
|
||||
<p>
|
||||
One method that was glossed over was the <span class="new_code">makeDry()</span>
|
||||
method.
|
||||
If you run this method, with no parameters, on the reporter
|
||||
before the test suite is run no actual test methods
|
||||
will be called.
|
||||
You will still get the events of entering and leaving the
|
||||
test methods and test cases, but no passes or failures etc,
|
||||
because the test code will not actually be executed.
|
||||
</p>
|
||||
<p>
|
||||
The reason for this is to allow for more sophistcated
|
||||
GUI displays that allow the selection of individual test
|
||||
cases.
|
||||
In order to build a list of possible tests they need a
|
||||
report on the test structure for drawing, say a tree view
|
||||
of the test suite.
|
||||
With a reporter set to dry run that just sends drawing events
|
||||
this is easily accomplished.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="other"><h2>Extending the reporter</h2></a></p>
|
||||
<p>
|
||||
Rather than simply modifying the existing display, you might want to
|
||||
produce a whole new HTML look, or even generate text or XML.
|
||||
Rather than override every method in
|
||||
<span class="new_code">HtmlReporter</span> we can take one
|
||||
step up the class hiearchy to <span class="new_code">SimpleReporter</span>
|
||||
in the <em>simple_test.php</em> source file.
|
||||
</p>
|
||||
<p>
|
||||
A do nothing display, a blank canvas for your own creation, would
|
||||
be...
|
||||
<pre>
|
||||
<strong>require_once('simpletest/simple_test.php');</strong>
|
||||
|
||||
class MyDisplay extends SimpleReporter {<strong>
|
||||
</strong>
|
||||
function paintHeader($test_name) {
|
||||
}
|
||||
|
||||
function paintFooter($test_name) {
|
||||
}
|
||||
|
||||
function paintStart($test_name, $size) {<strong>
|
||||
parent::paintStart($test_name, $size);</strong>
|
||||
}
|
||||
|
||||
function paintEnd($test_name, $size) {<strong>
|
||||
parent::paintEnd($test_name, $size);</strong>
|
||||
}
|
||||
|
||||
function paintPass($message) {<strong>
|
||||
parent::paintPass($message);</strong>
|
||||
}
|
||||
|
||||
function paintFail($message) {<strong>
|
||||
parent::paintFail($message);</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
No output would come from this class until you add it.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="cli"><h2>The command line reporter</h2></a></p>
|
||||
<p>
|
||||
SimpleTest also ships with a minimal command line reporter.
|
||||
The interface mimics JUnit to some extent, but paints the
|
||||
failure messages as they arrive.
|
||||
To use the command line reporter simply substitute it
|
||||
for the HTML version...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/unit_tester.php');
|
||||
require_once('simpletest/reporter.php');
|
||||
|
||||
$test = &new TestSuite('File test');
|
||||
$test->addTestFile('tests/file_test.php');
|
||||
$test->run(<strong>new TextReporter()</strong>);
|
||||
?>
|
||||
</pre>
|
||||
Then invoke the test suite from the command line...
|
||||
<pre class="shell">
|
||||
php file_test.php
|
||||
</pre>
|
||||
You will need the command line version of PHP installed
|
||||
of course.
|
||||
A passing test suite looks like this...
|
||||
<pre class="shell">
|
||||
File test
|
||||
OK
|
||||
Test cases run: 1/1, Failures: 0, Exceptions: 0
|
||||
</pre>
|
||||
A failure triggers a display like this...
|
||||
<pre class="shell">
|
||||
File test
|
||||
1) True assertion failed.
|
||||
in createnewfile
|
||||
FAILURES!!!
|
||||
Test cases run: 1/1, Failures: 1, Exceptions: 0
|
||||
</pre>
|
||||
</p>
|
||||
<p>
|
||||
One of the main reasons for using a command line driven
|
||||
test suite is of using the tester as part of some automated
|
||||
process.
|
||||
To function properly in shell scripts the test script should
|
||||
return a non-zero exit code on failure.
|
||||
If a test suite fails the value <span class="new_code">false</span>
|
||||
is returned from the <span class="new_code">SimpleTest::run()</span>
|
||||
method.
|
||||
We can use that result to exit the script with the desired return
|
||||
code...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/unit_tester.php');
|
||||
require_once('simpletest/reporter.php');
|
||||
|
||||
$test = &new TestSuite('File test');
|
||||
$test->addTestFile('tests/file_test.php');
|
||||
<strong>exit ($test->run(new TextReporter()) ? 0 : 1);</strong>
|
||||
?>
|
||||
</pre>
|
||||
Of course we don't really want to create two test scripts,
|
||||
a command line one and a web browser one, for each test suite.
|
||||
The command line reporter includes a method to sniff out the
|
||||
run time environment...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/unit_tester.php');
|
||||
require_once('simpletest/reporter.php');
|
||||
|
||||
$test = &new TestSuite('File test');
|
||||
$test->addTestFile('tests/file_test.php');
|
||||
<strong>if (TextReporter::inCli()) {</strong>
|
||||
exit ($test->run(new TextReporter()) ? 0 : 1);
|
||||
<strong>}</strong>
|
||||
$test->run(new HtmlReporter());
|
||||
?>
|
||||
</pre>
|
||||
This is the form used within SimpleTest itself.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="xml"><h2>Remote testing</h2></a></p>
|
||||
<p>
|
||||
SimpleTest ships with an <span class="new_code">XmlReporter</span> class
|
||||
used for internal communication.
|
||||
When run the output looks like...
|
||||
<pre class="shell">
|
||||
<?xml version="1.0"?>
|
||||
<run>
|
||||
<group size="4">
|
||||
<name>Remote tests</name>
|
||||
<group size="4">
|
||||
<name>Visual test with 48 passes, 48 fails and 4 exceptions</name>
|
||||
<case>
|
||||
<name>testofunittestcaseoutput</name>
|
||||
<test>
|
||||
<name>testofresults</name>
|
||||
<pass>This assertion passed</pass>
|
||||
<fail>This assertion failed</fail>
|
||||
</test>
|
||||
<test>
|
||||
...
|
||||
</test>
|
||||
</case>
|
||||
</group>
|
||||
</group>
|
||||
</run>
|
||||
</pre>
|
||||
You can make use of this format with the parser
|
||||
supplied as part of SimpleTest itself.
|
||||
This is called <span class="new_code">SimpleTestXmlParser</span> and
|
||||
resides in <em>xml.php</em> within the SimpleTest package...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/xml.php');
|
||||
|
||||
...
|
||||
$parser = &new SimpleTestXmlParser(new HtmlReporter());
|
||||
$parser->parse($test_output);
|
||||
?>
|
||||
</pre>
|
||||
The <span class="new_code">$test_output</span> should be the XML format
|
||||
from the XML reporter, and could come from say a command
|
||||
line run of a test case.
|
||||
The parser sends events to the reporter just like any
|
||||
other test run.
|
||||
There are some odd occasions where this is actually useful.
|
||||
</p>
|
||||
<p>
|
||||
A problem with large test suites is thet they can exhaust
|
||||
the default 8Mb memory limit on a PHP process.
|
||||
By having the test groups output in XML and run in
|
||||
separate processes, the output can be reparsed to
|
||||
aggregate the results into a much smaller footprint top level
|
||||
test.
|
||||
</p>
|
||||
<p>
|
||||
Because the XML output can come from anywhere, this opens
|
||||
up the possibility of aggregating test runs from remote
|
||||
servers.
|
||||
A test case already exists to do this within the SimpleTest
|
||||
framework, but it is currently experimental...
|
||||
<pre>
|
||||
<?php
|
||||
<strong>require_once('../remote.php');</strong>
|
||||
require_once('../reporter.php');
|
||||
|
||||
$test_url = ...;
|
||||
$dry_url = ...;
|
||||
|
||||
$test = &new TestSuite('Remote tests');
|
||||
$test->addTestCase(<strong>new RemoteTestCase($test_url, $dry_url)</strong>);
|
||||
$test->run(new HtmlReporter());
|
||||
?>
|
||||
</pre>
|
||||
The <span class="new_code">RemoteTestCase</span> takes the actual location
|
||||
of the test runner, basically a web page in XML format.
|
||||
It also takes the URL of a reporter set to do a dry run.
|
||||
This is so that progress can be reported upward correctly.
|
||||
The <span class="new_code">RemoteTestCase</span> can be added to test suites
|
||||
just like any other group test.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
References and related information...
|
||||
<ul>
|
||||
<li>
|
||||
SimpleTest project page on <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
|
||||
</li>
|
||||
<li>
|
||||
SimpleTest download page on <a href="http://www.lastcraft.com/simple_test.php">LastCraft</a>.
|
||||
</li>
|
||||
<li>
|
||||
The <a href="http://simpletest.org/api/">developer's API for SimpleTest</a>
|
||||
gives full detail on the classes and assertions available.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<span class="chosen">Reporting</span>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<div class="copyright">
|
||||
Copyright<br>Marcus Baker 2006
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
431
simpletest/docs/en/unit_test_documentation.html
Normal file
431
simpletest/docs/en/unit_test_documentation.html
Normal file
@@ -0,0 +1,431 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>SimpleTest for PHP regression test documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
|
||||
</head>
|
||||
<body>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<span class="chosen">Unit tester</span>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<h1>PHP Unit Test documentation</h1>
|
||||
This page...
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#unit">Unit test cases</a> and basic assertions.
|
||||
</li>
|
||||
<li>
|
||||
<a href="#extending_unit">Extending test cases</a> to
|
||||
customise them for your own project.
|
||||
</li>
|
||||
<li>
|
||||
<a href="#running_unit">Running a single case</a> as
|
||||
a single script.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="content">
|
||||
<p><a class="target" name="unit"><h2>Unit test cases</h2></a></p>
|
||||
<p>
|
||||
The core system is a regression testing framework built around
|
||||
test cases.
|
||||
A sample test case looks like this...
|
||||
<pre>
|
||||
<strong>class FileTestCase extends UnitTestCase {
|
||||
}</strong>
|
||||
</pre>
|
||||
Actual tests are added as methods in the test case whose names
|
||||
by default start with the string "test" and
|
||||
when the test case is invoked all such methods are run in
|
||||
the order that PHP introspection finds them.
|
||||
As many test methods can be added as needed.
|
||||
</p>
|
||||
<p>
|
||||
For example...
|
||||
<pre>
|
||||
require_once('simpletest/autorun.php');
|
||||
require_once('../classes/writer.php');
|
||||
|
||||
class FileTestCase extends UnitTestCase {
|
||||
function FileTestCase() {
|
||||
$this->UnitTestCase('File test');
|
||||
}<strong>
|
||||
|
||||
function setUp() {
|
||||
@unlink('../temp/test.txt');
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
@unlink('../temp/test.txt');
|
||||
}
|
||||
|
||||
function testCreation() {
|
||||
$writer = &new FileWriter('../temp/test.txt');
|
||||
$writer->write('Hello');
|
||||
$this->assertTrue(file_exists('../temp/test.txt'), 'File created');
|
||||
}</strong>
|
||||
}
|
||||
</pre>
|
||||
The constructor is optional and usually omitted.
|
||||
Without a name, the class name is taken as the name of the test case.
|
||||
</p>
|
||||
<p>
|
||||
Our only test method at the moment is <span class="new_code">testCreation()</span>
|
||||
where we check that a file has been created by our
|
||||
<span class="new_code">Writer</span> object.
|
||||
We could have put the <span class="new_code">unlink()</span>
|
||||
code into this method as well, but by placing it in
|
||||
<span class="new_code">setUp()</span> and
|
||||
<span class="new_code">tearDown()</span> we can use it with
|
||||
other test methods that we add.
|
||||
</p>
|
||||
<p>
|
||||
The <span class="new_code">setUp()</span> method is run
|
||||
just before each and every test method.
|
||||
<span class="new_code">tearDown()</span> is run just after
|
||||
each and every test method.
|
||||
</p>
|
||||
<p>
|
||||
You can place some test case set up into the constructor to
|
||||
be run once for all the methods in the test case, but
|
||||
you risk test inteference that way.
|
||||
This way is slightly slower, but it is safer.
|
||||
Note that if you come from a JUnit background this will not
|
||||
be the behaviour you are used to.
|
||||
JUnit surprisingly reinstantiates the test case for each test
|
||||
method to prevent such interference.
|
||||
SimpleTest requires the end user to use <span class="new_code">setUp()</span>, but
|
||||
supplies additional hooks for library writers.
|
||||
</p>
|
||||
<p>
|
||||
The means of reporting test results (see below) are by a
|
||||
visiting display class
|
||||
that is notified by various <span class="new_code">assert...()</span>
|
||||
methods.
|
||||
Here is the full list for the <span class="new_code">UnitTestCase</span>
|
||||
class, the default for SimpleTest...
|
||||
<table><tbody>
|
||||
<tr>
|
||||
<td><span class="new_code">assertTrue($x)</span></td>
|
||||
<td>Fail if $x is false</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertFalse($x)</span></td>
|
||||
<td>Fail if $x is true</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertNull($x)</span></td>
|
||||
<td>Fail if $x is set</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertNotNull($x)</span></td>
|
||||
<td>Fail if $x not set</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertIsA($x, $t)</span></td>
|
||||
<td>Fail if $x is not the class or type $t</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertNotA($x, $t)</span></td>
|
||||
<td>Fail if $x is of the class or type $t</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertEqual($x, $y)</span></td>
|
||||
<td>Fail if $x == $y is false</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertNotEqual($x, $y)</span></td>
|
||||
<td>Fail if $x == $y is true</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertWithinMargin($x, $y, $m)</span></td>
|
||||
<td>Fail if abs($x - $y) < $m is false</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertOutsideMargin($x, $y, $m)</span></td>
|
||||
<td>Fail if abs($x - $y) < $m is true</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertIdentical($x, $y)</span></td>
|
||||
<td>Fail if $x == $y is false or a type mismatch</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertNotIdentical($x, $y)</span></td>
|
||||
<td>Fail if $x == $y is true and types match</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertReference($x, $y)</span></td>
|
||||
<td>Fail unless $x and $y are the same variable</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertClone($x, $y)</span></td>
|
||||
<td>Fail unless $x and $y are identical copies</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertPattern($p, $x)</span></td>
|
||||
<td>Fail unless the regex $p matches $x</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertNoPattern($p, $x)</span></td>
|
||||
<td>Fail if the regex $p matches $x</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">expectError($x)</span></td>
|
||||
<td>Swallows any upcoming matching error</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assert($e)</span></td>
|
||||
<td>Fail on failed <a href="expectation_documentation.html">expectation</a> object $e</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
All assertion methods can take an optional description as a
|
||||
last parameter.
|
||||
This is to label the displayed result with.
|
||||
If omitted a default message is sent instead, which is usually
|
||||
sufficient.
|
||||
This default message can still be embedded in your own message
|
||||
if you include "%s" within the string.
|
||||
All the assertions return true on a pass or false on failure.
|
||||
</p>
|
||||
<p>
|
||||
Some examples...
|
||||
<pre>
|
||||
$variable = null;
|
||||
<strong>$this->assertNull($variable, 'Should be cleared');</strong>
|
||||
</pre>
|
||||
...will pass and normally show no message.
|
||||
If you have
|
||||
<a href="http://www.lastcraft.com/display_subclass_tutorial.php">set up the tester to display passes</a>
|
||||
as well then the message will be displayed as is.
|
||||
<pre>
|
||||
<strong>$this->assertIdentical(0, false, 'Zero is not false [%s]');</strong>
|
||||
</pre>
|
||||
This will fail as it performs a type
|
||||
check, as well as a comparison, between the two values.
|
||||
The "%s" part is replaced by the default
|
||||
error message that would have been shown if we had not
|
||||
supplied our own.
|
||||
<pre>
|
||||
$a = 1;
|
||||
$b = $a;
|
||||
<strong>$this->assertReference($a, $b);</strong>
|
||||
</pre>
|
||||
Will fail as the variable <span class="new_code">$a</span> is a copy of <span class="new_code">$b</span>.
|
||||
<pre>
|
||||
<strong>$this->assertPattern('/hello/i', 'Hello world');</strong>
|
||||
</pre>
|
||||
This will pass as using a case insensitive match the string
|
||||
<span class="new_code">hello</span> is contained in <span class="new_code">Hello world</span>.
|
||||
<pre>
|
||||
<strong>$this->expectError();</strong>
|
||||
trigger_error('Catastrophe');
|
||||
</pre>
|
||||
Here the check catches the "Catastrophe"
|
||||
message without checking the text and passes.
|
||||
This removes the error from the queue.
|
||||
<pre>
|
||||
<strong>$this->expectError('Catastrophe');</strong>
|
||||
trigger_error('Catastrophe');
|
||||
</pre>
|
||||
The next error check tests not only the existence of the error,
|
||||
but also the text which, here matches so another pass.
|
||||
If any unchecked errors are left at the end of a test method then
|
||||
an exception will be reported in the test.
|
||||
</p>
|
||||
<p>
|
||||
Note that SimpleTest cannot catch compile time PHP errors.
|
||||
</p>
|
||||
<p>
|
||||
The test cases also have some convenience methods for debugging
|
||||
code or extending the suite...
|
||||
<table><tbody>
|
||||
<tr>
|
||||
<td><span class="new_code">setUp()</span></td>
|
||||
<td>Runs this before each test method</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">tearDown()</span></td>
|
||||
<td>Runs this after each test method</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">pass()</span></td>
|
||||
<td>Sends a test pass</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">fail()</span></td>
|
||||
<td>Sends a test failure</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">error()</span></td>
|
||||
<td>Sends an exception event</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">signal($type, $payload)</span></td>
|
||||
<td>Sends a user defined message to the test reporter</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">dump($var)</span></td>
|
||||
<td>Does a formatted <span class="new_code">print_r()</span> for quick and dirty debugging</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="extending_unit"><h2>Extending test cases</h2></a></p>
|
||||
<p>
|
||||
Of course additional test methods can be added to create
|
||||
specific types of test case, so as to extend framework...
|
||||
<pre>
|
||||
require_once('simpletest/autorun.php');
|
||||
<strong>
|
||||
class FileTester extends UnitTestCase {
|
||||
function FileTester($name = false) {
|
||||
$this->UnitTestCase($name);
|
||||
}
|
||||
|
||||
function assertFileExists($filename, $message = '%s') {
|
||||
$this->assertTrue(
|
||||
file_exists($filename),
|
||||
sprintf($message, 'File [$filename] existence check'));
|
||||
}</strong>
|
||||
}
|
||||
</pre>
|
||||
Here the SimpleTest library is held in a folder called
|
||||
| < | ||||