Matchers

จากหัวข้อ assertThat() เราจะมาพูดถึงตัว Matchers กันต่อ ถ้าสังเกตจากชื่อ package org.hamcrest จะเห็นว่าตัว Matchers จริงๆแลวแต่เดิมมันไม่ได้อยู่ใน JUnit มาก่อน แรกเริ่มเดิมที่ตัว Matchers นั้นอยู่ใน framework ตัวนึงที่ชื่อว่า Hamcrest ตอนหลัง JUnit ก็เอาเข้ามารวมตั้งแต่ JUnit 4.8.2 เป็นต้นมา

เราจะทำ Unit test ด้วยเทคนิคการ Matchers ผ่าน assertThat() ซึ่งตัว assertThat() จะมีหน้าตาประมาณนี้

   public void assertThat(Object o, Matcher matcher){
        ...
    }

จริงๆเราสามารถทำตัว Matchers ไว้ใช้เองได้แต่ตัว JUnit (Hamcrest) ได้มี Matchers พื้นฐานไว้ให้ใช้บ้างแล้ว ดังตัวอย่างนี้
ตัวอย่าง: แสดงการทำ Unit test ว่า String และ int เท่ากัน กันหรือไม่ด้วย is()

import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
import org.junit.Test;

public class MyMatcherTest {

    @Test
    public void testWithMatchers() {
        assertThat("this string", is("this string"));
        assertThat(123, is(123));
    }
}

อธิบาย code:
1. ส่ง object หรือค่า ที่เราต้องการจะทำ Unit test ไปให้ assertThat() ก่อน ตัวอย่างนี้ส่ง “this string”
2. ตัว is() รับ “this string” แล้วสร้าง Matcher แล้ว return ออกมา
3. assertThat() ทำการ Unit test

สังเกตว่าตัว assertThat() ไม่ได้ทำอะไรมากไปกว่าเรียกให้ Matcher ทำการ match กับ object ที่ส่งไปเท่านั้น JUnit(จริงแล้วคือตัว Hamcrest) มาพร้อมกับชุด Matchers พร้อมให้เราเลือกใช้ไว้บ้างแล้ว

ตัวอย่างด้านบนใช้ org.hamcrest.CoreMatchers.is() เพื่อสร้างตัว Matcher และตัว Matcher ที่ได้จาก is() จะ return ค่า true เมื่อค่าทั้ง 2 เท่ากันและแน่นอนเป็น false เมื่อไม่เท่ากัน

ตัว assertThat() จะเกิด exception ทันทีถ้า Matcher คืนค่า false แต่ถ้าเป็น true ก็ Unit test ผ่านไม่มีปัญหาอะไร ส่วน Matcher จะ return ค่า true หรือ false แบบไหนยังไง เดี๋ยวเราค่อยมาดูกัน เอาเป็นว่าให้เข้าใจตรงกันก่อนว่ามันทำงานประมาณนี้

การใช้ Matcher มากกว่า 2 ตัวร่วมกัน
เราสามารถใช้ Matcher 2 ตัวซ้อนกันได้ตามตัวอย่างนี้
ตัวอย่าง: แสดงการทำ Unit test ว่าค่าที่นำมาทดสอบคือ 123 นั้นจะต้องมีค่าไม่เท่ากับค่าที่ระบุไว้คือ 345

@Test
public void testWithMatchers() {

    assertThat(123, not( is(345) ) );
}

อธิบาย code:
ส่วนที่ Matcher ที่ซ้อนกันคือ not(is(345)) สังเกตว่า
1. is() คือ method ที่สร้าง Matcher มาก่อน
2. ส่ง Matcher ที่ได้มานั้นไปยัง not() เพื่อสร้าง Matcher อีกตัวนึง
3. Matcher ที่สร้างโดย not() จะให้ผล “ตรงข้าม” จาก Matcher ที่ส่งมา จากตัวอย่างด้านบน ผล Unit test ที่จะได้คือ ผลตรงข้ามจาก is() นั้นเอง
4. assertThat() ทำการ Unit test

