Maven ใช้งาน part 2 — with Tests
งานอย่างหนึ่งที่สำคัญสำหรับการพัฒนาโปรแกรมก็คือการเขียนเทส เป็นการการันตีความถูกต้องของ logic ที่ใช้เขียนโปรแกรมว่าผลลัพธ์ที่ได้เป็นไปตามที่คาดหวังเพื่อให้โปรแกรมผิดพลาดน้อยที่สุดซึ่ง Maven ไม่ได้นิ่งเฉย ตรงกันข้ามมันถูกออกแบบให้รองรับสิ่งนี้ในระดับโครงสร้างของโปรเจกต์เทียวครับ
ความเดิมจาก part ที่แล้ว
พูดถึงการเขียนเทสคงไม่มีนักพัฒนาจาวาคนไหนไม่รู้จัก unit test tool อย่าง JUnit ซึ่งขณะนี้เดินทางมาถึงเวอร์ชันที่ 5 แล้ว แต่ก่อนจะไปจบที่ JUnit เรามาทำความรู้จักโครงสร้างที่ Maven เข้าใจเรื่องการเขียน unit test กันดีกว่า
Project Structure for Tests
จากภาพโครงสร้างที่ว่านั้นเริ่มจาก root project หรือก็คือ folder โปรเจกต์ hello (จาก part 1) ให้สร้าง sub folder ถัดจาก src เข้าไป ได้ว่า
src > test > java
แล้วจึงสร้างชื่อ package ต่อจากนั้น โดยทั่วไปแล้วชื่อ package ดังกล่าวก็จะเหมือนกับ groupId ที่ระบุไว้ใน pom.xml ได้ว่า
com.example.pros
ผมสร้างคลาสทดสอบชื่อ MainTests.java และลบ folder ชื่อ target ออกไปก่อน
package com.example.pros;public class MainTests { void test1() { System.out.println("Hello Tests"); }}
จากนั้นรันคำสั่ง
mvn package
ผล
ปรากฏไฟล์ MainTests.class อยู่ใน folder ชื่อ test-classes นั่นแสดงว่า Maven เข้าใจและแปลความถูกต้อง
Unit Test with JUnit 5
unit test tool ที่ผมเลือกใช้คือ JUnit 5 เป็น .jar ประกอบด้วยกลุ่มของ .class ที่นักพัฒนาได้เขียนและแจกจ่ายแก่ผู้ที่ต้องการนำไปใช้งาน โดยบอกกับ Maven ผ่าน pom.xml ในรูปแบบของ dependency
เปิดไฟล์ pom.xml แล้วเพิ่ม JUnit 5 dependency ขณะนี้เวอร์ชัน 5.6.2
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">... <dependencies> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.6.2</version> <scope>test</scope> </dependency> </dependencies></project>
เพื่อนๆจะสังเกตว่า groupId, artifactId และ version ได้เห็นผ่านตามาแล้วใน part 1 ที่เพิ่มเติมตอนนี้คือ scope
dependency scope หมายถึงการจำกัดขอบเขตใดๆของ dependency ตัวนั้นๆอันมีผลต่อกระบวนการ build tasks และการเปลี่ยนแปลงของ classpath ซึ่งจะกล่าวถึงอีกครั้งภายหลัง
จากตัวอย่างกำหนด depencency scope เป็น test นั่นหมายความว่า หากปราศจากกระบวนการที่จะไปเรียก phase ที่เกี่ยวข้องกับการ test ก็จะไม่สนใจโค้ดที่เขียน unit test เลย
พูดได้ว่า dependency scope เป็นการกำหนดบทบาทหน้าที่ตลอดจนศักดิ์ศรีของสามีและภรรยา (รวมเมียน้อยและชู้) ของทุกคนในบ้านให้แตกต่างกันออกไป บางงานก็จะสนใจและบางงานก็จะปล่อยไป หรือพูดว่าแบ่งกลุ่มภาระรับผิดชอบก็ได้
เมื่อระบุ dependency ใหม่เข้าไปหรือลบ dependency เดิมทิ้งไป เราจะต้องบอกกล่าวกับ Maven ทุกครั้ง (บาง IDE จะมี service ที่จะทำเรื่องนี้ให้อัติโนมัติ) เพื่อให้ dependency ทั้งหมด update เป็นปัจจุบันเสมอ
mvn dependency:resolve
ผล
คำสั่ง mvn dependency:resolve
มีผลให้ Maven ทำ resolve all test scope (รวมถึง compile scope ด้วย) .jar ไหนไม่มีก็ดาวน์โหลดมา หรือ .jar ไหนไม่ใช้ก็นำออกจากโปรเจกต์ไป
มาถึงตรงนี้เชื่อว่าเพื่อนๆมือใหม่พอจะเห็นภาพของประดา .jar ที่ต้องคุยกันเป็นจำนวนมากและมันทั้งหมดต้องทำงานร่วมกันได้เป็นอย่างดี
กลับไปที่ MainTests.java เรามาเขียน test case เล็กๆกันโดยปรับปรุงโค้ดเป็น
package com.example.pros;import org.junit.jupiter.api.Test;import static org.junit.jupiter.api.Assertions.assertEquals;public class MainTests { @Test void test1() { String iWantToSay = "Hello Tests"; assertEquals("Hello", iWantToSay); }}
จากนั้นรัน
mvn package
ผล
BUILD SUCCESS แต่ว่า test case ไม่พัง มันเป็นเพราะอะไร?
เหตุผลนั้นมาจาก JUnit เวอร์ชัน 4 และ 5 ต่างกัน โดยเฉพาะเรื่องของ naming convention เบื้องต้นหมายความว่าคลาสทดสอบใดๆ (เช่น MainTests) จะต้องตั้งชื่อที่เลือกจากเงื่อนไขต่อไปนี้
- Test*
- *Test
- *Tests (ต้องเพิ่ม Maven Surefire Plugin)
- *TestCase
ฉะนั้นผมเลือกใช้ JUnit 5 และลงท้ายคลาสทดสอบเป็น Tests จึงต้องแก้ไข pom.xml ให้ระบุ plugin ชื่อ maven-surefire-plugin เข้าไปด้วย
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">... <dependencies> ... </dependencies> <build> <plugins> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.2</version> </plugin> </plugins> </build></project>
รัน
mvn package
ผล
มันต้องอย่างนี้สิ! บอกเลยว่าพังเพราะคาดหวังคำว่า “Hello” แต่กลับได้คำว่า “Hello Tests” มาแทน
ในเมื่อเราต้องการ say ว่า “Hello Tests” เราก็ไปแก้ไขความคาดหวังครับ
assertEquals("Hello Tests", iWantToSay);
แล้วจึงรันใหม่
ผล
สรุปตรงนี้ก่อนว่า Maven เข้าใจคลาสทดสอบได้ทั้งหมดตราบที่ตั้งชื่อตามกฏที่มันชอบและได้วางโครงสร้างของโปรเจกต์ตามที่มันกำหนดไว้
ขอส่งท้าย part นี้ด้วยคำสั่ง mvn test
เฉยๆ
หากว่าเราลบ target folder ทิ้งไปแล้วรัน mvn test
สิ่งที่ได้คือ target folder ใหม่ ของด้านในส่วนใหญ่เหมือนกับรัน mvn package
ขาดก็แต่ไฟล์ .jar
โดยรวมจึงขอกล่าวถึง Maven ในส่วนของโครงสร้างสำหรับ test เพียงเท่านี้ ไว้พบกันใหม่ สวัสดีครับ
อ่านต่อ
อ้างอิง
https://maven.apache.org/plugins/maven-dependency-plugin/usage.html
https://www.baeldung.com/maven-dependency-scopes
https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Dependency_Scope
https://stackoverflow.com/questions/6178583/maven-does-not-find-junit-tests-to-run