dqteh7347 2014-04-14 14:04 采纳率: 100%
浏览 28

如何在PHPUnit中存根更复杂的方法

I'm trying to reduce the dependencies of my program and make it more easily testable. One instance where I did this is in the __construct() method of one of my classes. Before, it used to take in a file name and then the __construct() method would use file_get_contents() on that filename to save the contents into a property:

public function __construct($name){
  $this->name = $name;
  $this->contents = file_get_contents($name);
}

To reduce dependency on the filesystem I replaced this with:

public function __construct(SplFileObject $file){
  $this->name = $file->getFilename();
  $this->contents = '';
  while(!$file->eof()){
    $this->contents .= $file->fgets();
  }
}

I believe that this is more easily testable, since I can mock up an SplFileObject (which could be set to contain whatever content I want) and pass it in. The examples I have seen so far involve doing something like this:

$stub = $this->getMock('SplFileObject');
$stub->expects($this->any())
     ->method('fgets')
     ->will($this->returnValue('contents of file'));

However the mock fgets method of the SplFileObject will need to be more complicated - it needs to loop through each line of the contents, and stop when it has reached the end.

For the time being I have a solution that works - I just created an entirely new class called MockSplFileObject which overrides these methods:

class MockSplFileObject extends SplFileObject{
    public $maxLines;
    public $filename;
    public $contents;
    public $currentLine = 1;

  public function __construct($filename, $contents){
    $this->filename = $filename;
    $this->contents = explode("
",$contents);
    return true;
  }

  public function eof(){
    if($this->currentLine == count($this->contents)+1){
        return true;
    }
    return false;
  }

  public function fgets(){
    $line = $this->contents[$this->currentLine-1];
    $this->currentLine++;
    return $line."
";
  }

  public function getFilename(){
    return $this->filename;
  }
}

I then use this instead of calling PHPUnit's getMock() function. My question is: is this a legitimate way of doing things? Or is there a better way of mocking up more complex methods?

  • 写回答

3条回答 默认 最新

  • doutuo3935 2014-04-14 15:47
    关注

    What you try to do is stubing internal function. Complexity of method doesn't have much to the problem. First solution is to throw away responsibility of reading file. Your class needs contents only and some name so deeper knowledge about file is not really needed (assumption). If any memory issues are not considered then i would use simple DTO object (simple object with getters and setters only) with name and contents. I assume that your class is not responsible for reading file... Then you can simply put the filled DTO object as dependency in constructor without any concern. Your solution needs the file mock to be unit tested as the normal Domain Class...

    Second solution is to extract file_get_contents into method like

    public function __construct($name){
        $this->name = $name;
        $this->contents = $this->getFileContents($name);
    }
    
    private function getFileContents($fileFullPath) {
        return file_get_contents($fileFullPath);
    }
    

    Then you can stub this function in mock and test the mock. This solution apply when you would like to stub some global state or static code.

    I would prefer first solution unless your class is responsible for reading file...

    Hope helpful

    评论

报告相同问题?

悬赏问题

  • ¥15 求帮我调试一下freefem代码
  • ¥15 R语言Rstudio突然无法启动
  • ¥15 关于#matlab#的问题:提取2个图像的变量作为另外一个图像像元的移动量,计算新的位置创建新的图像并提取第二个图像的变量到新的图像
  • ¥15 改算法,照着压缩包里边,参考其他代码封装的格式 写到main函数里
  • ¥15 用windows做服务的同志有吗
  • ¥60 求一个简单的网页(标签-安全|关键词-上传)
  • ¥35 lstm时间序列共享单车预测,loss值优化,参数优化算法
  • ¥15 Python中的request,如何使用ssr节点,通过代理requests网页。本人在泰国,需要用大陆ip才能玩网页游戏,合法合规。
  • ¥100 为什么这个恒流源电路不能恒流?
  • ¥15 有偿求跨组件数据流路径图