➜ Old React website
Chung Cheuk Hang MichaelJava Web Developer
Java 測試(二):JUnitSpring 基礎功能:AOP

Java 測試(一):簡介

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 既容忍模式。
PODAMPOJO 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]