可选的PHP类型提示/检查单元测试或静态分析?

时间:2014-06-29 04:42:22

标签: php unit-testing static-analysis design-by-contract type-hinting

PHP类型提示不支持标量变量[1],如int或string

但是,我们发现在连续集成期间注释函数中的类型(int或string)以发现错误仍然非常有用,例如

目前我使用像

这样的方法
function foo($s) {
    //assert( is_string($s), 'not a string' );
    ...
}

在单元测试和开发模式期间,断言将被取消注释,以发现潜在的错误。

我在寻找是否有更好的方法。

[1] http://www.php.net/manual/en/language.oop5.typehinting.php

3 个答案:

答案 0 :(得分:4)

一个有趣而优雅的解决方案是AOP。您可以从代码中删除所有断言并开始使用这样的标准phpdoc:

/**
* @param string $s
*/
function foo($s) {
    ...
}

然后在单元测试期间和开发模式中使用AOP框架,如下所示: http://go.aopphp.com/blog/2013/02/11/aspect-oriented-framework-for-php/

从他们的文件:

  

...在10-20行代码的帮助下,我们可以拦截所有的代码   所有类中的公共,受保护和静态方法   应用...

您可以使用它来动态拦截所有方法,读取agruments,获取ReflectionMethod对象,解析类型相关注释并执行运行时检查。这听起来很复杂,但这很容易做到。

结果:在每个包含的PHP文件的测试过程中它会占用一些运行时资源(不多),但对于你的代码库来说它看起来会更好(更干净)。

答案 1 :(得分:1)

如果您不介意应用程序中的一些开销,请考虑在文件中定义一组类型检查函数" typecheck.php":

   assert_string($s) {  
      assert( is_string($), 'not a string');
   }

   assert_int($i) { 
     assert( is_int($i), 'not an integer);
   }

   ... lots more type checks as appropriate ...

"需要"这个" typecheck.php"在所有脚本的顶部,并编写类似于您的示例的类型检查:

    require("typecheck.php");
    ...
    function foo($s) {
       assert_string($s); 
    ...

然后您不需要对支票进行评论。这有很好的补充 编写代码包含断言的属性,作为文档,以帮助代码维护者;他们的存在将确保他们在更改代码时进行检查,并会提醒他根据需要添加更多内容。

您可以通过这种方式添加各种方便,专业的支票;考虑:

     assert_integer_range($i, $l, $u) {
         assert_int($i);
         assert($i>=$l);
         assert($i<=$u);
    }

     bar($n) {
         assert_integer_range($n,1,10);
     ...

你可能获得的任何体面的静态分析工具都会因断言的存在而受益匪浅。

如果assert_xxx调用的开销太大,您无法在生产代码中受到影响,则可以降低生产代码的成本。有另一种选择&#34; typecheck.php&#34;文件使用生产代码,它定义相同的功能,但不进行检查。不完美,但它会有所帮助。

这个解决方案并不需要每个程序员已经拥有的文本编辑器之外的任何工具。

答案 2 :(得分:-1)

我使用了一些解决方案,类似于下面的答案。

使用这样的标准docblock:

/**
 * This function sets InternalVariable to be $s
 * @param string $s
 */
function foo($s) 
{
    ...
}

现在,我还组合了PHP_CodeSniffer以确保启用了Docblock,以确保注释已经到位并已定义。

然后将PHPUnit与断言一起使用来测试该函数是否仍使用字符串。

class FOO_TEST
{
    public function test_foo()
    {
        $TestObject = new FOO_CLASS();
        $TestObject->Foo(1);
        $this->assertTrue( is_string($TestObject->InternalString, 'Foo should have accepted a string') );
    }
}

通常,如果传递给函数的变量必须是某种类型,那么您始终可以在函数本身中强制执行该变量。

如果您使用的是较新的PHP,请考虑使用Standard PHP Library SPL-Types

/**
 * This function sets InternalVariable to be $s
 * @param SplString $s
 */
function foo(SplString $s) 
{
    ...
}

现在还可以进行编译时验证。