單元測試中的Fluent Interface
測試的重要性是每個程序員都明白的, 但真正自己去做測試(Unit Test)的卻很少, 曾經我也是其中的一員.
因為寫個main調用一些方法, 打印出結果或狀態, 然后人工肉眼去排查, 若不是迫于無奈, 我相信沒有程序員愿意糾結于這些瑣碎的東西.
其實, 測試本可以很有趣的.借助JUnit, 我們可以將測試按不同的場景組織起來, 在”一鍵”之后的紅綠條的反饋下, 快速解決代碼中存在的問題. 如果你還不太了解JUnit, 請先去這里. 后文將以JUnit為基礎, 以Fluent Interface(這個在國內還比較時髦的術語)為切入點, 展示一下更有趣的測試.
在解釋什么是Fluent Interface之前, 請先看這樣一段測試代碼:
public class Calculator { public int sum(int one, int other); } public class CalculatorTest { private final Calculator calc = new Calcuator(); @Test public void 08 onePlusOne() { assertEquals(2, calc.sum(1, 1)); } } |
上述代碼是基于JUnit4編寫的, 用assertEquals來測試Calculator的sum方法對一加一計算的結果. 這種寫法很簡單, 但從語義上并不是那么流暢, 若換種寫法, 如:
public class CalculatorTest { [...] @Test public void assertThatOnePlusOneIsEqualToTwo() { assertThat(calc.sum(1, 1)).isEqualTo(2); } } |
這樣閱讀起來是否感到更為清晰呢? 若是將語句中的符號換成空格:
assert that calc sum 1 and 1 is equal to 2
這幾乎就是人類的自然語言了(囧, 盡管是e文).
也許這個例子只是讓大家看到易讀性的優勢, 那么再看看下面這個易編寫的例子:
public interface Querier { Collection<String> findNameBy(int age); } public class OrderQuerier { private final Querier querier = [...] @Test public void findNamesWithAgeInThirty() { Collection<String> names = querier.findNameBy(30); assertEquals(2, names.size()); assertTrue(names.contains("allen")); assertTrue(names.contains("john")); } @Test public void findNamesWithAgeInThirty() { Collection<String> names = querier.findNameBy(30); assertThat(names).hasSize(2).contains("allen", "john"); } } |
怎么樣, 上面這個對比下, 后者是否能讓你感到”清爽”呢?
assertThat風格的assert正是應用了Fluent Interface, 使得測試的代碼流暢易讀, 編寫簡單.
Fluent interface可以看作是借用了Method Chaining來實現的一種Internal DSL(Domain-Specific Language), 關于它這兒有更為全面的介紹.
前面展示的assertThat僅是FEST-Assert提供一組API的很小一部分, 它還支持其它的:
Primary Type
Object
Array
Iterator
Throwable
File
Map
除了FEST-Assert, 其實還有另一個在JUnit測試中被廣泛應用的”assertThat”——hamcrest, 它使用靜態導入加工廠方法實現的Internal DSL, 同樣很有趣的, 不妨look一下.