如何在 PHP 中编写单元测试?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/282150/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I write unit tests in PHP?
提问by letgo
I've read everywhere about how great they are, but for some reason I can't seem to figure out how exactly I'm supposed to test something. Could someone perhaps post a piece of example code and how they would test it? If it's not too much trouble :)
我到处都读到它们有多棒,但出于某种原因,我似乎无法弄清楚我应该如何测试某些东西。有人可以发布一段示例代码以及他们将如何测试它吗?如果不是太麻烦:)
回答by Till
There is a 3rd "framework", which is by far easier to learn - even easier than SimpleTest, it's called phpt.
有第三个“框架”,它更容易学习 - 甚至比简单测试更容易,它被称为 phpt。
A primer can be found here: http://qa.php.net/write-test.php
可以在此处找到入门:http: //qa.php.net/write-test.php
Edit:Just saw your request for sample code.
编辑:刚刚看到您对示例代码的请求。
Let's assume you have the following function in a file called lib.php:
假设您在名为lib.php的文件中有以下函数:
<?php
function foo($bar)
{
return $bar;
}
?>
Really simple and straight forward, the parameter you pass in, is returned. So let's look at a test for this function, we'll call the test file foo.phpt:
非常简单直接,你传入的参数被返回。所以让我们看一下这个函数的测试,我们将调用测试文件foo.phpt:
--TEST--
foo() function - A basic test to see if it works. :)
--FILE--
<?php
include 'lib.php'; // might need to adjust path if not in the same dir
$bar = 'Hello World';
var_dump(foo($bar));
?>
--EXPECT--
string(11) "Hello World"
In a nutshell, we provide the parameter $barwith value "Hello World"and we var_dump()the response of the function call to foo().
概括地说,我们所提供的参数$bar与价值"Hello World",我们var_dump()的函数调用的响应foo()。
To run this test, use: pear run-test path/to/foo.phpt
要运行此测试,请使用: pear run-test path/to/foo.phpt
This requires a working install of PEARon your system, which is pretty common in most circumstances. If you need to install it, I recommend to install the latest version available. In case you need help to set it up, feel free to ask (but provide OS, etc.).
这需要在您的系统上安装 PEAR,这在大多数情况下很常见。如果您需要安装它,我建议安装可用的最新版本。如果您需要帮助来设置它,请随时询问(但提供操作系统等)。
回答by okoman
There are two frameworks you can use for unit testing. Simpletestand PHPUnit, which I prefer. Read the tutorials on how to write and run tests on the homepage of PHPUnit. It is quite easy and well described.
有两个框架可用于单元测试。Simpletest和PHPUnit,我更喜欢。在 PHPUnit 的主页上阅读有关如何编写和运行测试的教程。这很容易并且描述得很好。
回答by Preston
You can make unit testing more effective by changing your coding style to accommodate it.
您可以通过更改编码风格来适应单元测试,从而使单元测试更有效。
I recommend browsing the Google Testing Blog, in particular the post on Writing Testable Code.
回答by Kris
I rolled my own because i didnt have time to learn someone elses way of doing things, this took about 20 minutes to write up, 10 to adapt it for posting here.
我自己动手,因为我没有时间学习别人的做事方式,这花了大约 20 分钟写下来,10 分钟来调整它以张贴在这里。
Unittesting is veryusefull to me.
单元测试对我来说非常有用。
this is kinda long but it explains itself and there is an example at the bottom.
这有点长,但它解释了自己,底部有一个例子。
/**
* Provides Assertions
**/
class Assert
{
public static function AreEqual( $a, $b )
{
if ( $a != $b )
{
throw new Exception( 'Subjects are not equal.' );
}
}
}
/**
* Provides a loggable entity with information on a test and how it executed
**/
class TestResult
{
protected $_testableInstance = null;
protected $_isSuccess = false;
public function getSuccess()
{
return $this->_isSuccess;
}
protected $_output = '';
public function getOutput()
{
return $_output;
}
public function setOutput( $value )
{
$_output = $value;
}
protected $_test = null;
public function getTest()
{
return $this->_test;
}
public function getName()
{
return $this->_test->getName();
}
public function getComment()
{
return $this->ParseComment( $this->_test->getDocComment() );
}
private function ParseComment( $comment )
{
$lines = explode( "\n", $comment );
for( $i = 0; $i < count( $lines ); $i ++ )
{
$lines[$i] = trim( $lines[ $i ] );
}
return implode( "\n", $lines );
}
protected $_exception = null;
public function getException()
{
return $this->_exception;
}
static public function CreateFailure( Testable $object, ReflectionMethod $test, Exception $exception )
{
$result = new self();
$result->_isSuccess = false;
$result->testableInstance = $object;
$result->_test = $test;
$result->_exception = $exception;
return $result;
}
static public function CreateSuccess( Testable $object, ReflectionMethod $test )
{
$result = new self();
$result->_isSuccess = true;
$result->testableInstance = $object;
$result->_test = $test;
return $result;
}
}
/**
* Provides a base class to derive tests from
**/
abstract class Testable
{
protected $test_log = array();
/**
* Logs the result of a test. keeps track of results for later inspection, Overridable to log elsewhere.
**/
protected function Log( TestResult $result )
{
$this->test_log[] = $result;
printf( "Test: %s was a %s %s\n"
,$result->getName()
,$result->getSuccess() ? 'success' : 'failure'
,$result->getSuccess() ? '' : sprintf( "\n%s (lines:%d-%d; file:%s)"
,$result->getComment()
,$result->getTest()->getStartLine()
,$result->getTest()->getEndLine()
,$result->getTest()->getFileName()
)
);
}
final public function RunTests()
{
$class = new ReflectionClass( $this );
foreach( $class->GetMethods() as $method )
{
$methodname = $method->getName();
if ( strlen( $methodname ) > 4 && substr( $methodname, 0, 4 ) == 'Test' )
{
ob_start();
try
{
$this->$methodname();
$result = TestResult::CreateSuccess( $this, $method );
}
catch( Exception $ex )
{
$result = TestResult::CreateFailure( $this, $method, $ex );
}
$output = ob_get_clean();
$result->setOutput( $output );
$this->Log( $result );
}
}
}
}
/**
* a simple Test suite with two tests
**/
class MyTest extends Testable
{
/**
* This test is designed to fail
**/
public function TestOne()
{
Assert::AreEqual( 1, 2 );
}
/**
* This test is designed to succeed
**/
public function TestTwo()
{
Assert::AreEqual( 1, 1 );
}
}
// this is how to use it.
$test = new MyTest();
$test->RunTests();
This outputs:
这输出:
Test: TestOne was a failure /** * This test is designed to fail **/ (lines:149-152; file:/Users/kris/Desktop/Testable.php) Test: TestTwo was a success
回答by PartialOrder
Get PHPUnit. It is very easy to use.
获取 PHPUnit。这是非常容易使用。
Then start with very simple assertions. You can do alot with AssertEquals before you get into anything else. That's a good way to get your feet wet.
然后从非常简单的断言开始。在进入其他任何事情之前,您可以使用 AssertEquals 做很多事情。这是让你的脚湿透的好方法。
You may also want to try writing your test first (since you gave your question the TDD tag) and then write your code. If you haven't done this before it is an eye-opener.
您可能还想先尝试编写测试(因为您为问题提供了 TDD 标签),然后再编写代码。如果您之前没有这样做过,那真是大开眼界。
require_once 'ClassYouWantToTest';
require_once 'PHPUnit...blah,blah,whatever';
class ClassYouWantToTest extends PHPUnit...blah,blah,whatever
{
private $ClassYouWantToTest;
protected function setUp ()
{
parent::setUp();
$this->ClassYouWantToTest = new ClassYouWantToTest(/* parameters */);
}
protected function tearDown ()
{
$this->ClassYouWantToTest = null;
parent::tearDown();
}
public function __construct ()
{
// not really needed
}
/**
* Tests ClassYouWantToTest->methodFoo()
*/
public function testMethodFoo ()
{
$this->assertEquals(
$this->ClassYouWantToTest->methodFoo('putValueOfParamHere), 'expectedOutputHere);
/**
* Tests ClassYouWantToTest->methodBar()
*/
public function testMethodFoo ()
{
$this->assertEquals(
$this->ClassYouWantToTest->methodBar('putValueOfParamHere), 'expectedOutputHere);
}
回答by Sofia
For simple tests AND documentation, php-doctestis quite nice and it's a really easy way to get started since you don't have to open a separate file. Imagine the function below:
对于简单的测试和文档,php-doctest非常好,它是一种非常简单的入门方法,因为您不必打开单独的文件。想象一下下面的函数:
/**
* Sums 2 numbers
* <code>
* //doctest: add
* echo add(5,2);
* //expects:
* 7
* </code>
*/
function add($a,$b){
return $a + $b;
}
If you now run this file through phpdt (command-line runner of php-doctest) 1 test will be run. The doctest is contained inside the < code > block. Doctest originated in python and is fine for giving useful & runnable examples on how the code is supposed to work. You can't use it exclusively because the code itself would litter up with test cases but I've found that it's useful alongside a more formal tdd library - i use phpunit.
如果您现在通过 phpdt(php-doctest 的命令行运行程序)运行此文件,将运行 1 个测试。doctest 包含在 <code> 块中。Doctest 起源于 python,非常适合提供关于代码应该如何工作的有用且可运行的示例。您不能专门使用它,因为代码本身会随测试用例乱扔垃圾,但我发现它与更正式的 tdd 库一起使用时很有用 - 我使用 phpunit。
This 1st answer heresums it up nicely (it's not unit vs doctest ).
这第一次的答案在这里概括起来很好(这不是单元VS文档测试)。
回答by kguest
phpunit is pretty much the defacto unit testing framework for php. there is also DocTest(available as a PEAR package) and a few others. php itself is tested for regressions and the like via phpt testswhich can also be run via pear.
phpunit 几乎是事实上的 php 单元测试框架。还有DocTest(可作为 PEAR 包提供)和其他一些。php 本身通过phpt测试进行回归等测试,也可以通过 pear 运行。
回答by Davert
Codeception tests are much like common unit tests but are much powerful in things where you need mocking and stubbing.
Codeception 测试很像常见的单元测试,但在需要模拟和存根的地方非常强大。
Here is the sample controller test. Notice how easily stubs are created. How easily you check the method was invoked.
这是示例控制器测试。请注意创建存根是多么容易。检查方法被调用的难易程度。
<?php
use Codeception\Util\Stub as Stub;
const VALID_USER_ID = 1;
const INVALID_USER_ID = 0;
class UserControllerCest {
public $class = 'UserController';
public function show(CodeGuy $I) {
// prepare environment
$I->haveFakeClass($controller = Stub::makeEmptyExcept($this->class, 'show'));
$I->haveFakeClass($db = Stub::make('DbConnector', array('find' => function($id) { return $id == VALID_USER_ID ? new User() : null ))); };
$I->setProperty($controller, 'db', $db);
$I->executeTestedMethodOn($controller, VALID_USER_ID)
->seeResultEquals(true)
->seeMethodInvoked($controller, 'render');
$I->expect('it will render 404 page for non existent user')
->executeTestedMethodOn($controller, INVALID_USER_ID)
->seeResultNotEquals(true)
->seeMethodInvoked($controller, 'render404','User not found')
->seeMethodNotInvoked($controller, 'render');
}
}
Also there are other cool things. You can test database state, filesystem, etc.
还有其他很酷的东西。您可以测试数据库状态、文件系统等。
回答by quickshiftin
Way too much to re-post here, but here is a great articleon using phpt. It covers a number of aspects around phptthat are often overlooked, so it could be worth a read to expand your knowledge of php beyond just writing a test. Fortunately the article also discusses writing tests!
在这里重新发布太多了,但这里有一篇关于使用phpt的好文章。它涵盖了有关phpt 的许多经常被忽视的方面,因此值得一读以扩展您对 php 的了解,而不仅仅是编写测试。幸运的是,本文还讨论了编写测试!
The main points of discussion
主要讨论点
- Discover how marginally documented aspects of PHP work (or pretty much any part for that matter)
- Write simple unit tests for your own PHP code
- Write tests as part of an extension or to convey a potential bug to the internals or QA groups
- 了解 PHP 的边际记录方面是如何工作的(或几乎任何部分)
- 为您自己的 PHP 代码编写简单的单元测试
- 编写测试作为扩展的一部分或向内部人员或 QA 小组传达潜在的错误
回答by Vivendi
I know there is a lot of info here already, but since this still shows up on Google searches i might as well add Chinook Test Suiteto the list. It is a simple and small test framework.
我知道这里已经有很多信息,但是由于这仍然会出现在 Google 搜索中,我不妨将Chinook 测试套件添加到列表中。它是一个简单而小巧的测试框架。
You can easily test your classes with it and also create mock objects. You run the tests through a web browser and (not yet)through a console. In the browser you can specify what test class or even what test method to run. Or you can simply run all tests.
您可以使用它轻松测试您的类,还可以创建模拟对象。您通过 Web 浏览器和(尚未)通过控制台运行测试。在浏览器中,您可以指定要运行的测试类甚至测试方法。或者您可以简单地运行所有测试。
A screenshot from the github page:
github页面截图:


What i like about it is the way you assert tests. This is done with so called "fluent assertions". Example:
我喜欢它的是你断言测试的方式。这是通过所谓的“流畅断言”完成的。例子:
$this->Assert($datetime)->Should()->BeAfter($someDatetime);
And creating mock objects is a breeze too (with a fluent like syntax):
创建模拟对象也轻而易举(使用流畅的语法):
$mock = new CFMock::Create(new DummyClass());
$mock->ACallTo('SomeMethod')->Returns('some value');
Anyway, more info can be found on the github page with a code example as well:
无论如何,可以在 github 页面上找到更多信息,并附有代码示例:

