2012-07-13 82 views
4

我寫單元測試我們的主要產品,並有一個擔憂:如何區分語義對損壞的單元測試

  • 測試失敗,因爲他們測試了錯誤(錯誤被發現和非迴歸測試它的實例)
  • 測試失敗,因爲測試的另一個意想不到的部分失敗(因爲測試是錯誤的或未知的bug出現)

這是第一個,我們對JUnit的斷言框架當然,但是我們對第二個有什麼?例如:我的單元測試正在測試c()不會拋出MyException,而是執行c()我需要先執行一個()然後b(),它們都可以拋出MyException(),所以我會寫:

@Test 
public void testC() { 
    a(); 
    Object forC = b(); 
    try { 
    c(forC); 
    } catch (MyException e) { 
    Assert.fail("...."); 
    } 
} 

但是然後我需要處理可以由a或b拋出的MyException,並且還處理了forC不應該爲null的事實。做這個的最好方式是什麼?

  • Catch MyException拋出a或b和Assert.fail,但a和b沒有通過這個測試測試,所以對我來說,他們不應該被標記爲失敗時測試失敗。也許他們以後失敗,因爲在這個時候我們應該做b(); a()不是a(); b();.
  • 讓testC拋出MyException,這樣測試將失敗並顯示「MyException」,但這是誤導,因爲MyException不會告訴測試寫錯了。所有的測試都會失敗,每個都有自己的異常。在這種情況下,如果forC爲空(也沒有語義),我還需要拋出類似NullPointerException的內容。
  • 將a和b拋出的異常捕獲幷包裝成異常,告訴測試可能是錯誤的,就像TestCorruptedException。但是我不能在jUnit中找到這樣的異常,所以它們不會被jUnit識別(這對我來說沒問題)。另外我需要從我所有的單元測試中知道這個異常,當然這些單元測試分爲多個模塊,項目等等。所以這是可行的,但是增加了依賴性。

你會怎樣解決這個問題?如上所述,我可能會參加第二場比賽,但我對此並不滿意。

回答

5

單元測試的基石是測試必需的最小代碼量,否則它可以歸入您正在尋找端到端功能的集成測試空間。

如果你能證明a()可以創建並在它自己的測試類測試,並b()也可以創建並在它自己的測試類測試,那麼它遵循你的測試上面可以忽略的a()測試和b(),而是使用不會失敗的已知值。這通常通過使用mock objects來滿足。

使用作爲模擬對象創建的()和b(),可以單獨測試c()。如果你發現不可能在()和b()的隔離中測試c(),那麼這是你的代碼需要改變的一個指標,以便關注點分離。這通常由()和b()到c()的injecting the dependany滿足。

when to use mock objects in unit testing上的這篇文章可能會幫助我們更多地瞭解這個主題。

+0

正確的,我的問題是確實是c是緊密相連的一個()因爲它們的真名是startTransaction()和commit(),所以它們具有公共結構和鎖。你的回答告訴我,我正在進行集成測試,所以我註定要失敗,只能祈求a和b是正確的。它太過努力讓他們獨立,但這對我來說是解決問題的方法。 – jolivier 2012-07-13 17:00:54

+0

這就是像[Spring的註解驅動的事務管理](http://static.springsource.org/spring/docs/3.0.x/reference/transaction.html#transaction- declarative-annotations)這樣的聲明式事務管理真正發光的地方。您的方法c()可以拋棄對startTransaction()和commit()的調用,讓您可以自由地對其進行測試。 c()不會在意它是否在事務中,你會在調用方法中捕獲任何異常。這裏是一個[小型教程](http://www.javacodegeeks.com/2011/09/spring-declarative-transactions-example.html)感興趣 – Brad 2012-07-13 20:36:34

0

着名的問題:誰來測試我們的測試?簡短的回答:你無法區分。您可以始終定義測試之間的依賴關係(在某些測試框架中)。當一個測試失敗時,依賴於它的所有測試都不會執行。你永遠不會知道你的測試代碼是否正確。這就是爲什麼它應該儘可能簡單。還應該在修復代碼之前執行它 - 這樣,當您認爲代碼已修復而實際上測試通過時可以避免出現這種情況,因爲它是錯誤的。

0

很酷的問題! :)

每個單元測試我寫遵循此結構:

// Given 
setup for the test 

// When 
what i specifically test 

// Then 
my assertions 

在你的榜樣,我認爲什麼是之前的嘗試是建立。你需要打電話給你的測試做準備,沒關係。

但如果你做得好,a();和b();在別處進行測試。所以如果在這些方法中有一個錯誤,其他測試,他們的測試將會中斷。這就是你如何區分它突破的地方。

給定和然後應該永遠不會中斷。你正在測試什麼時候。

如果你不確定()和b(),嘲笑他們。

而且,最後一種情況下,如果您想區分例外情況,請斷言該消息。

1

一種明智的字 - 永遠不會做這在單元測試

try { 
    c(forC); 
    } catch (MyException e) { 
    Assert.fail("...."); 
    } 

這對任何人誰永遠不會有對你的代碼工作真的很煩人的副作用:的初始原因異常丟失。這意味着修復開發者將進入並調試測試或移除try/catch塊來查看異常的根本原因。

如果測試引發異常,應該不會,只是讓它。測試將失敗,測試運行的開發人員可以很容易地在控制檯/日誌中看到首先導致異常的原因。接受@piotrek提供的建議,儘可能簡化測試。我不能指望我有多少次不得不從格式錯誤的測試中獲得這個構造。

0

只是不要捕捉異常,但把它們從你的測試方法中拋出。 JUnit會顯示測試失敗和測試誤差之間的區別:

public void test() throws MyException { 
    boolean result = instance.doStuff(); 
    Assert.true(result); 
} 

當你期待一個例外,這樣做:

public void test() { 
    try { 
     instance.doStuff(); 
     Assert.fail("Expected MyException"); 
    } 
    catch(MyException e) { 
     // expected 
    } 
} 
+0

您預期的異常方法有點過時,請使用@Test(預期= SomeException.class)用於期望的異常,其代碼少且易出錯--OP使用JUnit 4。 – 2012-07-20 15:47:47