在PHPUnit中模拟/存根FTP操作 [英] Mocking/stubbing FTP operations in PHPUnit

查看:69
本文介绍了在PHPUnit中模拟/存根FTP操作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一般来说是一个相对较新的转换为单元测试的人,我在这里遇到了绊脚石:

I'm a relatively new convert to unit testing in general and I've run into a stumbling block here:

如何使用PHP的内置ftp函数测试连接到远程FTP服务器并在其上执行操作的代码?一些谷歌搜索为Java提供了一个快速的模拟选项( MockFtpServer ),但PHP暂时没有可用的东西.

How do I test code that connects to and performs operations on a remote FTP server using PHP's built-in ftp functions? Some googling turned up a quick mocking option for Java (MockFtpServer), but nothing readily available for PHP.

我怀疑答案可能是为PHP的ftp函数创建一个包装器类,该类可以随后被打桩/模拟以模仿成功/失败的ftp操作,但是我真的要感谢比我聪明的人的一些投入!

I have a suspicion the answer may be to create a wrapper class for PHP's ftp functions that can subsequently be stubbed/mocked to mimic successful/unsuccessful ftp operations, but I'd really appreciate some input from people who are smarter than me!

请注意,我一直在使用PHPUnit,并且特别需要有关该框架的帮助.

Please note that I've been working with PHPUnit and require assistance with that framework specifically.

根据@hakre的请求,我要测试的简化代码如下所示.我本质上是在问最好的测试方法:

As per the request from @hakre the simplified code I want to test looks like the below. I'm essentially asking the best way to test:

public function connect($conn_name, $opt=array())
{
  if ($this->ping($conn_name)) {
    return TRUE;
  }

  $r = FALSE;

  try {    
    if ($this->conns[$conn_name] = ftp_connect($opt['host'])) {
      ftp_login($this->conns[$conn_name], $opt['user'], $opt['pass']);
    }
    $r = TRUE;
  } catch(FtpException $e) {
    // there was a problem with the ftp operation and the
    // custom error handler threw an exception
  }

  return $r;
}

更新/解决方案摘要

问题摘要

我不确定如何隔离测试需要与远程FTP服务器通信的方法.您应该如何测试能够连接到您无法控制的外部资源,对吗?

I wasn't sure how to test methods in isolation that required communicating with a remote FTP server. How are you supposed to test being able to connect to an outside resource you have no control over, right?

解决方案摘要

为FTP操作创建适配器类(方法:连接,ping等).然后,在测试使用适配器执行FTP操作的其他代码时,可以很容易地对适配器类进行打桩,以返回特定的值.

Create an adapter class for the FTP operations, (methods: connect, ping, etc). This adapter class is then easily stubbed to return specific values when testing other code that uses the adapter to perform FTP operations.

更新2

我最近遇到了一个在测试中使用名称空间的妙招,它允许您模拟" PHP的内置函数.虽然在我的特定情况下使用适配器是正确的方法,但在类似情况下这可能对其他人有帮助:

I recently came across a nifty trick using namespaces in your tests that allows you to "mock" PHP's built-in functions. While the adapter was the right way to go in my particular case, this may be helpful to others in similar situations:

模拟php全局函数进行单元测试

推荐答案

我想到了两种方法:

  1. 为您的FTP类创建两个适配器:

  1. Create two adapters for your FTP class:

  1. 使用PHP的ftp函数连接到远程服务器等的真实"主机.
  2. 实际上并没有连接到任何东西,只返回种子数据的模拟"对象.

  1. The "real" one that uses PHP's ftp functions to connect to the remote server, etc.
  2. A "mock" one that does not actually connect to anything and only returns seeded data.

FTP类的connect()方法如下:

The FTP class' connect() method then looks like this:

public function connect($name, $opt=array())
{
  return $this->getAdapter()->connect($name, $opt);
}

模拟适配器可能看起来像这样:

The mock adapter might look something like this:

