【问题标题】:How do I write unit tests in PHP with a procedural codebase?如何使用程序代码库在 PHP 中编写单元测试?
【发布时间】:2010-10-28 07:34:57
【问题描述】:

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

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

【问题讨论】:

    标签: php unit-testing phpunit simpletest paradigms


    【解决方案1】:

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

    在应用程序或验收测试级别,您的程序 PHP 可能依赖于超全局变量($_POST, $_GET, $_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)捕获输出并对其进行断言。如果您使用全局变量,您只需要格外小心(如上所述,在 $_POST 示例中)小心地将测试之间的所有全局变量清空。在处理推送和拉取大量全局变量的函数时,保持测试非常小(5-10 行,1-2 个断言)也特别有用。

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

    // 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);
        }
    

    希望这会有所帮助。

    【讨论】:

    • 如果您正在测试的代码中充斥着exit; 语句,这不公平:(
    • @YarekT 如果代码中充斥着exitdie 语句,那么任何测试都不会顺利进行。处理预期脚本终止的最可测试的方法是抛出异常并注册一个自定义异常处理程序,该处理程序足够聪明,可以在遇到时忽略自定义异常类型。然后您可以测试是否引发了预期的异常。当然,一个设计良好的应用程序在引导阶段之后真的不需要exitdie
    【解决方案2】:

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

    所以,如果你有一个程序代码库,你可以在测试方法中调用你的函数来完成这个

    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 文件,那么您有点不走运。您的代码不能分成不同的单元,这意味着单元测试对您没有多大用处。您最好将时间花在“验收测试”级别,使用SeleniumWatir 等产品。这些将让您自动化浏览器,然后将页面内容检查为特定位置/表单。

    【讨论】:

    • 我绝对可以将我的代码分成不同的单元。您的示例看起来很有希望,我认为这没有使用任何特定的框架?
    • 是的,这只是伪测试代码,但实际的 SIMpleTest 测试和 PHPUnit 测试看起来非常相似。
    【解决方案3】:

    您可以尝试使用

    将非 oop 代码包含到测试类中
    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' );
    }
    

    【讨论】:

    • 这看起来是一个有趣的解决方案,但是如果你的大部分逻辑不涉及函数而是简单的循环和条件呢?
    • 那么你已经得到了意大利面条代码的帮助。
    • @luc M -- 你猜对了。它是结构良好的代码:数据、逻辑和表示是分开的。这不是 OOP。
    • 我从事的项目有大量遗留的意大利面条代码,但我正在设置 PhpUnit 来测试我已经建立的有用的“通用功能”(以及我引入的较新的 OOP 东西以及)。仅仅因为你不能测试所有东西并不意味着单元测试没有用。
    猜你喜欢
    • 2018-01-09
    • 2015-03-30
    • 1970-01-01
    • 2020-04-15
    • 1970-01-01
    • 2020-06-25
    • 2015-03-12
    • 2013-06-02
    相关资源
    最近更新 更多