Core Matchers
ก่อนที่เราจะลงแรงเขีน Matcher มาใช้เอง เรามาดูก่อนซิว่ามันมี Matcher อะไรที่เตรียมไว้ให้เราใช้แล้วบ้าง

Core
any() – ไว้สร้าง Matcher ที่ match กับอะไรก็ได้ (เพื่อ?)
is() – เอาไว้ Unit test ตัว Object ว่า equal หรือเปล่า (จริงๆทำได้มากกว่านั้น)
describedAs() – เอาไว้เพิ่มรายละเอียดของการทำ Unit test

Logic
allOf() – รับ Array ของ Matcher และต้อง match ทั้งหมดจึงจะผ่าน
anyOf() – รับ Array ของ Matcher เช่นกันเพียงแต่ถ้า match อันใดอันหนึ่งก็ Unit test ผ่านแล้ว
not() – ได้เห็นแล้วในตัวอย่างด้านบน เอาไว้กลับผลลัพท์ให้ได้ตรงกันข้ามกับ Matcher ที่ส่งเข้ามา

Object
equalTo() – ไว้ Unit test ตัว Object ว่ามีค่าเท่ากัน(equal)หรือไม่
instanceOf() – ใช้ Unit test ว่า Object นั้นเป็นชนิดที่ตรงกับที่ Matcher กำหนดหรือไม่
notNull() & nullValue() – สมชื่อครับไว้ Unit test ค่า null หรือไม่ null
sameInstance() – ไว้ Unit test ตัว Object เป็นตัวเดียวกันหรือไม่

จริงๆแล้วในแต่ละ method ที่เอามาให้ดูนั้นแต่ละตัวจะมีวิธีการส่ง parameter ไม่เหมือนกัน ดังนั้นเราต้องอ่านดูไปในแต่ละตัวว่าเราจะเอามาใช้ได้ยังไง

Custom Matcher ทำ Matcher ไว้ใช้เอง
เราจะมาลองเขียน Matcher ใช้เองกันบ้าง นี้คือตัวอย่าง
ตัวอย่าง: เราจะสร้าง method ชื่อ matches ไว้สำหรับสร้าง Matcher โดยให้ match ว่า object มีค่าเท่ากัน(equal) หรือไม่

public static Matcher matches(final Object expected){

    return new BaseMatcher() {

        protected Object theExpected = expected;

        public boolean matches(Object o) {
            return theExpected.equals(o);
        }

        public void describeTo(Description description) {
            description.appendText(theExpected.toString());
        }
    };
}

สังเกตุ matches() จะถูกกำหนดเป็น static ทำหน้าที่สร้าง Matcher แล้ว return ออกมา

Matcher ตัวนี้เป็น anonymous subclass ของ BaseMatcher ในเอกสารของ JUnit เลือกที่จะใช้การ extend จาก BaseMatcher มากกว่าที่จะ implement จาก Interface เอาเอง เพราะถ้ามีการเพิ่ม method อะไรใหม่ๆใน interface ของ Matcher ตัว BaseMatcher ก็จะ implement มาให้ด้วย นั้นทำให้ custom matcher ที่เรา extend มาจาก BaseMatcher ก็จะมี method ใหม่พวกนี้มาอัตโนนาโถ ซึ่งมันทำให้ไม่ต้องมาปวดหัวการการแก้ code ตาม interface ไปด้วย และนี้คือตัวอย่างการเรียกใช้ Matcher ของเราเอง

@Test
public void testThat() {
    MyUnit myUnit = new MyUnit();

    assertThat(myUnit.getTheSameObject(), matches("constant string"));

}

ง่ายใช่ไหมละ เราก็แค่เรียก matches() ในตัว assertThat() เหมือน Matcher ทั่วๆไปเลย

แปลจาก: Matchers by Jakob Jenkov

ปล. จะบทความนี้ยังมีเรื่องให้งงอีกเช่น ทำไมมี method ที่ดูซ้ำซ้อนกันอย่าง assertEqual() กับ equalTo() มันต่างกันยังไง? แล้วแต่ละตัวมันใช้ยังไงมีตัวอย่างไหม? ไว้ติดต่อตอนต่อไปครับ 🙂

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s