搜索

首页  >  问答  >  正文

在 PHPUnit 中,如何模拟不属于类的函数?

我目前正在从事的项目包含面向对象和过程化 PHP 代码的混合体。所以我有这样的东西:

function doStuff($value)
{
    $x = $value + 1;

    return $x;
}

class MyClass
{
    private $field;

    public function setMyValue($amount)
    {
        $this->field = doStuff($amount) + doStuff(2 * $amount);
    }
}

这些依赖项有几个,但是很少(一只手就能数出来)。但是,我需要为类编写单元测试(使用 PHPUnit),并且我不知道如何模拟来自程序端的函数(在本例中为 doStuff)。据我所知,PHPUnit 中的模拟功能仅适用于类。

我会在没有任何模拟的情况下完成它,但问题是其中一些函数执行一些 IO 操作;我认为不以某种方式嘲笑他们并不是一个好主意。

我该如何解决这个问题?

P粉216807924P粉216807924411 天前687

全部回复(2)我来回复

  • P粉275883973

    P粉2758839732023-10-30 09:33:53

    我看到的唯一选项是依赖注入,因为您的类想要使用类外部的资源。因此,这打破了一些封装规则。

    我过去是如何做到这一点的,是将这些函数放在自己的类中并要求/包含它们,并且当设置测试变量时,包含一个具有相同的半“模拟”函数的基本文件,该文件返回已知状态。

    我的另一种方法是创建一个简单的 UTILITY 类,其中包含所有这些数据函数,然后使用依赖项注入和模拟来进行测试。

    class Utilities
    {
    
        function doStuff($value)
        {
            $x = $value + 1;
            return $x;
        }
    }
    
    class MyClass
    {
        private $UtilitiesObject;
    
        private $field;
    
        public function setMyValue($amount)
        {
    //        $this->field = doStuff($amount) + doStuff(2 * $amount);
            $this->field = $this->UtilitiesObject->doStuff($amount) + $this->UtilitiesObject->doStuff(2 * $amount);
        }
    }
    
        // Constructor Injection, pass the Utilities object here
        public function __construct($Utilities = NULL)
        {
            if(! is_null($Utilities) )
            {
                if($Utilities instanceof Utilities)
                {
                    $this->SetUtilities($Utilities);
                }
            }
        }
    
        function SetUtilities(Utilities $Utilities)
        {
            $this->UtilitiesObject = $Utilities
        }
    
    }

    测试:

    class UtilitiesTest extends PHPUnit_Framework_TestCase
    {
    
        // Could also use dataProvider to send different returnValues, and then check with Asserts.
        public function testSetMyValue()
        {
            // Create a mock for the Utilities class,
            // only mock the doStuff() method.
            $MockUtilities = $this->getMock('Utilities', array('doStuff'));
    
            // Set up the expectation for the doStuff() method 
            $MockUtilities->expects($this->any())
                        ->method('doStuff')
                        ->will($this->returnValue(1));
    
            // Create Test Object - Pass our Mock as the Utilities
            $TestClass = new MyClass($MockUtilities);
            // Or
            // $TestClass = new MyClass();
            // $TestClass->SetUtilitiess($MockUtilities);
    
            // Test doStuff
            $amount = 10;   // Could be checked with the Mock functions
            $this->assertEquals(2, $TestClass->doStuff($amount));       // Mock always returns 1, so 1+1=2
        }
    }

    回复
    0
  • P粉155832941

    P粉1558329412023-10-30 00:12:07

    当您从名称空间调用函数(在全局名称空间中定义)并始终将它们称为不合格的函数时,您可以利用 PHP 的命名空间回退策略

    这允许您通过在调用者的命名空间内提供函数来创建模拟。

    为了让您的生活变得特别轻松,我将其打包到库中 php-mock- phpunit 可以与 PHPUnit 一起使用:

    namespace foo;
    
    use phpmock\phpunit\PHPMock;
    
    class BuiltinTest extends \PHPUnit_Framework_TestCase
    {
    
        use PHPMock;
    
        public function testTime()
        {
            $time = $this->getFunctionMock(__NAMESPACE__, "time");
            $time->expects($this->once())->willReturn(3);
    
            $this->assertEquals(3, time());
        }
    }

    回复
    0
  • 取消回复