2012-01-18 193 views
2

目前,我試圖首次將單元測試應用到項目中。兩個問題出現了:單元測試中的依賴關係

  1. 它是壞的做法,如果多次測試彼此依賴?在下面的代碼中,幾個測試需要其他測試的結果是肯定的,這是一般的最佳做法嗎?

  2. 多遠,你去與嘲諷的對象SUT取決於?在下面的代碼中,'路由器'取決於'Route',這取決於'RouteParameter'。嘲笑,還是不嘲笑?

以下代碼是測試我的「路由器」對象,它接受通過Router::addRoute($route)和路由經由Router::route($url)一個URL的路由。

class RouterTest extends PHPUnit_Framework_TestCase { 
    protected function createSimpleRoute() { 
     $route = new \TNT\Core\Models\Route(); 
     $route->alias = 'alias'; 
     $route->route = 'route'; 
     $route->parameters = array(); 

     return $route; 
    } 

    protected function createAlphanumericRoute() { 
     $route = new \TNT\Core\Models\Route(); 
     $route->alias = 'alias'; 
     $route->route = 'test/[id]-[name]'; 

     $parameterId = new \TNT\Core\Models\RouteParameter(); 
     $parameterId->alias = 'id'; 
     $parameterId->expression = '[0-9]+'; 

     $parameterName = new \TNT\Core\Models\RouteParameter(); 
     $parameterName->alias = 'name'; 
     $parameterName->expression = '[a-zA-Z0-9-]+'; 

     $route->parameters = array($parameterId, $parameterName); 

     return $route; 
    } 

    public function testFilledAfterAdd() { 
     $router = new \TNT\Core\Helpers\Router(); 

     $router->addRoute($this->createSimpleRoute()); 

     $routes = $router->getAllRoutes(); 

     $this->assertEquals(count($routes), 1); 

     $this->assertEquals($routes[0], $this->createSimpleRoute()); 

     return $router; 
    } 

    /** 
    * @depends testFilledAfterAdd 
    */ 
    public function testOverwriteExistingRoute($router) { 
     $router->addRoute(clone $this->createSimpleRoute()); 

     $this->assertEquals(count($router->getAllRoutes()), 1); 
    } 

    /** 
    * @depends testFilledAfterAdd 
    */ 
    public function testSimpleRouting($router) { 
     $this->assertEquals($router->route('route'), $this->createSimpleRoute()); 
    } 

    /** 
    * @depends testFilledAfterAdd 
    */ 
    public function testAlphanumericRouting($router) { 
     $router->addRoute($this->createAlphanumericRoute()); 

     $found = $router->route('test/123-Blaat-and-Blaat'); 

     $data = array('id' => 123, 'name' => 'Blaat-and-Blaat'); 

     $this->assertEquals($found->data, $data); 
    } 

    /** 
    * @expectedException TNT\Core\Exceptions\RouteNotFoundException 
    */ 
    public function testNonExistingRoute() { 
     $router = new \TNT\Core\Helpers\Router(); 

     $router->route('not_a_route'); 
    } 
} 

回答

5

1)是的,如果測試相互依賴,那肯定是一種不好的做法。

一個單元測試應該以這樣的方式,當它馬上失敗,它指向你的代碼中的特定區域來構建。良好的單元測試會減少您花費在調試上的時間。如果測試相互依賴,您將失去此優勢,因爲您無法確定代碼中的哪個錯誤使測試失敗。而且,這是一個維護噩夢。如果在「共享測試」中發生了某些變化,那麼您將不得不更改所有依賴測試。

Here你可以找到關於如何解決交互測試問題的一些很好的指導(整個的xUnit測試模式書是必讀!)

2)單元測試是關於測試是最小的事情成爲可能。

假設你有一個鬧鐘(C#代碼):

public class AlarmClock 
{ 
    public AlarmClock() 
    { 
     SatelliteSyncService = new SatelliteSyncService(); 
     HardwareClient = new HardwareClient(); 
    } 

    public void Execute() 
    { 
     HardwareClient.DisplayTime = SatelliteSyncService.GetTime(); 

     // Check for active alarms 
     // .... 
    } 
} 

這不是測試。您將需要一個真正的衛星連接和一個硬件客戶端來檢查是否設置了正確的時間。

的不過以下會讓你嘲笑都hardwareClient和satelliteSyncService。你應該從不嘲笑你實際測試的對象(聽起來合乎邏輯,但有時候我看到它發生了)。

那麼,有多遠你應該去嘲弄。你應該嘲笑你的測試所依賴的類。通過這種方式,你可以完全隔離地測試你的班級。你可以控制你的依賴關係的結果,這樣你就可以確保你的SUT會遍歷所有的代碼路徑。

例如,讓SatelliteSyncService發生異常,讓它返回一個無效時間,然後讓它返回正確的時間,然後在特定時刻讓您測試您的鬧鐘是否在正確的時刻激活。

用於創建路由的測試數據。考慮使用Builder Pattern.這將幫助您僅設置測試成功所需的內容。它會讓你的測試更具表現力,更易於閱讀。它還會降低您的測試維護,因爲您擁有較少的依賴關係。

我寫了一個關於blog post上這裏所說的思想擴展單元測試。它使用C#來解釋概念,但它適用於所有語言。

+0

+1,還有很多更多要說的測試和嘲弄,但這是一個很好的介紹 – Guillaume 2012-01-18 10:19:37

+0

這聽起來合乎邏輯的,感謝這個明確的解釋! – Thanaton 2012-01-18 10:22:15

0

您的示例不顯示相互測試,而是使用相關類RouteRouteParameter的類Router的單個測試。由於這些人都持有的數據,我也沒問題,在該Router測試中使用它們。

使用mock對象進行測試時,作爲沃特,德科爾特指出,是非常有益的,但要記住,有取捨。該測試可能更難閱讀和維護,因爲測試代碼不具有其自己的測試,任何複雜的風險。

+0

在我的例子中,一些測試依賴於對方。例如'testOverwriteExistingRoute'取決於'testFilledAfterAdd'。我解釋錯了嗎,還是這不被認爲是真正的'依賴'?測試依賴性意味着多個使用對方的測試類? – Thanaton 2012-01-18 22:27:31

+0

測試依賴性使用'@depends X'註釋,意思是「試驗X必須通過或測試Ÿ將被跳過」,進一步,無論是通過測試X返回將被傳遞到測試Y.儘管這往往使測試通常表明更復雜的是,如果謹慎使用它會很有用。 – 2012-01-18 22:51:48