2016-07-30 55 views
2

在單元測試中,根據toString()返回的字符串測試返回值通常是個好主意嗎?使用toString()在Java中進行單元測試

例如,執行以下操作,以確保返回預期的列表:

assertEquals(someExpression.toString() ,"[a, b, c]"); 

在我看來,該事項如下:

優點:節省時間(建設實際預期值需要更長的代碼)。

缺點:測試依賴於toString(),它在文檔中沒有正式定義,因此可以在將來的任何版本中更改。

+1

如果'toString'是CUT的API的一部分,那麼測試'toString'是個好主意。否則,這是一個壞主意。 –

+0

@BoristheSpider它不是測試API的一部分。使用toString()是否是一個壞主意,因爲它可以在將來改變? – Ofer

+0

如果你的'toString()'將來會改變,你也需要改變你的測試。這就是使用單元測試的另一個好處。顯然,將列表與其toString進行比較是有缺陷的 –

回答

4

,我將測試上的對象的toString()唯一的一次是當我有沒有實現hashcodeequals的,不可編輯類,而是實施了toString()輸出其領域的內容。即使這樣,我也不會用硬編碼字符串作爲平等的測試,而是像做

SomeObject b = new SomeObject(expected, values, here); 
assertEquals(a.toString(), b.toString()); 

你的方法可能會節省時間開始,但是從長遠來看,這將需要更多的時間只是爲了由於您正在對toString()的預期結果字符串進行硬編碼,因此請保持該測試。

編輯1:當然,如果您正在測試輸出字符串的函數/進程,那麼您應該使用硬編碼字符串作爲預期結果的時間之一。

String input = "abcde"; 
String result = removeVowels(input); 
assertEquals(result, "bcd"); 
+0

「硬編碼的預期結果」 - 你是什麼意思?我以任何方式對預期結果進行硬編碼。你的意思是我從toString()硬編碼期望的字符串,這是一個壞主意,因爲(主要)它從來沒有正式的定義? – Ofer

+0

好吧,測試都是關於硬編碼過程的預期結果。但是,是的,我的意思是硬編碼的字符串。對於那個很抱歉。 一般來說,從toString()對期望的字符串進行硬編碼是一個壞主意,因爲您將它從當前實現的預期結果中提取出來,該預期結果可能(並且會在一段時間後)改變。 它也可能容易出錯,因爲測試創建者可能會因爲它是預期的字符串而放置正確的預期字符串。通過另一個對象的toString()來測試對象的toString()方法更加可靠。 – Kree

+0

好的,我無法編輯我以前的評論。通過「......不妨把正確的預期字符串放在正確的位置,因爲它是預期的字符串。」我的意思是硬編碼字符串不存在根據,除非它是預期的結果。如果toString突然改變了,你還必須改變測試,而在斷言中使用對象的toString()對維護測試的完整性沒有任何問題。現在我想到了,這不是真正的toString()被記錄或不記錄的問題。 – Kree

0

這是更好地覆蓋equalshashCode(自己或例如使用lomboks @EqualsAndHashCode爲),做使用它們比使用toString比較。通過這種方式,您可以明確地進行關於平等的測試,之後您可以重新使用這些方法

然後你可以做(​​以下與列表中爲例):

assertEquals(
    someExpression, 
    Arrays.asList(new Foo("a"), new Foo("b"), new Foo("c")) 
); 
+0

但假設構建對象需要更多的代碼,那麼使用toString()是否合法? (即使在一個標準的Java類的實例(如LinkedList) - 它的toString()沒有記錄) – Ofer

1

Assert.assertThat()方法有具體的集合和更多的匹配。

例如,作品類別:

List<String> someExpression = asList("a", "b", "c"); 
assertThat(someExpression.toString(), contains("a", "b", "c")); 

的優點是:

  • 你並不需要實現.toString();
  • 當測試失敗時,錯誤信息非常具有描述性,它會說「Expected collection containig'b'並且沒有找到它。」

嘗試:

import static org.junit.Assert.assertThat; 
import static org.hamcrest.Matchers.contains; 
import static java.util.Arrays.asList; 

List<String> someExpression = asList("a", "c"); 
assertThat(someExpression.toString(), contains("a", "b", "c")); 
+0

謝謝,列表只是一個例子。使用toString()而不是構建一個對象時通常合理,因爲它可以節省大量代碼? – Ofer

+1

你可以使用它,如果它可以節省你*很多時間。但它是一種脆弱的方法('.toString()'本身可能有錯誤併產生誤報!)。但總的來說,我會面臨這種需求/困難,作爲其他設計問題的可能跡象。爲什麼現有的API不夠?爲什麼你需要檢查這麼多的信息,是不是太多的實施細節?等等。沒有什麼是寫在石頭上的,所以你可以使用它,但我不會爲每個案例或新手提供建議 - 你必須真正理解它是一個次優的方法(有缺陷),然後才能使用它。 – acdcjunior

1

在特定情況下,我使用toString,特別是當等效代碼更加複雜時。隨着您獲得更復雜的數據結構,您需要快速測試整個結構。如果您編寫代碼來逐個測試,則可能會忘記添加字段。

