[英]Using toString() for unit testing in Java
在單元測試中,根據其toString()返回的字符串測試返回值通常是個好主意嗎?
例如,執行以下操作以確保返回預期的列表:
assertEquals(someExpression.toString() ,"[a, b, c]");
在我看來,考慮因素如下:
優點:節省時間(構建實際預期值需要更長的代碼)。
缺點:測試依賴於toString(),它在doc中沒有正式定義,因此可以在以后的任何版本中進行更改。
我唯一一次測試對象的toString()
是因為我有一個不能實現hashcode
或equals
的不可編輯的類,而是實現了一個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");
Assert.assertThat()
方法具有特定於集合的匹配器等等。
例如, contains()
適用於集合:
List<String> someExpression = asList("a", "b", "c");
assertThat(someExpression.toString(), contains("a", "b", "c"));
優點是:
.toString()
; 嘗試:
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"));
我在特定情況下使用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中輕松查看它產生的內容
當您獲得更復雜的示例時,檢查每個(嵌套)值並在以后中斷時進行更正非常繁瑣。 更長的例子是
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)));
我有更長的例子,我這樣做。 我不需要為我檢查的每個值寫一行,如果格式改變,甚至我知道它的一點點。 我不希望格式出乎意料地改變。
注意:我總是把預期值放在第一位。
使用toString()在Java中進行單元測試
如果在單元測試中,您的意圖是斷言對象的所有字段都是等於的,則測試對象的toString()
方法必須返回一個字符串,顯示所有字段的鍵值。
但正如你強調的那樣,在這個時候,類的字段可以改變,所以你的toString()
方法可能不會反映實際的字段,而toString()
方法也沒有為它設計。
測試依賴於toString(),它在doc中沒有正式定義,因此可以在以后的任何版本中進行更改。
toString()
還有其他選擇,可以在沒有的情況下編寫漂亮的代碼
限制你維護一個toString()
方法,返回所有鍵值字段或混合toString()
初始意圖用於調試目的與斷言目的。
此外,單元測試應記錄測試方法的行為。
以下代碼對預期行為不能自我解釋:
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);
最好覆蓋equals
和hashCode
(你自己或者使用lomboks @EqualsAndHashCode
)並使用它們比使用toString
進行比較。 通過這種方式,您可以明確地進行有關相等性的測試,然后可以重用這些方法。
然后你可以做(跟你的列表示例):
assertEquals(
someExpression,
Arrays.asList(new Foo("a"), new Foo("b"), new Foo("c"))
);
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.