4

这是我正在单元测试的课程。目前我正在测试该doSomething功能:

class FooClass {
  public function doSomething( $user ) {
    $conn = $this->getUniqueConnection( $user->id );
    $conn->doSomethingDestructive();
  }

  private function getUniqueConnection( $id ) {
    return new UniqueConnection( $id );
  }
}

如您所见,该函数根据它接收到的参数的属性获取(我不在此处测试的类)doSomething的新实例。UniqueConnection问题是该UniqueConnection:: doSomethingDestructive方法是我在测试期间无法调用的东西,因为它...具有破坏性。所以我想存根/模拟UniqueConnection而不是使用真实的。

我看不到任何方法可以注入我的 mocked UniqueConnection。我会UniqueConnection为构造函数创建一个参数,FooClass但是,正如您所看到的,一个新的参数是基于doSomething函数的参数创建的,并且它可能被调用的所有唯一 ID 都是提前不知道的。

我能看到的唯一选择是测试一个模拟FooClass而不是FooClass它本身。然后我将用getUniqueConnection返回模拟/存根的函数替换该函数。测试模拟似乎很糟糕,但我看不出有任何方法可以实现我所追求的目标。 UniqueConnection是第三方供应商库,无法修改。

4

5 回答 5

4

你可以创建一个UniqueConnectionFactory, 并将它的一个实例传递给 FooClass。然后你有

  private function getUniqueConnection( $id ) {
    return $this->uniqueConnectionFactory->create( $id );
  }

一般来说,这是使用工厂的好处之一——您将new操作员排除在类之外,这使您可以更轻松地改变正在创建的对象。

于 2012-08-13T23:47:28.693 回答
2

在您可以按照 rambo coder 的建议花时间重构代码以使用工厂之前,您可以使用部分模拟来返回非破坏性的唯一连接。当你发现自己处于这个位置时,通常意味着被测试的班级有不止一项责任。

function testSomething() {
    $mockConn = $this->getMock('UniqueConnection');
    $mockConn->expects($this->once())
             ->method('doSomethingDestructive')
             ->will(...);
    $mockFoo = $this->getMock('FooClass', array('getUniqueConnection'));
    $mockFoo->expects($this->once())
            ->method('getUniqueConnection')
            ->will($this->returnValue($mockConn));
    $mockFoo->doSomething();
}
于 2012-08-14T08:46:36.287 回答
2

就像 Rambo Coder 所说的那样,这是在课堂上做太多事情的问题。我不会想创建一个工厂,特别是如果您只创建一个特定类的实例。最简单的解决方案是颠倒创建 UniqueConnection 的职责:

<?php
class FooClass {
  public function doSomething( UniqueConnection $connection ) {
    $connection->doSomethingDestructive( );
  }
}

在测试时传递一个模拟,new UniqueConnection( $user->id )在真实代码中传递一个..

于 2012-08-14T08:51:54.167 回答
0

在某些情况下,以支持不同执行模式的方式创建类非常重要。其中一种情况就是您所要求的。

创建您的类以支持各种模式。例如

Class Connection {
    private $mode;

    public function setMode($mode) {
         $this -> $mode = $mode;
    }
}

现在,您doSomethingDestructive可以按照执行模式进行操作。

public function doSomethingDestructive() {
    if($this -> mode === "test") { //if we are in a test mode scenario
        //Log something
        // Or just do some logging and give a message
    } else {
        // do what it was suppose to do
    }
}

下次,当您测试该类时,您不必担心破坏性函数会意外破坏。

  public function doSomething( $user ) {
    $conn = $this->getUniqueConnection( $user->id );
    $conn -> setMode("test"); //Now we are safe
    $conn->doSomethingDestructive(); //But the Testing is still being Ran
  }
于 2012-08-13T23:54:45.460 回答
0

在这种情况下,您想要的不是模拟对象,而是测试子类。将您$conn->doSomethingDestructive();分解为一个方法,然后将其子类FooClassTestFooClass并覆盖子类中的新方法。然后,您可以使用子类进行测试,而不会出现不需要的破坏性行为。

例如:

class FooClass {
  public function doSomething( $user ) {
    $conn = $this->getUniqueConnection( $user->id );
    $this->connDoSomethingDestructive($conn);
  }

  protected function connDoSomethingDestructive($conn) {
    $conn->doSomethingDestructive();
  }

  private function getUniqueConnection( $id ) {
    return new UniqueConnection( $id );
  }
}

class TestFooClass extends FooClass {
  protected function connDoSomethingDestructive() {

  }

  private function getUniqueConnection( $id ) {
    return new MockUniqueConnection( $id );
  }
}
于 2012-08-14T00:02:23.317 回答