ปัญหา

โจทย์ที่ได้รับมาก็คือ ในการพัฒนา software นั้น ถ้ามีการแตกงานออกมาเป็น backlog แล้ว
เมื่อเวลานำเข้ามาพัฒนาแล้วนั้น จำเป็นจะต้องเขียน unit test ขึ้นมา
คำถามที่มักเกิดขึ้นมาก็คือ แล้วเราจะรู้ได้อย่างไรว่า
unit test แต่ละ test case นั้น เกิดขึ้นจาก backlog ไหน ?

วิธีการแก้ไขปัญหา

ในทีมพัฒนานั้นใช้ภาษา Java โดย unit test ใช้ library ชื่อว่า JUnit
ดังนั้น สิ่งที่ต้องการนั้น คือ การ mapping แต่ละ test case หรือ method ใน unit test
เข้ากับ Backlog ต่างๆ

ตัวอย่างของ Unit test ที่พัฒนาตามรูปแบบของ JUnit

public class HelloTest {

    @Test
    public void testcase_01() {
    }

    @Test
    public void testcase_02() {
    }
}

สิ่งแรกที่ต้องคิดก็คือ แล้วเราจะใช้วิธีการไหนดีล่ะ ?

หลังจากนั่งดู และ คิด ไปมาหลายรอบ เพื่อหาวิธีการที่กระทบต่อการพัฒนาของทีมให้น้อยที่สุด
จึงคิดว่า ถ้าใช้งานผ่าน Annotation ล่ะ เช่น @Backlog ไปเลยจะสะดวกไหม ?

ตัวอย่างของสิ่งที่คิดไว้

public class HelloTest {

    @Test
    @Backlog( name="Feature01" )
    public void testcase_01() {
    }

    @Test
    @Backlog( name="Feature02" )
    public void testcase_02() {
    }

}

ปัญหาต่อมา คือ แล้วจะสร้าง annotation @Backlog อย่างไรล่ะ เกิดมาผมก็ไม่เคยทำ

ลองไปค้นหาข้อมูลพบว่า มันสร้างง่ายมาก
โดย Annotation มันเป็นความสามารถของภาษา Java มาตั้งแต่ Java 5 (Tiger) แล้ว
เป็น  meta data ที่สามารถใส่เข้าไปให้กับ class, property, method และ parameter ได้
ทำให้สามารถเพิ่มความสามารถบางอย่างเข้าไป โดยไม่ต้องไปแก้ไข code เดิมเลย

และแน่นอนว่า มันอนุญาตให้เราสามารถสร้าง Annotation ใช้เองได้ด้วย
ดังนั้น มาดูกันว่าจะสร้าง Annotation ใช้เองได้อย่างไร

ขั้นตอนการสร้าง Annotation ใช้เอง

1. สร้าง interface สำหรับกำหนดโครงสร้าง annotation ที่เราต้องการ
นั่นคือ Backlog และมี property name ให้ใช้งาน ดังนี้

public @interface Backlog {
    String name();
}

2. ทำการกำหนด Meta Annotation ของ interface
มี Annotation ให้ใช้ 4 ตัวประกอบไปด้วย

  1. @Target
  2. @Retention
  3. @Documented
  4. @Inherited

ตัวอย่างที่ใช้งานคือ
ให้สามารถใช้ @Backlog ได้ในระดับ method

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Backlog {
    String name();
}

3. การใช้งาน
ในแต่ละ test case ที่พัฒนาด้วย JUnit สามารถใส่ annotation @Backlog ได้ดังนี้

public class HelloTest {

    @Test
    @Backlog( name="Feature01" )
    public void testcase_01() {
    }

    @Test
    @Backlog( name="Feature02" )
    public void testcase_02() {
    }

}

ปัญหาต่อมาก็คือ แล้ว เราจะทำงานออกรายงานสรุป เพื่อบอกว่า แต่ละ Backlog มี test case อะไรบ้าง ?

