2013-07-09 56 views
2

我有類似這樣的(爲簡潔一些邏輯刪除)類:如何測試一個集合類使用PHPUnit

class FooCollection { 
    protected $_foos; 

    public function __construct() { 
     $this->_foos = new SplObjectStorage(); 
    } 

    public function addFoo(FooInterface $foo) { 
     $this->_foos->attach($foo); 
    } 

    public function removeFoo(FooInterface $foo) { 
     $this->_foos->detach($foo); 
    } 
} 

我想用PHPUnit的測試addFoo()removeFoo()方法,我是想知道做這件事最好的策略是什麼?據我所知,我只有幾個選項:

  1. 添加一個方法hasFoo(FooInterface $foo)並檢查後添加。
  2. 添加方法getFoos(),直接返回SplObjectStorage實例,並檢查添加後是否在$foo之內。
  3. 嘗試removeFoo($foo)addFoo($foo)並檢查是否有異常。
  4. $_foos設爲公共屬性,並在添加後直接檢查(壞,壞,壞...)。

選項#1和#2正在改變公共界面僅用於測試目的,我不確定我對此的看法。它們看起來像是非常普通且有用的方法,但在我的具體情況中,我從來沒有必要檢查集合中是否存在特定的實例,也沒有檢索所有實例,所以它真的只會膨脹。另外,似乎如果我在一次測試中測試接口的多個部分,我並不真正測試一個「單元」,但這或多或少只是一個哲學掛斷。

選項#3對我來說似乎很尷尬。

選項#4是一個非常糟糕的主意,我不應該把它列出來,因爲即使它在這裏被建議,我也不會這樣做。

+0

我敢肯定,集合類有一些返回集合中的項目。否則,班級將是無用的。並且不要改變你的代碼以便能夠測試它(除非你以其他方式搞砸了你的設計,以防止你測試它) – PeeHaa

+0

@PeeHaa:它沒有包含任何檢索項目的方法(因爲存在不需要這樣做)。它確實包含了對集合進行*操作的方法(我忽略了這些方法,因爲它們不相關),這就是爲什麼它不是「無用」類。 – FtDRbwLXw6

+0

有點不相干,但在我的辯護中,你可能想給你的班級在那種情況下更好的名字;) – PeeHaa

回答

0

你沒有發佈一個公共訪問器來獲取集合,但我相信你有一個,否則添加/刪除foos到一個公共無法訪問的數組是沒有意義的。所以,你可以嘗試像(PHPUnit的3.6,PHP 5.4):

public function setUp() 
{ 
    $this->NumbersCollection = new NumbersCollection; 
} 

public function tearDown() 
{ 
    unset($this->NumbersCollection); 
} 

public function testNumbersCollection() 
{ 
    $this->NumbersCollection->addNumber(1); 
    $this->NumbersCollection->addNumber(2); 

    $this->assertSame(3, $this->NumbersCollection->sum()); 
    $this->assertSame(2, $this->NumbersCollection->product()); 

    $this->NumbersCollection->removeNumber(1); 

    $this->NumbersCollection->addNumber(7); 

    $this->assertSame(9, $this->NumbersCollection->sum()); 
    $this->assertSame(14, $this->NumbersCollection->product()); 
} 
+0

還有其他方法對集合進行操作(爲簡潔起見我省略),但沒有任何東西可以檢索對象,因爲從來沒有必要再把它們拉回來(除了我在問題中提到的測試)。該類的功能是接受對象,並將它們作爲集合執行某些操作。 – FtDRbwLXw6

+0

沒錯,但是你不想在某個時候回收藏品嗎?爲什麼要編寫影響集合中對象的邏輯,但從不返回或使用它們? –

+0

集合的方法對項目執行操作並返回結果,但不需要自行返回項目。對不起,我不能更好地解釋細節(NDA)。如果考慮一個數字集合,可能會更好地解釋:'$ numbers = new NumbersCollection(); $ numbers-> addNumber(1); $ numbers-> addNumber(2); $ sum = $ numbers-> sum(); $ product = $ numbers-> product();'等等。我保證,這不是一個毫無價值的課程。 :-) – FtDRbwLXw6

5

爲什麼不創建您在傳遞給構造一個模擬SplObjectStorage對象?然後你可以斷言在模擬上調用了attachdetach方法。

function testAttachFOO() { 
    $mockStorage = $this->getMockBuilder('SplObjectStorage') 
        ->setMethods(array('attach')) 
        ->getMock(); 

    $mockFoo = $this->getMock('FooInterface'); 

    $mockStorage->expects($this->once()) 
     ->method('attach') 
     ->with($mockFoo); 

    $collection = new FooCollection($mockStorage); 

    $collection->addFoo($mockFoo); 
} 

removeFoo類似的東西。

這樣做的確需要您更改構造函數,以便可以注入依賴項。但是IMO使得代碼更清晰。也使測試更容易。

所以構造變爲:

public function __construct(SPLObjectStorage $storage) { 
    $this->_foos = $storage; 
} 

如果類變得很難建立這樣做,這是一個跡象,表明類是做得太多了,應該重構到更多更小的類。

+0

你能演示一些你會在這個例子中使用的斷言嗎? –

+0

根據您擁有的代碼,沒有其他斷言。我的例子測試有兩個。 'expect()'和'with()'是斷言模擬中發生了一些事情。我斷言該方法只被調用一次,並斷言該方法調用是由一個特定的參數進行的。 – Schleis

+0

有趣,有道理。 –