如何编写一个依赖于真实POST/GET数据的PHPUnit测试?

7

我创建了一个PHP类,封装了filter_input函数,让我们的开发者生活更加容易。
要验证带有urlnameage字段的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超全局变量。

我尝试使用以下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
(抱歉使用葡萄牙语编码,我知道用英语编码会更好,但这是我工作的地方惯用的方式。如果您真的认为需要,我可以翻译代码)。

有什么办法可以测试这个类吗?


你能展示一下你的Filter类吗? - Anti Veeranna
这个想法是以一种方式编写代码,使您不必创建“真实”的POST数据 - 没有意义,过滤器扩展已经起作用并以不同的方式进行了测试,您需要测试_您自己的_代码。为了给您提供如何做到这一点的建议,您需要展示您的代码。 - Anti Veeranna
我不是要贬低你的代码,但如果你使用一个已经建立的验证类(如Zend_Validate),你可能会遇到更少的问题。 - ryeguy
3个回答

2

您无法伪造原始的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

如果你的代码可以进行单元测试,那么它与之前的代码做的事情是相同的。

附注:FILTER_VALIDATE_INTEGER 无效。正确的常量应该是 FILTER_VALIDATE_INT。


看看我在最后一次编辑中添加的代码,也许可以帮助您理解我需要什么... - igorsantos07
好的,我认为这是一个不错的想法。我可以创建一个新类,比如FilterT,它继承原始类并强制使用filter_var而不是filter_input。我正在着手实现这个想法。 - igorsantos07

2

您的代码存在两个问题。其一是访问全局变量,这些变量很难进行测试。第二个问题是将类紧密地绑定到特定数据(如post、get等)。

您应该做的是使该类满足以下接口:

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

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

哎呀,我漏掉了使用filter_input的部分。解决方案是使用filter_var代替。它允许您在任何变量上运行过滤器。


我已经接近解决方案了,ryeguy。给你一个赞,因为另一个答案让我找到了这条路。 (= - igorsantos07

1
一种方法是使用辅助方法来运行您的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() );
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接