[Testilence] PHP5的免费单元测试库
Unit testing library for PHP 5Author: Roman Neuhauser
Contact: neuhauser@sigpipe.cz
Copyright: This document has been placed in the public domain.
Id: README.rest 238 2006-10-11 15:48:55Z roman
HeadURL: $HeadURL:https://svn.sigpipe.cz/r/trunk/testilence/README.rest $
Contents
1 Introduction
2 Prerequisities
3 Downloads
3.1 Repository
3.2 Official tarballs
4 Build and Installation
4.1 From a package
4.2 From the source
4.2.1 Wash and Go instructions
4.2.2 Detailed description
4.2.2.1 Running the test suite
5 Reference
5.1 Overview
5.2 Command line interface
5.3 Library
5.3.1 Test Results
5.3.2 Tence_RunnableTest
5.3.3 Tence_TestCollection
5.3.4 Tence_TestMethod
5.3.5 Tence_TestCase
5.3.5.1 Test Methods
5.3.5.2 setUp() and tearDown()
5.3.5.3 Assertion Methods
5.3.6 Tence_TestSuite
6 Examples
7 Releases
7.1 Release types
7.2 Release versioning
8 Test suite
1 Introduction
Testilence is an open-source unit testing library written in PHP 5 for programs written in PHP 5. It is published under the MIT license which means you are free and welcome to use it in any way. Although it is similar to other JUnit -inspired unit testing libraries, Testilence is written with emphasis on what's useful, not what's usual. That said, most clever ways of doing useful things for unit test authors and users have already been discovered. Testilence combines original ideas with the best features found in different unit-testing toolkits into a coherent set.
The testing part roughly follows the canon , comprising an abstract test case class with a number of assertFoo() methods, and a test suite class which allows arbitrary nesting of cases and suites, thanks to the Composite organization. Testilence differs in that you only get to have one assertion per test method (this is a Good Thing).
Testilence is an essential ingredient in BEER .
2 Prerequisities
PHP >= 5.1.3
BSD, Linux, or UNIX
3 Downloads
3.1 Repository
The latest source get be checked out from the Subversion repository , which is also available for anonymous browsing using the viewvc interface .
3.2 Official tarballs
Testilence source releases are distributed as bzip2ed tarballs created with either Tim Kientzle's bsdtar or GNU tar, Julian Seward's bzip2 in all cases. Your operating system should include compatible extraction tools.
All Testilence releases can be found in the download directory .
4 Build and Installation
4.1 From a package
The most comfortable way to install Testilence is using your operating system's packaging system. Ask your OS vendor for a package of the latest Testilence release.
The source distribution includes a FreeBSD port skeleton.
4.2 From the source
4.2.1 Wash and Go instructions
% url=http://codex.sigpipe.cz/testilence/download/testilence-x.y.x.tar.gz
% fetch -o - $url | tar -xzf -
% cd testilence-x.y.z
% make check install
4.2.2 Detailed description
The build and installation process can be configured using a number of variables. Use the help target to display builtin help.
% make help
these targets display help for individual topics:
help-targets:help for targets
help-var: help for variables
%
Functionality not described in the builtin help should be considered implementation details. If the documented features don't support all your configuration needs, please do send a feature request.
Testilence is tested on FreeBSD 6 with the system make (should work on any BSD) and GNU make.
4.2.2.1 Running the test suite
Testilence's own test suite can be exercised with or without installed Testilence by running make check or make maint-check in the source directory. This will run the tests using tence found in that directory (will be built if necessary). The former target excludes tests that use filesystem or other external resources, because a bug in Testilence might prevent proper cleanup or (although unlikely) delete your files.
The test suite is described in more detail further below.
5 Reference
5.1 Overview
Testilence consists of a library and a command line test runner. The library is perfectly usable without the tool, tence has require "Testilence.php"; within the first ten lines (to spare test authors from having to reproduce this code over and over in their files with tests).
5.2 Command line interface
tence, the command line frontend, is documented in the accompanying man page.
5.3 Library
The library code is organized into six circuits (with some overlaps): testing, organization, execution, evaluation, reporting, and presentation.
The testing circuit contains these important classes: Tence_TestCase, Tence_Assertion (there's more but those are private parts).
Organization comprises Tence_TestSuite and Tence_TestCase classes.
Execution includes Tence_RunnableTest, Tence_TestCollection interfaces, and the Tence_TestCaseClass class.
The evaluation, reporting, and presentation circuits are still too raw for documentation.
Tence_TestCase is an abstract base class you're welcome to use as the parent of your test cases. However, any class that implements Tence_RunnableTest (and optionally Tence_TestCollection) will do. Tence_TestCase provides a handful of assertion methods and provides a very concise interface for exception testing.
Tence_TestCaseClass helps keep the number of non-assertion methods in Tence_TestCase as low as possible, a sort of proxy for Tence_TestCase, identifies test methods in the test case instance.
Do not define any constructors or destructors for Tence_TestCase subclasses. Test cases must be default-constructible and any constructor or destructor methods are discouraged.
Tence_Assertion transports details of test method execution from the case into outside world. Instances of this class are virtually invisible to normal Testilence users, the class is mentioned here to prevent confusion later as it appears a lot in the Tence_TestCase's synopsis.
5.3.1 Test Results
Possible test results are:
success
failure
exception
defect
A "test" consists of calls to setUp(), the test method, and tearDown(). All three are considered during evaluation of the test, the formula is:
if setUp() or tearDown() threw, the test was defective, else
if the test method threw, and it was an expected exception (see setExpectedException()), the test was successful, else
if the test method threw, but a different exception class was expected, the test failed, else
if the test method threw and no exception was expected, the result is exception, else
if an exception was expected but not thrown, the test failed, else
if the test method returned a Tence_Assertion, that object defines whether the test was a success or a failure, else
the test was defective
5.3.2 Tence_RunnableTest
Interface through which the tests are run.
void Tence_RunnableTest::run(Tence_Reporter $reporter)
5.3.3 Tence_TestCollection
Interface for access to components of a composite test.
Tence_RunnableTest[] Tence_TestCollection::getTests()
5.3.4 Tence_TestMethod
implements: Tence_RunnableTest
Tence_TestMethod::__construct(Tence_TestCaseClass $tcc, ReflectionMethod $rm)
$rm must contain a method of the class wrapped $tcc.
void Tence_TestMethod::run(Tence_Reporter $reporter)
Constructs a new instance using $tcc and calls the method passed in $rm. Result of the invocation is passed to $reporter.
5.3.5 Tence_TestCase
implements: Tence_RunnableTest, Tence_TestCollection
Abstract base for test case classes.
Intent of this class is to provide comfortable environment for robust tests. To this end, Tence_TestCase provides a set of assertion methods. A test case class inherits Tence_TestCase. It can define any number of test methods, and optionally either or both of setUp() and tearDown(). It should not define any constructor or destructor methods: the library is free to create and destroy suite and case instances without actually running them. 5.3.5.1 Test Methods
public Tence_Assertion Tence_TestCase::testXXX()
Any public nullary method whose name begins with "test" is considered a test method, and must return an instance of Tence_Assertion. Failing to do so results in the test method being marked as defective.
Important part of robustness in tests is isolation: any test that depends on things it doesn't control is fragile. This library guarantees that each test method will be called on a separate test case instance.
5.3.5.2 setUp() and tearDown()
public void Tence_TestCase::setUp()
public void Tence_TestCase::tearDown()
setUp() and/or tearDown() can be used for setup and cleanup tasks common to all test methods of the test case. setUp() is called before every test method, tearDown() afterwards, both on the same instance as the test method.
If setUp() throws, tearDown() is run immediately. The test method is skipped. If either of these two methods throws, the test method is marked as defective. Exceptions thrown from setUp() mute all exceptions thrown from tearDown().
5.3.5.3 Assertion Methods
Note that none of the assertFoo() methods performs any type conversions on the tested values. This eliminates a few false negatives: for example, 0 == false, but their string representations differ drammaticaly ("0" vs. ""); the sooner this kind of things is caught the better.
assertTrue(1) will fail, as will assertEquals(1, "1")!
Tence_Assertion Tence_TestCase::assertTrue($bool, $msg = '')
Checks that $bool is true.
Tence_Assertion Tence_TestCase::assertFalse($bool, $msg = '')
Checks that $bool is false.
Tence_Assertion Tence_TestCase::assertEquals($exp, $act, $msg = '')
Checks that $act equals $exp.
Instances of the same class are compared by value (using the == operator).
Tence_Assertion Tence_TestCase::assertSame($exp, $act, $msg = '')
Checks that $act equals $exp.
Instances of the same class are compared by identity (using the === operator).
Tence_Assertion Tence_TestCase::assertNull($any, $msg = '')
Checks that $bool is null.
Tence_Assertion Tence_TestCase::assertNotNull($any, $msg = '')
Checks that $bool is not null.
Tence_Assertion Tence_TestCase::assertIsA($o, $class, $msg = '')
Checks that $o is an instance of $class.
Tence_Assertion Tence_TestCase::assertMatches($re, $val, $msg = '')
Checks that $val matches the Perl-compatible regular expression $re.
void Tence_TestCase::setExpectedException($className)
Iff the code following the call of this method throws an exception that is of (or a descendant of) class $className, the test method will "return" successful assertion. All other outcomes cause a failure.
Actual return value of this test method will be ignored.
void Tence_TestCase::run(Tence_Reporter $reporter)
Calls every public method whose name begins with test. Result of each invocation is passed to $reporter.
Tence_RunnableTest[] Tence_TestCase::getTests()
Returns an array of Tence_TestMethod instances, one for each test method.
5.3.6 Tence_TestSuite
Tence_TestSuite & Tence_TestSuite::add(Tence_RunnableTest $test)
Adds $test to the suite. Returns $this.
void Tence_TestSuite::run(Tence_Reporter $reporter)
Calls run($reporter) on every $test it contains.
Tence_RunnableTest[] Tence_TestSuite::getTests()
Returns an array of all $tests added so far.
6 Examples
Here are a few examples to illustrate usage of Tence_TestCase and Tence_TestSuite in writing tests, and tence for their execution.
strcmp_allLowerCaseTest shows one approach to writing test cases: the implementation under test, strcmp() is a pure function so there's only one thing to assert after each strcmp() call.
<?php
class strcmp_allLowerCaseTest extends Tence_TestCase
{
function setUp()
{
$this->oldLocale = setlocale(LC_COLLATE, 'C');
}
function tearDown()
{
setlocale(LC_COLLATE, $this->oldLocale);
}
function testEqualStringsReturnZero()
{
return $this->assertEquals(0, strcmp('aaa', 'aaa'));
}
function testLeftSortsBeforeReturnsMinusOne()
{
return $this->assertEquals(-1, strcmp('aaa', 'aab'));
}
function testRightSortsBeforeReturnsOne()
{
return $this->assertEquals(1, strcmp('aab', 'aaa'));
}
function testShortLeftReturnsMinusOne()
{
return $this->assertEquals(-1, strcmp('aaa', 'aaaa'));
}
function testShortRightReturnsOne()
{
return $this->assertEquals(1, strcmp('aaaa', 'aaa'));
}
}
Paste the above code into mytests.php and run through tence:
% tence mytests.php strcmp_allLowerCaseTest
.....
5 run, 0 failed, 0 threw, 0 malformed
You can (and probably want to) group tests into suites:
class strcmp_mixedCaseTest extends Tence_TestCase
{
// present for demonstration purposes, body is irrelevant...
// contains 5 test methods
}
class AllStrcmpTests extends Tence_TestSuite
{
function __construct()
{
$this
->add(new strcmp_allLowerCaseTest)
->add(new strcmp_mixedCaseTest)
;
}
}
Let's say one of the tests failed (reason for the failure is beside the point of this example):
% tence mytests.php AllStrcmpTests
.....F....
strcmp_mixedCaseTest::testUpperLeftReturnsMinusOne
expected integer(-1) got integer(1)
10 run, 1 failed, 0 threw, 0 malformed
You might have more than one thing to assert after an operation. For example, an instance of a MyFileReader class has to return consistent values from its contents() and lines() methods:
interface FileReader
{
/* string[] */ function contents();
/* int */ function lines();
}
class MyFileReader implements FileReader
{
function __construct($path)
{
// ...
}
// ...
}
Such code is better tested with the tested operation performed in setUp() and the various consequences tested in individual test methods:
class EmptyFileReaderTest extends Tence_TestCase
{
private $reader;
function setUp()
{
$this->file = tempnam('/tmp', 'whatever');
$this->reader = new MyFileReader($this->file);
}
function tearDown()
{
unlink($this->file);
}
function testContentsReturnsEmptyArray()
{
return $this->assertEquals(array(), $this->reader->contents());
}
function testLinesReturnsZero()
{
return $this->assertEquals(0, $this->reader->lines());
}
}
EmptyFileReaderTest alone is insufficient since it leaves the MyFileReader constructor (partially) untested: what if the path passed to the constructor names a non-file type of object or doesn't exist at all? What happens if the permissions disallow read access? These are different tests, and need to be written as well:
class MyFileReaderConstructionErrors
{
function testNonexistentFile()
{
$tmpdir = $this->mkdtemp();
$file = sprintf('%s/nonexistent', $tmpdir->path());
$this->setExpectedException('FileDoesNotExist');
new MyFileReader($file);
}
function testInsufficientPermissions()
{
$tmpdir = $this->mkdtemp();
$file = sprintf('%s/nonexistent', $tmpdir->path());
touch($file);
chmod($file, 0);
$this->setExpectedException('InsufficientPermissions');
new MyFileReader($file);
}
}
The above code relies on the fact that Tence_TestCase::mkdtemp() returns an object whose destructor removes the directory recursively.
It might make sense to compose the MyFileReader tests into a suite:
class FileReaderTests extends Tence_TestSuite
{
function __construct()
{
$this
->add(new MyFileReaderConstructionErrors)
->add(new EmptyFileReaderTest)
;
}
}
Tence_TestSuite allows arbitrary nesting:
class AllMyTests extends Tence_TestSuite
{
function __construct()
{
$this
->add(new AllStrcmpTests)
->add(new FileReaderTests)
;
}
}
Presuming the failing test in strcmp_mixedCaseTest has been fixed:
% tence mytests.php AllMyTests
............
14 run, 0 failed, 0 threw, 0 malformed
Note that suites are not required, you can name any number of classes implementing Tence_RunnableTest on the command line and tence will construct an anonymous suite on the fly:
% tence mytests.php strcmp_allLowerCaseTest strcmp_mixedCaseTest \
> MyFileReaderConstructionErrors EmptyFileReaderTest
............
14 run, 0 failed, 0 threw, 0 malformed
7 Releases
7.1 Release types
Testilence releases fall into one of the following four categories:
snapshots
bugfix releases
new functionality
backward-incompatible change
7.2 Release versioning
Each release is tagged with a version number string with the following structure (ABNF ): version-string = compat-cnt "." newfun-cnt "." bugfix-cnt
compat-cnt = counter
newfun-cnt = counter
bugfix-cnt = counter
rel-candidate = "." snapshot counter
; "snap"
snapshot = %x73.6e.61.70
; counter is a positive integer (includes 0)
counter = 1 * DIGIT
For each release of any type the appropriate counter is incremented by at least one and counters to the right of it are reset to 0 (the release-candidate part is removed altogether), while counters to the left of it are left at their current values.
Version string of the first nonsnapshot Testilence release is 0.0.0, while "0.0.0.snap135" is the version string of a pre-0.0.0 snapshot. Note that there may be gaps between.
This handling is consistent with the way FreeBSD's pkg_version -t interprets version strings.
8 Test suite
The suite is organized around Testilence's classes, meaning there is one or more cases per class under test. These are in turn organized into suites, possibly multiple levels of them. Any part of the resulting tree can be run.
The top level suite is called tencetest_AllTests. This suite contains tencetest_SafeTests and tencetest_DangerousTests
http://opensource.org/docs/definition.php
http://opensource.org/licenses/mit-license.php
http://www.php.net/
http://www.junit.org/
http://en.wikipedia.org/wiki/XUnit
http://beer.sigpipe.cz/
http://subversion.tigris.org/
https://svn.sigpipe.cz/r/
http://www.viewvc.org/
http://svn.sigpipe.cz/
http://people.freebsd.org/~kientzle/libarchive/
http://www.bzip.org/
http://codex.sigpipe.cz/testilence/dist/
http://tools.ietf.org/html/rfc4234
http://www.freebsd.org/cgi/man.cgi?query=pkg_version
--------------------------------------------------------------------------------
View document source. Generated on: 2006-10-28 12:41 UTC. Generated by Docutils from reStructuredText source.
页:
[1]