【问题标题】:How to make a PHPUnit test that depends on ~real~ POST/GET data?如何进行依赖于 ~real~ POST/GET 数据的 PHPUnit 测试?
【发布时间】:2010-11-11 19:12:51
【问题描述】:

我创建了一个包含 filter_input 函数的 PHP 类,以使我们的开发人员的生活更轻松。
要验证带有 url、name 和 age 字段的 HTML 表单,代码如下:

$post = Filter::POST();
if ($post->validate_string('name') && $post->validate_integer('age')) {
    $url = $post->sanitize_url('url');
}

它与以下内容相同:

if (filter_input(INPUT_POST,'name',FILTER_UNSAFE_RAW) && filter_input(INPUT_POST,'age',FILTER_VALIDATE_INTEGER)) {
    $url = filter_input(INPUT_POST,'url',FILTER_SANITIZE_URL);
}

好吧,我认为代码已经完成,现在我想为它创建一个 PHPUnit 测试。

问题是我不知道如何在 PHPUnit 方法上伪造 GET/POST 数据,不适用于这种情况。
我不需要在$_POST 中插入值,我需要“真实”数据,因为filter_input 使用脚本接收到的数据,而不是实际的$_POST superglobal。

我尝试使用以下 PHPT 测试和 PHPUnit 方法来实现这一点,但完全没有成功:

--TEST--
Generates POST and GET data to be used in FilterTest.php
--POST--
name=Igor&age=20
--GET--
name=Igor&age=19
--FILE--
<?php
echo $_POST['nome'].' = '.$_POST['idade'];
?>
--EXPECT--
Igor = 20

public function testPhpt() {
 $phpt = new PHPUnit_Extensions_PhptTestCase('FilterTest_data.phpt', array('cgi' => 'php-cgi'));
 $result = $phpt->run();
 $this->assertTrue($result->wasSuccessful());
}

编辑

原始代码:http://pastebin.com/fpw2fpxM
用于初始测试的代码:http://pastebin.com/vzxsBQWm
(对不起葡萄牙语,我知道用英语编码会更好,但这是我工作的地方的工作方式。如果您真的认为确实需要,我可以翻译代码)。

知道我可以做些什么来测试这个类吗?

【问题讨论】:

  • 你能展示你的过滤器类吗?
  • 这个想法是以一种您不必创建“真实” POST 数据的方式编写代码 - 没有意义,过滤器扩展工作并且以不同的方式进行测试,您需要测试你的代码。为了给你建议如何做,你需要展示你的代码。
  • 我并不是要敲你的代码,但使用 Zend_Validate 等已建立的验证类,你可能不会那么头疼。

标签: php testing phpunit


【解决方案1】:

您不能伪造原始 POST 数据。但问题在于您的代码:它不是可单元测试的。而不是:

if (filter_input(INPUT_POST,'name',FILTER_UNSAFE_RAW) && filter_input(INPUT_POST,'age', FILTER_VALIDATE_INTEGER)) {
    $url = filter_input(INPUT_POST,'url',FILTER_SANITIZE_URL);
}

如果你有:

if (filter_var($data['name'], FILTER_UNSAFE_RAW) && filter_var($data['age'], FILTER_VALIDATE_INT)) {
    $url = filter_var($data['url'], FILTER_SANITIZE_URL);
}
// where $data is a copy of $_POST in that case

将使您的代码可单元测试,并且与您之前的代码一样。

P.S.:FILTER_VALIDATE_INTEGER 无效。正确的常量是 FILTER_VALIDATE_INT

【讨论】:

  • 看看我上次编辑时添加的代码,也许它可以帮助你理解我需要什么......
  • 好吧,无论如何我认为这是个好主意。我可以创建一个新类,如 FilterT,它扩展了原始类并强制它使用 filter_var 而不是 filter_input。目前正在研究这个想法。
【解决方案2】:

您的代码存在 2 个问题。一是您正在访问难以测试的全局变量。第二个是您将类与特定数据(发布、获取等)紧密绑定。

你应该做的是让类满足这种接口:

$filter = new Filter($_POST);
$filter->validate_string('name');

好处应该是显而易见的。您不必使用$_POST 或$_GET 或任何其他预定义类型作为输入。您现在不仅可以验证来自任何来源的输入(因为您只是将其传递给构造函数),更重要的是,您可以将任何您喜欢的数据注入其中以进行测试。

糟糕,我错过了使用filter_input 的部分。解决方案是改用filter_var。它允许您对任何变量运行过滤器。

【讨论】:

  • 我已经接近那个解决方案了,黑麦。给你一票,因为另一个答案把我带到了那个方向。 (=
【解决方案3】:

解决此问题的一种方法是使用辅助方法在内部运行 filter_input,然后在测试期间模拟此方法以使用 filter_var 之类的其他方法。

例如,这个用例可以通过以下方式完成:

class Data_Class {

    protected function i_use_filters() {
        if ( $this->filter_input( 'name', FILTER_UNSAFE_RAW ) && $this->filter_input( 'age', FILTER_VALIDATE_INT ) ) {
            $url = $this->filter_input( 'url', FILTER_SANITIZE_URL );
        }
        return $url;
    }

    protected function filter_input( $name, $filter ) {
        return filter_input( INPUT_POST, $name, $filter );
    }
}

class Test_Class extends TestCase {

    public function test_filters() : void {
        $mock = $this->getMockBuilder( Data_Class::class )
                        ->setMethods( [ 'filter_input' ] )
                        ->getMock();
        $mock->method( 'filter_input' )
                ->willReturnCallback( function ( $name, $filter ) {
                    if ( ! isset( $_POST[ $name ] ) ) {
                        return null;
                    }
                    return filter_var( $_POST[ $name ], $filter );
                } );

        $_POST[ 'name' ] = 'Myself';
        $_POST[ 'age'] = 40;
        $_POST[ 'url' ] = 'https://test.com';

        $this->assertEquals( 'https://test.com', $mock->i_use_filters() );
    }
}

【讨论】:

    猜你喜欢
    • 2014-02-12
    • 2016-02-06
    • 1970-01-01
    • 2018-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-16
    相关资源
    最近更新 更多