如何用PHP编写带有过程代码库的单元测试?

如何用PHP编写带有过程代码库的单元测试?,php,unit-testing,phpunit,simpletest,paradigms,Php,Unit Testing,Phpunit,Simpletest,Paradigms,我基本上相信单元测试的好处,我想开始将这个概念应用到用PHP编写的大型现有代码库中。此代码中只有不到10%是面向对象的 我研究了几个单元测试框架(PHPUnit、SimpleTest和phpt)。然而,我还没有找到任何测试过程代码的示例。对于我的情况,最好的框架是什么?有没有使用非OOP代码对PHP进行单元测试的例子?您可以尝试使用 require_once 'your_non_oop_file.php' # Contains fct_to_test() 使用phpUnit,您可以定义测试功能

我基本上相信单元测试的好处,我想开始将这个概念应用到用PHP编写的大型现有代码库中。此代码中只有不到10%是面向对象的


我研究了几个单元测试框架(PHPUnit、SimpleTest和phpt)。然而,我还没有找到任何测试过程代码的示例。对于我的情况,最好的框架是什么?有没有使用非OOP代码对PHP进行单元测试的例子?

您可以尝试使用

require_once 'your_non_oop_file.php' # Contains fct_to_test()
使用phpUnit,您可以定义测试功能:

testfct_to_test() {
   assertEquals( result_expected, fct_to_test(), 'Fail with fct_to_test' );
}

单元测试做得好的地方,以及你应该使用它们的地方,是当你有一段代码,你给了一些输入,你期望得到一些输出。其思想是,当您以后添加功能时,您可以运行测试并确保它仍然以相同的方式执行旧功能

因此,如果您有一个过程代码库,那么您可以通过在测试方法中调用函数来实现这一点

require 'my-libraries.php';
class SomeTest extends SomeBaseTestFromSomeFramework {
    public function testSetup() {
        $this->assertTrue(true);
    }

    public function testMyFunction() {
        $output = my_function('foo',3);

        $this->assertEquals('expected output',$output);
    }
}
PHP代码库的技巧是,库代码通常会干扰测试框架的运行,因为代码库和测试框架中有大量与在web浏览器中设置应用程序环境相关的代码(会话、共享全局变量等)。您需要花一些时间来包含库代码并运行简单的测试(上面的testSetup函数)


如果您的代码没有函数,只是一系列输出HTML页面的PHP文件,那么您就有点运气不好了。你的代码不能被分成不同的单元,这意味着单元测试对你没有多大用处。您最好将您的时间花在“验收测试”级别,比如和。这将使您能够自动创建浏览器,然后在特定位置/表单中检查页面内容

您可以对过程PHP进行单元测试,没有问题。如果你的代码与HTML混合在一起,你肯定不会走运

在应用程序或验收测试级别,过程PHP可能依赖于超全局变量(
$\u POST、$\u GET、$\u COOKIE
等)的值来确定行为,并通过包含模板文件和输出结束

要进行应用程序级测试,只需设置超全局值;启动一个输出缓冲区(防止一堆html充斥你的屏幕);调用页面;断言缓冲区内的内容;并在结尾处丢弃缓冲区。 所以,你可以这样做:

public function setUp()
{
    if (isset($_POST['foo'])) {
        unset($_POST['foo']);
    }
}

public function testSomeKindOfAcceptanceTest()
{
    $_POST['foo'] = 'bar';
    ob_start();
    include('fileToTest.php');
    $output = ob_get_flush();
    $this->assertContains($someExpectedString, $output);
}
即使对于包含大量内容的大型“框架”,这种测试也会告诉您应用程序级功能是否正常工作。当您开始改进代码时,这一点非常重要,因为即使您确信数据库连接器仍然可以工作,并且看起来比以前更好,您也会希望单击一个按钮,然后看到,是的,您仍然可以通过数据库登录和注销

在较低的级别上,根据变量范围以及函数是通过副作用(返回true还是false)工作,还是直接返回结果,会有一些微小的变化

变量是否作为参数或函数之间的参数数组显式传递?或者变量设置在许多不同的地方,并作为全局变量隐式传递?如果是(好的)显式情况,您可以通过(1)包括保存函数的文件,然后(2)直接输入函数测试值,以及(3)捕获输出并对其进行断言来对函数进行单元测试。如果您使用的是全局变量,那么您只需格外小心(如上所述,在$u POST示例中),在测试之间仔细地清空所有全局变量。在处理推拉大量全局变量的函数时,将测试保持在非常小的范围(5-10行,1-2个断言)也特别有用

另一个基本问题是函数是通过返回输出来工作,还是通过改变传入的参数来工作,而是返回true/false。在第一种情况下,测试更容易,但同样,在这两种情况下都有可能:

// assuming you required the file of interest at the top of the test file
public function testShouldConcatenateTwoStringsAndReturnResult()
{
  $stringOne = 'foo';
  $stringTwo = 'bar';
  $expectedOutput = 'foobar';
  $output = myCustomCatFunction($stringOne, $stringTwo);
  $this->assertEquals($expectedOutput, $output);
}
在坏的情况下,如果代码通过副作用工作并返回true或false,您仍然可以非常轻松地进行测试:

/* suppose your cat function stupidly 
 * overwrites the first parameter
 * with the result of concatenation, 
 * as an admittedly contrived example 
 */
public function testShouldConcatenateTwoStringsAndReturnTrue()
    {
      $stringOne = 'foo';
      $stringTwo = 'bar';
      $expectedOutput = 'foobar';
      $output = myCustomCatFunction($stringOne, $stringTwo);
      $this->assertTrue($output);
      $this->Equals($expectedOutput, $stringOne);
    }

希望这能有所帮助。

这看起来是一个有趣的解决方案,但是,如果您的大多数逻辑不涉及函数,而是涉及简单的循环和条件,该怎么办?那么您就有了大量的意大利面代码。@luc M--您的假设是正确的。它是结构良好的代码:数据、逻辑和表示是分开的。这不是OOP。我所从事的项目有大量传统的意大利面代码,但我正在设置PhpUnit来测试我建立的有用的“通用函数”(以及我引入的较新的OOP东西)。仅仅因为你不能测试所有的东西并不意味着单元测试是无用的。我完全可以把我的代码分成不同的单元。您的示例看起来很有希望,我认为这没有使用任何特定的框架?是的,这只是伪测试代码,但实际的SImpleTest测试和PHPUnit测试看起来非常相似。如果您正在测试的代码中充斥着
exit语句:(@YarekT如果代码中充斥着
exit
die
语句,则任何测试都不可能顺利进行。处理预期脚本终止的最可测试的方法是抛出异常并注册一个自定义异常处理程序,该处理程序足够智能,可以在遇到异常时忽略自定义异常类型。然后,您可以测试该异常当然,一个设计良好的应用程序在引导阶段之后真的不需要退出或死亡。