unit test คือการเขียนคำสั่งไปทดสอบ method หรือ function ที่เราสร้างขึ้น ว่าทำงานได้ถูกต้องหรือไม่ แทนที่จะต้องรอระบบให้เรียบร้อย แล้วมา manual test ทีหลัง แต่เราสามารถแยกส่วนของระบบเป็นหน่วยย่อย ๆ แยกกัน test ได้
unit test ได้รับการยอมรับว่าเป็น software practice ที่ดี ลดความผิดพลาดของ program ที่เราสร้างขึ้น ช่วยให้ค้นพบข้อมูลผิดพลาดไวขึ้น แก้ปัญหาได้ไวขึ้น ชีวิตง่ายขึ้น มีความสุขมากขึ้น
unit test ทำให้เรามีความมั่นใจ เมื่อแก้ไขส่วนต่างๆ ของ program แล้ว run unit test ก็สามารถรู้ได้ทันทีว่า สิ่งที่เราแก้ไขนั้น กระทบส่วนไหนในระบบบ้าง
ลองมาดู test method ที่สร้างด้วย Spock กัน
def "score 90 get grade A"() {
given:
def gradeCalculator = new GradeCalculator()
def rawScore = 90
when:
def result = gradeCalculator.getGradeForScore(rawScore)
then:
result == "A"
}
โดยทั่วไปโครงสร้างการเขียน test method นั้นเราจะเขียนในรูปแบบ AAA ย่อมาจาก arrange, act, assert หมายความว่า ในแต่ละ test method จะมีขั้นตอนทำงานหลักๆ แบ่งเป็น 3 ส่วน
เมื่อกลับไปดูที่ Spock test method จะเห็นได้ว่าโครงสร้างเป็น given: , when : , then : ถ้าเปรียบกับ AAA pattern จะเป็นดังนี้
!!! และที่สำคัญ เราสามารถเขียน test method เป็นชุด string เป็นประโยค อ่านเข้าใจง่ายได้เลย ซึ่งไม่มีใน test framework ตัวอื่นๆ ของ Java ตรงนี้ เป็นความสามารถพิเศษที่มาจาก Groovy Magic DSL ดังเช่นในตัวอย่างนี้ def "score 90 must have grade A"()
โดยกำหนดค่า
แก้ไข gradle file เพื่อ include library ที่จำเป็น เลือก Spock version ให้ตรงกับ Groovy Version ด้วยนะครับ
group 'com.codesanook.example'
version '1.0-SNAPSHOT'
apply plugin: 'java'
//เพิ่ม plugin groovy เข้าไป
apply plugin: 'groovy'
sourceCompatibility = 1.7
repositories {
mavenCentral()
}
dependencies {
//เพิ่ม lib ที่ต้องใช้ใน testCompile คือ groovy และ spock ดู version ให้ตรงกันด้วยนะครับ
testCompile 'org.codehaus.groovy:groovy-all:2.4.4'
testCompile "org.spockframework:spock-core:1.0-groovy-2.4"
}
เราจะสร้าง class GradeRate และ GradeCalcualtor ไว้คำนวณ Grade ผลการเรียนครับ แล้วจะใช้ Spock มาทดสอบ method ต่างๆ ของ class GradeCalcualtor
สร้าง Java Class GradeRate.java
src\main\java\com\codesanook\example\GradeRate.java
package com.codesanook.example;
public class GradeRate implements Comparable<GradeRate> {
private float minScore;
private String grade;
public GradeRate(float minScore, String grade) {
this.minScore = minScore;
this.grade = grade;
}
public float getMinScore() {
return minScore;
}
public void setMinScore(float minScore) {
this.minScore = minScore;
}
public String getGrade() {
return grade;
}
public void setGrade(String grade) {
this.grade = grade;
}
@Override
public int compareTo(GradeRate other) {
return Float.compare(this.getMinScore(), other.getMinScore());
}
}
src\main\java\com\codesanook\example\GradeCalculator.java
package com.codesanook.example;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class GradeCalculator {
private List<GradeRate> rates;
private int currentIndex;
public GradeCalculator() {
rates = new ArrayList<>();
rates.add(new GradeRate(85, "A"));
rates.add(new GradeRate(70, "B"));
rates.add(new GradeRate(60, "C"));
rates.add(new GradeRate(50, "D"));
rates.add(new GradeRate(0, "F"));
Collections.sort(rates);
resetIndex();
}
private void resetIndex() {
currentIndex = rates.size() - 1;
}
public String getGradeForScore(double rawScore) {
if (rawScore > 100 || rawScore < 0)
throw new IllegalStateException("Score must be between 0 to 100");
if (rawScore >= rates.get(currentIndex).getMinScore()) {
String grade = rates.get(currentIndex).getGrade();
resetIndex();
return grade;
}
currentIndex--;
return getGradeForScore(rawScore);
}
}
สร้าง Test Class กัน ใน Spock Test Class เราเรียกว่า Specification โดย class ที่สร้างขึ้น จะสืบทอดจาก Specification และนิยมตั้งชื่อ class ให้ลงท้ายด้วย Spec
สร้าง Groovy class GradeCalculatorSpec.groovy
src\test\groovy\com\codesanook\example\test\GradeCalculatorSpec.groovy
package com.codesanook.example.test
import com.codesanook.example.GradeCalculator
import spock.lang.Specification
import spock.lang.Unroll
class GradeCalculatorSpec extends Specification {
def "score 90 get grade A"() {
given:
def gradeCalculator = new GradeCalculator()
def rawStore = 90
when:
def result = gradeCalculator.getGradeForScore(rawStore)
then:
result == "A"
}
}
เปิด Gradle Window double click run หรือสั่งจาก command line gradle run
หากการทำงานถูกต้อง จะมี test report file ให้เราเข้าไปดูได้ อยู่ใน build folder
build\reports\tests\index.html
ถ้าเราต้องการเขียน test method ขึ้นมาเพื่อทดสอบว่า ถ้ามีคะแนน 60, 70, 85 บ้าง เด็กนักเรียนคนนี้ควรได้ grade อะไร หากต้องสร้าง method ใหม่อีก 3 method ก็คงเป็นเรื่องน่าเบื่อ และเรายังต้องเขียน code ซ้ำกัน ไม่ตรงตาม concept codesanook อีก ดังนั้น เราสามารถยุบให้เหลือ method เดียวกันได้ด้วย where ครับ
เราสามารถเพิ่ม method นี้เข้าไปใน class GradeCalculatorSpec.groovy ได้เลย
@Unroll
def "score #rawScore get grade #grade"() {
given:
def gradeCalculator = new GradeCalculator()
when:
def result = gradeCalculator.getGradeForScore(rawScore)
then:
result == grade
where:
rawScore | grade
85 | "A"
70 | "B"
60 | "C"
50 | "D"
49 | "F"
}
ทดลอง run test อีกครั้ง เราจะเห็นได้ว่า มีผลลัพธ์ของ test เพิ่มขึ้นมาอีก 5 tests
จะเห็นได้ว่า Spock เป็น test framework ตัวนึง ที่น่าใช้งานไม่น้อย และเข้ากันได้ดีกับ JUnit ถ้าผู้ใช้มีความพื้นฐานอยู่แล้ว ก็สามารถต่อยอดได้ไว อีกทั้งยังนำส่วนดีของ Groovy มาใช้ได้ด้วย สามารถทดสอบ production code ที่เป็น Java ได้ เรียกว่านำส่วนดีของหลายๆ อย่างมารวมกัน
ถ้าใครมีคำถาม ข้อสงสัยใดๆ comment มาได้เลยนะครับ