Table of contents
1 Java 測試簡介
1.1 Development 既方式
Development 離不開做 testing 去保證 production code 既執行結果符合期望。Development 既同時做埋 automated testing 既方式有以下兩種:
方式名稱 | 描述 |
---|
Test-driven development(TDD) | 最 ideal 既做法係先寫 test code,寫曬我地期望既 given-when-then,因為有 production code 未寫而導致有 compilation error(例如 method 不存在)先再去寫 production code,當 production code 足以解決 compilation error 就再完成埋個 unit test case,直到個 unit test 行到(正常會係 fail 左)而且又冇 compilation error,再去寫埋啲 production code 既 implementation,寫完果陣應該個 unit test 就會 pass。 |
Behavior-driven development(BDD) | 以 end user 既 user journey 既角度去諗 test case scenarios,來源可以係黎自 functional specs 或者 business analysts 得出既 user requirements,而寫出黎既 test code 都會同一般 TDD 所針對單個 class 既 method 去寫既 test code 有明顯分別。 |
1.2 Unit test case 既表達形式
一般既 unit test case 寫法:
1public void test() {
2 // Given - 喺呢個 context 下…
3 // 構建 context,為 dependencies 作出一啲合理假設(mocking)
4
5 // When - 當我地執行呢個 method…
6 // 執行需要測試既 method
7
8 // Then - 咁我地期望得到…
9 // 有結果既話,驗證結果係咪符合期望;void return 既話,驗證下有冇出 exception
10 // 亦可以驗證下某一啲 objects 既 methods 應該曾經被 call
11}
JUnit 例子:
1@Test
2public void test_happy_calculateSine_degMode() {
3
4 // given
5 Calculator calculator = new Calculator(Mode.DEG);
6
7 // when
8 double result = calculator.calculateSine(90);
9
10 // then
11 Assert.assertTrue("Sine result under DEG mode should be correct",
12 result == 1);
13}
14
15@Test
16public void test_happy_calculateSine_radMode() {
17
18 // given
19 Calculator calculator = new Calculator(Mode.RAD);
20
21 // when
22 double result = calculator.calculateSine(90);
23
24 // then
25 Assert.assertTrue("Sine result under RAD mode should be correct",
26 result > 0.8939 && result < 0.894);
27}
1.3 Libraries
名稱 | 功能 |
---|
JUnit、TestNG | 提供 API 畀我地執行 assertion statements,咁我地就可以利用佢去做 TDD(test-driven development)。如果 expected value 同 actual value 兩者唔 match 就會令個 unit test fail,IDE 裡面既 plugin 會顯示紅燈;如果 match 既話就會顯示綠燈黎表示通過。Build tools 包括 Maven(Maven Surefire plugin 以及 Maven Failsafe plugin)、Gradle 會喺各自 build artifact 既 goal 執行果陣先自動執行 unit tests 甚或 integration tests。 |
Mockito | 提供 API 畀我地 mock 一啲 dependency objects 既 public instance methods 所返回既 return values,咁我地就可以專注 test 一個 class 既 business logic。Mockito 3.4.0 之前既版本未能夠 mock static methods(雖然需要咁樣做可能象徵住有 code smell,因為要咁做唔係幾 OOP)。 |
PowerMock | 係一個基於 Mockito 既 library,佢提供 API 畀我地 mock 埋 static methods 既 return values(雖然需要咁樣做可能象徵住有 code smell,因為要咁做唔係幾 OOP),呢一點 Mockito 3.4.0 之前係做唔到。 |
WireMock | 提供 API 畀我地建立 runtime mock servers 黎 mock external web services 既 HTTP responses。呢個比較係 end-to-end 多過 unit testing。 |
Cucumber | 提供 API 畀我地做 behavior-driven 既 testing,咁我地就可以利用佢去做 BDD(behavior-driven development)。佢支持以文字去描述 given-when-then 既表達形式。呢個比較係 end-to-end 多過 unit testing。 |
PITest | 呢一個 mutation testing library 會自動 analyze source code,知道啲 if-else conditions 用咩 values 可以令到個 condition 成立,從而透過修改我地 source code 裡面既一啲 operators 之類(例如將 == 改成 != )去睇下我地既 unit tests 有冇因為佢 library 做既 code changes 而影響左結果——即係有冇 test failures。如果冇 test fail 到既話就算係唔正常,因為如果我地改左 code 但係所有 unit tests 都照 pass,咁就即係我地既 unit test coverage 唔足以 cover 曬所有 conditions。 |
1.4 其他輔助 libraries
名稱 | 功能 |
---|
JSONAssert | 呢個 library 係 Jackson Databind 既一個 alternative,可以根據內容黎 compare JSON strings,而唔係當一般 strings 咁 compare character by character。佢提供左幾個 predefined 既容忍模式。 |
PODAM | POJO Data Mocker 係一個可以為任何 Java POJO class 生成假數據既 library。有啲時候我地想要一個 entity/DO/DTO/VO/POJO class 既 object 裡面既所有 instance fields 都有隨機既數據,呢個 library 就非常簡單易用。 |
1.4.1 使用 JSONAssert
JSONCompareMode
(容忍模式):
LENIENT
- Extensible:actual JSON 可以包含比 expected JSON 多既內容,只要唔少過就得
- Non-strict array ordering:唔理 array 順序
NON_EXTENSIBLE
- Non-extensible:actual JSON 唔可以包含比 expected JSON 多既內容
- Non-strict array ordering:唔理 array 順序
STRICT_ORDER
- Extensible:actual JSON 可以包含比 expected JSON 多既內容,只要唔少過就得
- Strict ordering:會睇埋 array 順序
STRICT
- Non-extensible:actual JSON 唔可以包含比 expected JSON 多既內容
- Strict ordering:會睇埋 array 順序
註:JSON objects 既 property 次序一定係冇關係。
1.4.1.1 Maven dependencies
<dependency>
<groupId>org.skyscreamer</groupId>
<artifactId>jsonassert</artifactId>
<version>1.4.0</version>
</dependency>
1.4.1.2 準備 JSON 檔
expected.json
:
{
"arr": ["A", "B"],
"name": "something",
"obj": {}
}
actual.json
:
1{
2 "name": "something",
3 "arr": ["B", "A"],
4 "obj": {
5 "extra": null
6 }
7}
1.4.1.3 寫 Java code
1final String expected = FileUtils.readFileToString(new File("expected.json"), "UTF-8");
2final String actual = FileUtils.readFileToString(new File("actual.json"), "UTF-8");
3
4System.out.println(JSONCompare.compareJSON(expected, actual, JSONCompareMode.LENIENT).passed());
5// true
6
7System.out.println(JSONCompare.compareJSON(expected, actual, JSONCompareMode.NON_EXTENSIBLE).passed());
8// false
9
10System.out.println(JSONCompare.compareJSON(expected, actual, JSONCompareMode.STRICT_ORDER).passed());
11// false
12
13System.out.println(JSONCompare.compareJSON(expected, actual, JSONCompareMode.STRICT).passed());
14// false
1.4.2 使用 PODAM
1.4.2.1 Maven dependencies
<dependency>
<groupId>uk.co.jemos.podam</groupId>
<artifactId>podam</artifactId>
<version>7.2.7.RELEASE</version>
</dependency>
1.4.2.2 寫 Java code
@SuppressWarnings("unchecked")
final List<String> randomData = new PodamFactoryImpl().manufacturePojo(List.class, String.class);
System.out.println(randomData); // [J_hO7wAETv, F08kZk64aV, ncjXUymIT6, tk354lMwGX, CQhUbaS3nn]