【问题标题】:PHPUnit testing file_get_contentsPHPUnit 测试 file_get_contents
【发布时间】:2015-12-29 12:57:41
【问题描述】:

我有一个类,它有一个使用 PHP 的全局 file_get_contents 函数的方法。我需要在类上测试方法,而不是实际调用全局函数。

我知道我可以使用命名空间来覆盖从 file_get_contents 返回的内容,但是我的测试已经在一个单独的命名空间中,所以我不能简单地将命名空间与类匹配。

这里有一些代码:

<?php
namespace MyVendor\MyProject;

class MyClass
{

private $someProperty;

public function __construct($override = '')
{
    $this->someProperty = $override;
}

public function myMethod()
{
    $request = 'http://example.com';
    $response = $this->someMethodUsingGlobals($request);
    // Do something with the response..
}
public function someMethodUsingGlobals($url)
{
    return json_decode(file_get_contents($url),true)['results'][0];
}

}

测试

<?php
namespace MyProjectTests;

public function test_it_does_something_with_the_response()
{
    $sut = new MyClass();

    $response = $sut->myMethod();

    $this->assertEquals('Some expectation', $response);
}

我需要在课堂上模拟 someMethodUsingGlobals() 方法,但不完全确定如何去做。

【问题讨论】:

  • 很难模拟私有方法。如果问题是 file_get_content 我建议你使用vfsStream library。你可以找到一篇关于使用它进行测试的好文章here。如果您需要一些帮助来将其集成到您的测试中,请告诉我。
  • 老实说,将该方法更改为 public 很好。这不是我面临的问题的原因(更新操作)。
  • 好的,我们可以对测试类进行部分模拟。好的?您需要一个工作示例吗?
  • 这就是我正在努力解决的问题。模拟被测类,而不实例化另一个扩展被测类的类。举个例子就好了。

标签: unit-testing testing mocking phpunit


【解决方案1】:

您可以使用部分模拟对象对其进行归档:您可以仅模拟您的类的特定方法并执行(和测试)其他方法。

进一步参考here

例如,假设您修改后的类:

<?php
namespace Acme\DemoBundle\Service;


class MyClass {

    public function myMethod()
    {
        $request = 'http://domain.com';
        $response = $this->someMethodUsingGlobals($request);
        // Do something with the response..

        return $response;
    }
    public function someMethodUsingGlobals($url)
    {
        return json_decode(file_get_contents($url),true)['results'][0];
    }
}

您可以使用以下测试类进行测试:

<?php
namespace Acme\DemoBundle\Tests;



class MyProjectTest extends \PHPUnit_Framework_TestCase
{

    public function test_it_does_something_with_the_response()
    {
        $sut = $this->getMock('Acme\DemoBundle\Service\MyClass', array('someMethodUsingGlobals') );

        // Set up the expectation for the someMethodUsingGlobals() method
        // to be called only once and with the string 'http://domain.com'
        // as its parameter.
        $sut->expects($this->once())
            ->method('someMethodUsingGlobals')
            ->with($this->equalTo('http://domain.com'))
            ->willReturn('Some expectation');

        $response = $sut->myMethod();


        $this->assertEquals('Some expectation', $response);
    }
}

所以someMethodUsingGlobals 方法没有被执行并返回模拟定义中定义的值。方法myMethod使用模拟函数执行和处理。

希望有帮助

【讨论】:

    【解决方案2】:

    解决方案:围绕原生函数做一个类包装器

    最简单和最干净的方法是围绕本机函数创建一个包装类。

    如果您遵循 DDD 和/或六边形架构,您可能会将其放置在“适配器”空间中,如果您不遵循 DDD 或 hex-arch,请将其放置在任何“接触外部”的类组之外世界”。

    这个包装器是一个单行类:

    <?php
    
    declare( strict_types = 1 );
    
    namespace MyVendor\MyProject\Adapters;
    
    class FileGetContentsWrapper
    {
        public function fileGetContents( string $filename )
        {
            return file_get_contents( $filename );
        }
    }
    

    这个类不能测试,因为它只是使用原生函数。

    但是有了它,您只需将“未测试”“转移”到这个单行类,您现在就可以让所有其他曾经使用 file_get_contents() 可测试的地方获得围绕代码的逻辑覆盖,除了文件读取。

    你原来的类,修改

    你是这样进行的:

    1. 您通过构造函数注入服务。
    2. 您将包装器视为服务。
    3. 如果您在其最新版本中使用 symfony 等框架,您可以使用自动装配进行注入以简化类的构建。

    例如,您的课程可能会导致:

    <?php
    
    namespace MyVendor\MyProject;
    
    use MyVendor\MyProject\Adapters\FileGetContentsWrapper;
    
    class MyClass
    {
        private $fileGetContentsWrapper;
    
        public function __construct( FileGetContentsWrapper $fileGetContentsWrapper )
        {
            $this->fileGetContentsWrapper = $fileGetContentsWrapper;
        }
    
        /* ... */
    
        public function someMethodUsingTheWrapper( $url )
        {
            $contents = $this->fileGetContents( $url );
            return json_decode( $contents, true )[ 'results' ][ 0 ];
        }
    }
    

    如何测试它

    您执行以下操作:

    1. 在测试类中,您为包装器设置了一个模拟。
    2. 在测试方法中,您可以将模拟配置为返回您需要的任何内容。
    3. 你正常调用你的类。

    例如:

    <?php
    
    declare( strict_types = 1 );
    
    namespace MyProjectTests;
    
    use MyVendor\MyProject\Adapters\FileGetContentsWrapper;
    use MyVendor\MyProject\MyClass;
    use PHPUnit\Framework\TestCase;
    
    class MyClassTest extends TestCase
    {
        private $fileGetContentsWrapper;
    
        //---------------------------------------------------------------------//
        // Setup                                                               //
        //---------------------------------------------------------------------//
    
        protected function setUp()
        {
            $this->fileGetContentsWrapper = $this->createMock( FileGetContentsWrapper::class )
    
            parent::setUp();
        }
    
        //---------------------------------------------------------------------//
        // Tests                                                               //
        //---------------------------------------------------------------------//
    
        public function testSomeMethodUsingTheWrapper()
        {
            $sut = $this->getSut();
    
            $someSimulatedJson = '{"results":["abc","xyz"]}';
            $this->fileGetContentsWrapper->method( 'fileGetContents' )->willReturn( $someSimulatedJson );
    
            $this->assertEquals( 'xyz', $sut->someMethodUsingGlobals( 'dummy-url' ) );
        }
    
        //---------------------------------------------------------------------//
        // Private                                                             //
        //---------------------------------------------------------------------//
    
        private function getSut() : MyClass
        {
            return new MyClass( $this->fileGetContentsWrapper );
        }
    }
    

    就是这样!希望有所帮助!

    【讨论】:

      猜你喜欢
      • 2014-07-07
      • 2015-04-29
      • 2019-04-21
      • 2011-08-19
      • 2017-11-29
      • 2018-02-21
      • 2014-08-21
      • 2014-07-21
      • 2014-12-31
      相关资源
      最近更新 更多