如何用PHP编写单元测试?[关闭]


97

我到处都读过关于它们有多出色的信息,但是由于某种原因,我似乎无法弄清楚应该测试什么。有人可能会发布一段示例代码,他们将如何对其进行测试?如果不是很麻烦:)


5
为了平衡起见,没有用于PHP的2或3个单元测试框架-此处有列表:en.wikipedia.org/wiki/List_of_unit_testing_frameworks#PHP
Fenton

Answers:


36

有第三个“框架”,到目前为止,它很容易学习-比简单测试更容易,它叫做phpt。

可以在这里找到入门手册:http : //qa.php.net/write-test.php

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

假设您在名为lib.php的文件中具有以下功能:

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

返回的参数非常简单明了。因此,让我们看一下此功能的测试,我们将测试文件称为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提供value,"Hello World"并且var_dump()函数对的响应foo()

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

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


不是run-tests吗?
Dharman

30

您可以使用两个框架进行单元测试。SimpletestPHPUnit,我更喜欢。在PHPUnit主页上阅读有关如何编写和运行测试的教程。这很容易并且描述得很好。


21

您可以通过更改编码样式来适应它,从而使单元测试更加有效。

我建议浏览Google测试博客,尤其是有关编写可测试代码的文章


7
我认为您提到了一个很棒的帖子。用“单元测试不是很有效”来开始您的答案几乎使我不赞成,但是,作为一名测试专家……可能,以积极的方式措辞会鼓励人们阅读本文。
xtofl 2013年

2
@xtofl编辑它以略微提高“积极性” :)
icc97 '16

13

我自己动手做,因为我没有时间去学习别人的做事方式,这花了大约20分钟来写,花了10分钟使它适合在这里发布。

单元测试对我来说非常有用。

这有点长,但是它说明了自己,并且在底部有一个示例。

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

输出:

测试:TestOne失败 
/ **
*此测试旨在失败
** /(行数:149-152;文件:/Users/kris/Desktop/Testable.php)
测试:TestTwo成功 

7

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

然后从非常简单的断言开始。在进行任何其他操作之前,您可以对AssertEquals进行很多操作。那是弄湿你的脚的好方法。

您可能还想先尝试编写测试(因为您给了问题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);
}

5

对于简单的测试和文档,php-doctest相当不错,这是一种非常容易上手的方法,因为您不必打开单独的文件。想象一下下面的函数:

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

如果现在通过phpdt(php-doctest的命令行运行程序)运行此文件,则将运行1个测试。doctest包含在<code>块中。Doctest起源于python,非常适合提供有用且可运行的示例,说明代码应如何工作。您不能专门使用它,因为代码本身会随测试用例而乱扔,但我发现它与更正式的tdd库一起使用非常有用-我使用phpunit。

这第一次的答案在这里概括起来很好(这不是单元VS文档测试)。


1
会不会使信号源有些混乱?
阿里·加纳瓦蒂安

它可以。它仅应用于单个简单测试。还兼作文档。如果您需要更多使用单元测试。
索非亚2015年

2

phpunit几乎是php的事实上的单元测试框架。还有DocTest(作为PEAR包提供)和其他一些。通过phpt测试对php本身进行回归等测试,也可以通过pear运行。


2

代码接收测试与普通的单元测试非常相似,但是在需要模拟和存根的功能上非常强大。

这是样品控制器测试。注意存根的创建是多么容易。您检查该方法被调用的难易程度。

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

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


1

除了已经给出的有关测试框架的出色建议之外,您是否还使用其中内置了自动测试的PHP Web框架之一(例如SymfonyCakePHP)来构建应用程序?有时可以放一些测试方法,以减少一些人与自动测试和TDD相关的启动摩擦。


1

在这里重新发布的方法太多了,但这是有关使用phpt精彩文章。它涵盖了phpt周围经常被忽略的许多方面,因此值得一读以扩展您对php的了解,而不仅仅是编写测试。幸运的是,本文还讨论了编写测试!

讨论要点

  1. 了解PHP边际文档各方面的工作原理(或与此相关的几乎任何部分)
  2. 为您自己的PHP代码编写简单的单元测试
  3. 编写测试作为扩展的一部分,或将潜在的错误传达给内部人员或质量检查小组

1

我知道这里已经有很多信息,但是由于这仍然会显示在Google搜索中,因此我不妨将Chinook Test Suite添加到列表中。这是一个简单而小型的测试框架。

您可以使用它轻松测试类,还可以创建模拟对象。您可以通过Web浏览器而不是通过控制台运行测试。在浏览器中,您可以指定要运行的测试类甚至测试方法。或者,您可以简单地运行所有测试。

github页面的屏幕截图:

奇努克单元测试框架

我喜欢它是您断言测试的方式。这是通过所谓的“流利断言”完成的。例:

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

创建模拟对象也很容易(使用流利的语法):

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

无论如何,更多信息也可以在github页面上找到,并带有代码示例:

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.