我如何在PHP中编写unit testing?

我已经读到了各地的情况,但由于某种原因,我似乎无法弄清楚我应该如何testing一些东西。 有人可能会张贴一个示例代码,他们将如何testing它? 如果它不是太麻烦:)

有一个第三个“框架”,比简单testing更容易学习 – 比简单testing更容易,它被称为phpt。

引用可以在这里find: http : //qa.php.net/write-test.php

编辑:刚刚看到您的示例代码的请求。

假设您在一个名为lib.php的文件中具有以下function:

<?php function foo($bar) { return $bar; } ?> 

真的简单而直接,你传入的参数被返回。 所以让我们来看看这个函数的testing,我们将调用testing文件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" 

简而言之,我们提供参数$bar ,值为"Hello World"var_dump()函数调用对foo()的响应。

要运行此testing,请使用: pear run-test path/to/foo.phpt

这需要在您的系统上安装PEAR ,这在大多数情况下是很常见的。 如果您需要安装它,我build议安装最新的版本。 如果您需要帮助来设置它,请随时询问(但提供操作系统等)。

有两个框架可以用于unit testing。 Simpletest和PHPUnit ,我更喜欢。 阅读关于如何在PHPUnit的主页上编写和运行testing的教程。 这是很容易,很好的描述。

你可以通过改变你的编码风格来使unit testing更有效。

我build议浏览Googletesting博客 ,特别是编写可testing代码的post。

因为我没有时间去学习别人的做事方式,所以我花了20分钟的时间写出来,10分钟的时间来适应这个post。

unit testing对我来说非常有用。

这是相当长的,但它解释了自己,底部是一个例子。

 /** * 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(); 

这输出:

 testing:TestOne是一个失败 
 / **
 *此testing旨在失败
 ** /(行:149-152;文件:/Users/kris/Desktop/Testable.php)
testing:TestTwo是成功的 

获取PHPUnit。 这是非常容易使用。

然后从非常简单的断言开始。 在进入其他任何事情之前,你可以用AssertEquals做很多事情。 这是一个好方法让你的脚湿。

您可能还想先尝试编写testing(因为您已将您的问题提供给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); } 

对于简单的testing和文档, php-doctest是相当不错的,这是一个非常简单的方法,因为你不必打开一个单独的文件。 想象下面的function:

 /** * Sums 2 numbers * <code> * //doctest: add * echo add(5,2); * //expects: * 7 * </code> */ function add($a,$b){ return $a + $b; } 

如果你现在通过phpdt(php-doctest的命令行亚军)运行这个文件,将运行1个testing。 doctest包含在<code>块中。 Doctest起源于python,对于代码应该如何工作给出有用和可运行的示例是很好的。 你不能独占使用它,因为代码本身会浪费在testing用例上,但是我发现它和更正式的tdd库一起使用是非常有用的 – 我使用phpunit。

这第一个答案在这里很好地总结(这不是单位与doctest)。

phpunit几乎是php的事实上的unit testing框架。 还有DocTest (作为PEAR包可用)和其他一些。 PHP本身是通过phpttesting进行回归testing ,也可以通过梨运行。

代码testing非常类似于常见的unit testing,但是在需要嘲笑和剔除的东西中function非常强大。

这是示例控制器testing。 注意如何轻松创build存根。 你很容易检查被调用的方法。

 <?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'); } } 

还有其他很酷的东西。 您可以testing数据库状态,文件系统等

除了已经给出的关于testing框架的优秀build议之外,您是否正在使用内置自动化testing的PHP Web框架之一构build您的应用程序,例如Symfony或CakePHP ? 有时候有一个地方可以放弃testing方法,这样可以减less一些人与自动化testing和TDD相关的启动冲突。

方式太多,重新张贴在这里,但这里是一个伟大的文章使用phpt 。 它涵盖了围绕phpt的一些常常被忽视的方面,所以值得一读,扩大你对php的知识,而不仅仅是写一个testing。 幸运的是文章还讨论了编写testing!

讨论的要点

  1. 发现PHP的边缘文档方面的工作(或几乎任何部分)
  2. 为您自己的PHP代码编写简单的unit testing
  3. 将testing作为扩展的一部分进行编写,或将潜在的错误传递给内部或QA组

我知道这里已经有很多信息了,但是由于这个问题在Googlesearch中还是显示出来了,所以我可能会把Chinooktesting套件添加到列表中。 这是一个简单而小巧的testing框架。

你可以很容易地testing你的类,也可以创build模拟对象。 您可以通过networking浏览器运行testing(尚未通过控制台)。 在浏览器中,你可以指定什么testing类,甚至是什么testing方法来运行。 或者你可以简单地运行所有的testing。

github页面的屏幕截图:

Chinook单元测试框架

我喜欢的是你坚持testing的方式。 这是所谓的“stream利的断言”。 例:

 $this->Assert($datetime)->Should()->BeAfter($someDatetime); 

创build模拟对象也是一件轻而易举的事情(stream利的语法):

 $mock = new CFMock::Create(new DummyClass()); $mock->ACallTo('SomeMethod')->Returns('some value'); 

无论如何,更多的信息可以在github页面find一个代码示例:

https://github.com/w00/Chinook-TestSuite