เริ่มต้นศึกษาการทำงานของ JUnit  กันก่อน พบว่า JUnit นั้นได้ทำงานผ่าน Runner
โดยมี default runner ทำงานอยู่แล้ว ซึ่ง JUnit 4 จะใช้ runner ชื่อว่า org.junit.runners.JUnit4
ดังนั้น ถ้าเราต้องการจะสร้าง Runner เข้ามา เพื่อทำการสร้าง report ของเราล่ะ จะทำได้ไหมล่ะ ?

ต้องได้สินะ เพราะว่า เราจะทำการสร้าง Runner ขึ้นมาใช้เอง นั่นคือ BacklogRunner
ซึ่งจะ extends มาจาก Runner นั่นเอง ดังตัวอย่าง

public class BacklogRunner extends Runner {

    public BacklogRunner(Class<?> aClass) {
    }

    @Override
    public Description getDescription() {
    }

    @Override
    public void run(RunNotifier runNotifier) {
    }

}

คำอธิบาย
จะพบว่าใน constructor ของคลาส BacklogRunner นั้น ค่าที่ได้รับมาก็คือ Class ของ unit test นั้นๆ
ดังนั้น ถ้าเราต้องการรู้ว่าในคลาส มี property, method, annotation อะไรบ้างนั้น
จะต้องใช้งานผ่าน Java Reflection นั่นเอง

โดยสิ่งที่ต้องการทำก็คือ
หา method ที่มี annotation @Test เนื่องจากคือ test case
หา method ที่มี annotation @Backlog เพื่อดึงข้อมูลของ backlog ที่กำหนดไว้
ดังตัวอย่าง

public class BacklogRunner extends Runner {

    public BacklogRunner(Class<?> aClass) {
        fTestClass = new TestClass(aClass);
        filterMethods(aClass, aClass.getDeclaredMethods());
    }

    private void filterMethods(Class<?> aClass, Method[] classMethods) {
        for (int i = 0; i < classMethods.length; i++) {
            Method classMethod = classMethods[i];
            String methodName = classMethod.getName();

            if (methodName.toUpperCase().startsWith("TEST") || classMethod.getAnnotation(Test.class) != null) {
                fTestMethods.add(classMethod);
            }

            Backlog backlog = classMethod.getAnnotation(Backlog.class);
            if (classMethod.getAnnotation(Test.class) != null && backlog != null) {
                BacklogModel newBacklog = new BacklogModel(backlog.name(), aClass.getName(),                                                         classMethod.getName());
                backlogList.add(newBacklog);
            }
        }
    }

}

ต่อจากนั้นทำการเขียนข้อมูลลงในไฟล์ในรูปแบบที่ต้องการ โดยผมเลือกใช้ JSON
สามารถเขียน code เพื่อให้ทำงานใน method run() ได้เลย
โดยจะแยกไฟล์ตามชื่อคลาสไปเลย ทำให้เราได้ไฟล์จำนวนมาก
ดังนั้น จะต้องทำโปรแกรมเพื่อสรุปข้อมูลจากไฟล์ JSON ให้อยู่ในรูปแบบ html
ดังรูป

Screen Shot 2557-07-21 at 4.00.47 PM

โดยหน้าตาของไฟล์ results.html จะมีหน้าตาดังรูป

Screen Shot 2557-07-21 at 4.10.23 PM

สิ่งที่ยังขาดไป
ก็คือ เราต้องไปบอก JUnit ด้วยว่า จะให้ทำงานด้วย BacklogRunner ผ่าน @RunWith ดังนี้

@RunWith( value=BacklogRunner )
public class HelloTest {

    @Test
    @Backlog( name="Feature01" )
    public void test case_01() {
    }

    @Test
    @Backlog( name="Feature02" )
    public void test case_02() {
    }
}

เพียงเท่านี้ก็สามารถสร้าง annotation  @Backlog เอาไว้ใช้เองได้แล้วครับ
จะพบว่า annotation ทำให้เราสามารถเพิ่มความสามารถต่างๆ ให้กับภาษา Java ได้แบบง่ายๆ ครับ

ตัวอย่าง code แบบกากๆ ที่ผมเขียนขึ้นมาอยู่ที่ Github Up1 :: Tracking Backlog