class FTPMockAdapter
  implements IFTPAdapter
{
  protected $_seeded = array();

  public function connect($name, $opt=array())
  {
    return $this->_seeded['connect'][serialize(compact('name', 'opt'))];
  }

  public function seed($data, $method, $opt)
  {
    $this->_seeded[$method][serialize($opt)] = $data;
  }
}

在测试中,您将随后为适配器添加结果,并验证是否正确调用了connect():

In your test, you would then seed the adapter with a result and verify that connect() is getting called appropriately:

public function setUp(  )
{
  $this->_adapter = new FTPMockAdapter();
  $this->_fixture->setAdapter($this->_adapter);
}

/** This test is worthless for testing the FTP class, as it
 *    basically only tests the mock adapter, but hopefully
 *    it at least illustrates the idea at work.
 */
public function testConnect(  )
{
  $name    = '...';
  $opt     = array(...);
  $success = true

  // Seed the connection response to the adapter.
  $this->_adapter->seed($success, 'connect', compact('name', 'opt'));

  // Invoke the fixture's connect() method and make sure it invokes the
  //  adapter properly.
  $this->assertEquals($success, $this->_fixture->connect($name, $opt),
    'Expected connect() to connect to correct server.'
  );
}

在上述测试案例中,setUp()注入了模拟适配器,以便测试可以调用FTP类的connect()方法,而无需实际触发FTP连接.然后,该测试将为适配器植入种子,如果使用正确的参数调用适配器的connect()方法,结果将仅返回 .

In the above test case, setUp() injects the mock adapter so that tests can invoke the FTP class' connect() method without actually triggering an FTP connection. The test then seeds the adapter with a result that will only be returned if the adapter's connect() method were called with the correct parameters.

此方法的优点在于,您可以自定义模拟对象的行为,如果需要在多个测试用例中使用模拟,则可以将所有代码都放在一个位置.

The advantage to this method is that you can customize the behavior of the mock object, and it keeps all of the code in one place if you need to use the mock across several test cases.

此方法的缺点是您必须复制许多已经构建的功能(请参阅方法2),并且可以说现在已经引入了另一个必须为其编写测试的类.

The disadvantages to this method are that you have to duplicate a lot of functionality that has already been built (see approach #2), and arguably you've now introduced another class that you have to write tests for.

一种替代方法是使用PHPUnit的模拟框架在测试中创建动态模拟对象.您仍然需要注入适配器,但可以即时创建它:

An alternative is to use PHPUnit's mocking framework to create dynamic mock objects in your test. You'll still need to inject an adapter, but you can create it on-the-fly:

public function setUp(  )
{
  $this->_adapter = $this->getMock('FTPAdapter');
  $this->_fixture->setAdapter($this->_adapter);
}

public function testConnect(  )
{
  $name = '...';
  $opt  = array(...);

  $this->_adapter
    ->expects($this->once())
    ->method('connect')
    ->with($this->equalTo($name), $this->equalTo($opt))
    ->will($this->returnValue(true));

  $this->assertTrue($this->_fixture->connect($name, $opt),
    'Expected connect() to connect to correct server.'
  );
}

请注意,上面的测试模拟了FTP类的 adapter ,而不是FTP类本身的,因为这样做很愚蠢.

Note that the above test mocks the adapter for the FTP class, not the FTP class itself, as that would be a rather silly thing to do.

此方法比以前的方法有优势:

This approach has advantages over the previous approach:

  • 您没有创建任何新类,并且PHPUnit的模拟框架具有自己的测试范围,因此您不必为模拟类编写测试.
  • 该测试可以作为幕后"情况的文档(尽管有些人可能认为这实际上不是一件好事).

这种方法有一些缺点:

  • 与以前的方法相比,它的速度(在性能方面)相当慢.
  • 在每个使用该模拟的测试中,您都必须编写很多额外的代码(尽管您可以重构常见的操作以减轻其中的某些负担).

请参见 http://phpunit.de /manual/current/en/test-doubles.html#test-doubles.mock-objects 了解更多信息.

这篇关于在PHPUnit中模拟/存根FTP操作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