Heim  >  Fragen und Antworten  >  Hauptteil

Wie kann man in PHPUnit eine Funktion verspotten, die nicht Teil einer Klasse ist?

Das Projekt, an dem ich gerade arbeite, enthält eine Mischung aus objektorientiertem und prozeduralem PHP-Code. Also ich habe so etwas:

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

    return $x;
}

class MyClass
{
    private $field;

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

Es gibt einige dieser Abhängigkeiten, aber nur sehr wenige (man kann sie an einer Hand abzählen). Allerdings muss ich Komponententests für die Klasse schreiben (mit PHPUnit) und weiß nicht, wie ich Funktionen vom Terminal aus verspotten kann (in diesem Fall doStuff). Soweit ich weiß, funktioniert die Mocking-Funktionalität in PHPUnit nur mit Klassen.

Ich hätte es ohne Verspottung gemacht, aber das Problem ist, dass einige dieser Funktionen einige IO-Operationen ausführen. Ich halte es nicht für eine gute Idee, sie nicht in irgendeiner Weise zu verspotten.

Wie kann ich dieses Problem lösen?

P粉216807924P粉216807924377 Tage vor665

Antworte allen(2)Ich werde antworten

  • 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
        }
    }

    Antwort
    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());
        }
    }

    Antwort
    0
  • StornierenAntwort