對於這裏 https://vanilla-java.github.io/2016/03/23/Microservices-in-the-Chronicle-world-Part-1.html

TopOfBookPrice tobp = new TopOfBookPrice("Symbol", 123456789000L, 1.2345, 1_000_000, 1.235, 2_000_000); 
assertEquals("!TopOfBookPrice {\n" + 
     " symbol: Symbol,\n" + 
     " timestamp: 123456789000,\n" + 
     " buyPrice: 1.2345,\n" + 
     " buyQuantity: 1000000.0,\n" + 
     " sellPrice: 1.235,\n" + 
     " sellQuantity: 2000000.0\n" + 
     "}\n", tobp.toString()); 

測試失敗我的例子,爲什麼呢?您可以在IDE中很容易看到,因爲它產生

Display Comparison

當你獲得更復雜的例子,檢查每(嵌套)值,修正它,當它打破實在是乏味的更新版本。一個更長的例子是

assertEquals("--- !!meta-data #binary\n" + 
     "header: !SCQStore {\n" + 
     " wireType: !WireType BINARY,\n" + 
     " writePosition: 0,\n" + 
     " roll: !SCQSRoll {\n" + 
     " length: !int 86400000,\n" + 
     " format: yyyyMMdd,\n" + 
     " epoch: 0\n" + 
     " },\n" + 
     " indexing: !SCQSIndexing {\n" + 
     " indexCount: !short 16384,\n" + 
     " indexSpacing: 16,\n" + 
     " index2Index: 0,\n" + 
     " lastIndex: 0\n" + 
     " },\n" + 
     " lastAcknowledgedIndexReplicated: -1,\n" + 
     " recovery: !TimedStoreRecovery {\n" + 
     " timeStamp: 0\n" + 
     " }\n" + 
     "}\n" + 
     "# position: 344, header: 0\n" + 
     "--- !!data #binary\n" + 
     "msg: Hello world\n" + 
     "# position: 365, header: 1\n" + 
     "--- !!data #binary\n" + 
     "msg: Also hello world\n", Wires.fromSizePrefixedBlobs(mappedBytes.readPosition(0))); 

我有更長的例子,我這​​樣做。我不需要爲我檢查的每一個值寫一行,如果格式改變了,即使有一點我知道。我不希望格式意外改變。

注:我總是把期望值放在第一位。

1

在Java中

使用的單元測試的toString()如果你的單元測試,你的目的是要斷言對象的所有字段是平等的,你的對象的測試必須在toString()方法所以返回一個字符串,顯示所有字段的鍵值。
但是正如你強調的那樣,在這段時間裏,這個類的字段可能會改變,所以你的toString()方法可能不會反映實際字段,並且toString()方法不是爲它設計的。

測試依賴於toString(),它在文檔中沒有正式定義,因此可以在將來的任何版本中更改。

有替代toString()允許寫漂亮的代碼不
約束你保持toString()方法與所有鍵值領域退回或混合toString()初衷與目的斷言調試的目的。
此外,單元測試應記錄測試方法的行爲。
A碼爲下列不上預期的行爲自我解釋:

assertEquals(someExpression.toString() ,"[a, b, c]"); 

1)反射庫

的想法是混合Java反射機制JUnit的斷言機制(或任何測試單元您使用的API)
通過反思,您可以比較兩個相同類型對象的每個字段是否相等。這個想法如下:你建立一個錯誤的文本消息,指出兩個字段之間不相等的地方,並且出於某些原因。
當所有字段都通過反射進行比較時,如果錯誤消息不爲空,則使用單元測試機制向您之前構建的相關錯誤消息拋出失敗異常。
否則,斷言是成功的。 我經常在項目中使用它,當它變得相關時。
你可以自己做,也可以使用像Unitils這樣的API來完成這項工作。

User user1 = new User(1, "John", "Doe"); 
User user2 = new User(1, "John", "Doe"); 
ReflectionAssert.assertReflectionEquals(user1, user2); 

就個人而言,我創造了我自己的庫中斷言添加多個自定義的可能性來完成這項工作。 這不是很難做到,你可以從Unitils中獲得靈感。

2)單元測試匹配器庫

我認爲這是一個更好的選擇。
您可以使用Harmcrest或AssertJ。
更詳細的純反射,但它也有很大的優勢:

  • 它專門記錄了被測試方法所期望的行爲。
  • 它提供了流暢的方法來斷言
  • 它提供了多種斷言功能來降低鍋爐板代碼在單元測試中

隨着AssertJ,可以將這段代碼:

assertEquals(foo.toString() ,"[value1=a, value1=b, value1=c]"); 

其中foo是一個類的Foo實例定義爲:

public class Foo{ 

    private String value1; 
    private String value2; 
    private String value3; 

    // getters 
} 

通過某些東西:

Assertions.assertThat(someExpression) 
      .extracting(Foo::getValue1, Foo::getValue2, Foo::getValue3) 
      .containsExactly(a, b, c);