คุณต้องใช้เครื่องมืออะไรบ้างเมื่อต้องทำงานกับ legacy code? คุณต้องมี editor (หรือ IDE) และ build system แต่คุณก็ต้องมี testing framework ด้วยเช่นกัน ถ้ามี refactoring tools สำหรับภาษาที่คุณใช้ มันก็จะช่วยได้มากทีเดียว

ในบทนี้ ผมจะอธิบายถึงเครื่องมือบางอย่างที่มีให้ใช้ในปัจจุบัน และบทบาทที่เครื่องมือเหล่านี้มีต่องาน legacy code ของคุณ

Automated Refactoring Tools (เครื่องมือ Refactoring อัตโนมัติ)

การ refactoring ด้วยมือก็ใช้ได้ แต่เมื่อคุณมีเครื่องมือที่ช่วย refactoring ให้คุณ ก็ช่วยประหยัดเวลาได้มาก ในยุค 1990s Bill Opdyke เริ่มทำงานบน C++ refactoring tool ซึ่งเป็นส่วนหนึ่งของงานวิทยานิพนธ์ของเขาเกี่ยวกับ refactoring แม้ว่ามันจะไม่ได้ถูกนำออกมาขายในเชิงพาณิชย์ เท่าที่ผมทราบ งานของเขาเป็นแรงบันดาลใจให้กับความพยายามอื่น ๆ อีกมากมายในภาษาอื่น หนึ่งในสิ่งที่สำคัญที่สุดคือ Smalltalk refactoring browser ที่พัฒนาโดย John Brant และ Don Roberts ที่ University of Illinois Smalltalk refactoring browser รองรับการ refactoring จำนวนมากและเป็นตัวอย่างชั้นนำของเทคโนโลยี automated refactoring มาเป็นเวลานาน ตั้งแต่นั้นมา ก็มีความพยายามมากมายในการเพิ่มความสามารถในการ refactoring ให้กับภาษาต่าง ๆ ที่ใช้งานอย่างแพร่หลาย ณ เวลาที่เขียนหนังสือนี้ มี Java refactoring tools มากมายให้ใช้; ส่วนใหญ่รวมอยู่ใน IDE แต่ก็มีบางส่วนที่ไม่ได้รวมอยู่ นอกจากนี้ยังมี refactoring tools สำหรับ Delphi และของใหม่สำหรับ C++ ด้วย ส่วนเครื่องมือสำหรับ C# refactoring ก็อยู่ระหว่างการพัฒนาอย่างจริงจังในตอนที่เขียนหนังสือนี้

ด้วยเครื่องมือทั้งหมดนี้ ดูเหมือนว่า refactoring น่าจะทำได้ง่ายขึ้นมาก มันก็เป็นแบบนั้นในบางสภาพแวดล้อม แต่ที่น่าเสียดายคือ ความสามารถในการ refactoring ในเครื่องมือหลาย ๆ ตัวนั้นแตกต่างกันไป มาทบทวนกันอีกครั้งว่า refactoring คืออะไร นี่คือนิยามของ Martin Fowler จาก Refactoring: Improving the Design of Existing Code (Addison-Wesley 1999):


refactoring (n.) การเปลี่ยนแปลงโครงสร้างภายในของซอฟต์แวร์เพื่อให้เข้าใจง่ายขึ้นและปรับเปลี่ยนได้ถูกลง โดยไม่เปลี่ยนแปลงพฤติกรรมเดิมของมัน


การเปลี่ยนแปลงจะเป็น refactoring ก็ต่อเมื่อมันไม่เปลี่ยนแปลงพฤติกรรมของระบบ เครื่องมือ refactoring ควรจะตรวจสอบว่าการเปลี่ยนแปลงนั้นไม่ได้เปลี่ยนพฤติกรรม และเครื่องมือหลาย ๆ ตัวก็ทำเช่นนั้น นี่เป็นกฎสำคัญใน Smalltalk refactoring browser, งานของ Bill Opdyke, และ Java refactoring tools ยุคแรก ๆ หลายตัว แต่ในขอบเขตที่ห่างออกไป เครื่องมือบางตัวไม่ได้ตรวจสอบจริง ๆ—และถ้ามันไม่ตรวจสอบ คุณอาจจะเผลอ introduce subtle bugs ตอนที่คุณ refactor ได้

การเลือก refactoring tools อย่างระมัดระวังนั้นคุ้มค่า ลองหาว่านักพัฒนาเครื่องมือพูดถึงความปลอดภัยของเครื่องมือพวกนั้นว่าอย่างไร ลองทดสอบด้วยตัวเอง เมื่อผมเจอเครื่องมือ refactoring ใหม่ ๆ ผมมักจะรัน sanity checks เล็กน้อย เมื่อคุณพยายาม extract method และตั้งชื่อให้มันเหมือนกับ method ที่มีอยู่แล้วใน class นั้น มันแจ้งเตือนว่าเป็น error หรือเปล่า? แล้วถ้าเป็นชื่อ method ใน base class ล่ะ—เครื่องมือตรวจเจอไหม? ถ้ามันไม่ตรวจ คุณอาจจะเผลอ override method และทำให้ code พังได้

ในหนังสือเล่มนี้ ผมพูดถึงการทำงานทั้งที่มีและไม่มี automated refactoring support ในตัวอย่างต่าง ๆ ผมจะบอกว่าผมสมมติว่ามี refactoring tool หรือไม่

ในทุกกรณี ผมสมมติว่า refactorings ที่มาพร้อมกับเครื่องมือนั้น preserve behavior ถ้าคุณพบว่าของที่มาพร้อมกับเครื่องมือของคุณไม่ได้ preserve behavior ไว้อย่าใช้ automated refactorings ให้ทำตามคำแนะนำสำหรับกรณีที่คุณไม่มี refactoring tool—มันจะปลอดภัยกว่า


Tests and Automated Refactoring (การทดสอบและการ Refactoring อัตโนมัติ)

เมื่อคุณมีเครื่องมือที่ช่วย refactor ให้ คุณอาจจะรู้สึกว่าคุณไม่จำเป็นต้องเขียน tests สำหรับ code ที่คุณกำลังจะ refactor ในบางกรณีก็จริง ถ้าเครื่องมือของคุณทำ safe refactorings และคุณใช้ automated refactoring ต่อเนื่องกันโดยไม่มีการแก้ไขอื่นใด คุณก็พอจะถือได้ว่าการแก้ไขของคุณไม่ได้เปลี่ยนพฤติกรรม แต่มันก็ไม่ได้เป็นเช่นนั้นเสมอไป

นี่คือตัวอย่าง:

public class A {
    private int alpha = 0;
    private int getValue() {
        alpha++;
        return 12;
    }
    public void doSomething() {
        int v = getValue();
        int total = 0;
        for (int n = 0; n < 10; n++) {
             total += v;
        }
    }
}

ใน Java refactoring tools อย่างน้อยสองตัว เราสามารถใช้ refactoring เพื่อลบตัวแปร v ออกจาก doSomething ได้ หลังจาก refactoring แล้ว code จะหน้าตาแบบนี้:

public class A {
    private int alpha = 0;
    private int getValue() {
        alpha++;
        return 12;
    }
    public void doSomething() {
       int total = 0;
       for (int n = 0; n < 10; n++) {
           total += getValue();
       }
    }
}

เห็นปัญหาหรือเปล่า? ตัวแปรถูกเอาออกไปแล้ว แต่ตอนนี้ค่าของ alpha ถูก increment 10 ครั้งแทนที่จะเป็น 1 การเปลี่ยนแปลงนี้ไม่สามารถรักษาพฤติกรรมเดิมไว้ได้อย่างชัดเจน

การมี tests ครอบคลุม code ของคุณก่อนที่จะเริ่มใช้ automated refactorings เป็นความคิดที่ดี คุณสามารถทำ automated refactoring โดยไม่มี tests ได้ แต่คุณต้องรู้ว่าเครื่องมือตรวจสอบอะไรบ้างและไม่ได้ตรวจสอบอะไรบ้าง เมื่อผมเริ่มใช้เครื่องมือใหม่ สิ่งแรกที่ผมทำคือทดสอบความสามารถในการ extract methods ของมันอย่างละเอียด ถ้าผมไว้ใจมันได้มากพอที่จะใช้โดยไม่มี tests ผมก็จะสามารถทำให้ code อยู่ในสภาพที่ testable มากขึ้นได้


Mock Objects (ออบเจกต์จำลอง)

หนึ่งในปัญหาใหญ่ที่เราเจอในการทำงานกับ legacy code คือ dependency ถ้าเราต้องการรัน code บางส่วนเพียงลำพังและดูว่ามันทำอะไร เรามักจะต้องทำลาย dependencies ที่มีต่อ code อื่น ๆ แต่มันก็ไม่ง่ายขนาดนั้น ถ้าเราเอาส่วนอื่นออกไป เราต้องมีอะไรสักอย่างมาแทนที่ที่ให้ค่าที่ถูกต้องตอนเราทดสอบ เพื่อที่เราจะได้ทดสอบ code ส่วนของเราอย่างละเอียด ใน object-oriented code สิ่งเหล่านี้มักถูกเรียกว่า mock objects

มี mock object libraries หลายตัวให้ใช้ฟรี เว็บไซต์ www.mockobjects.com เป็นแหล่งที่ดีในการหา references สำหรับ libraries เหล่านี้ส่วนใหญ่

Unit-Testing Harnesses (เครื่องมือทดสอบระดับ Unit)

Testing tools มีประวัติอันยาวนานและหลากหลาย แทบจะทุกปีผมก็เจอทีมสี่ห้าทีมที่ซื้อ testing tool ราคาแพงแบบคิดค่าลิขสิทธิ์ต่อ seat ซึ่งสุดท้ายก็ไม่คุ้มกับราคาที่จ่ายไป ขอยุติธรรมกับผู้ขาย tools นะครับ การทดสอบเป็นปัญหาที่ยาก และคนมักจะถูกล่อลวงด้วยความคิดที่ว่าพวกเขาสามารถทดสอบผ่าน GUI หรือ web interface โดยไม่ต้องทำอะไรพิเศษกับแอปพลิเคชันของพวกเขา มันก็ทำได้ แต่มันมักจะใช้ความพยายามมากกว่าที่ใคร ๆ ในทีมยอมรับ นอกจากนี้ user interface ก็มักจะไม่ใช่ที่ที่ดีที่สุดในการเขียน tests เพราะ UIs มักจะเปลี่ยนแปลงบ่อยและอยู่ห่างจากฟังก์ชันการทำงานที่กำลังทดสอบมากเกินไป เมื่อ UI-based tests ล้มเหลว ก็ยากที่จะหาสาเหตุ แต่ถึงอย่างนั้น คนก็ยังใช้เงินจำนวนมากเพื่อพยายามทดสอบทุกอย่างด้วยเครื่องมือประเภทนั้น

testing tools ที่มีประสิทธิภาพมากที่สุดที่ผมเคยเจอคือของฟรี อย่างแรกคือ xUnit testing framework เดิมทีเขียนใน Smalltalk โดย Kent Beck แล้วก็ port มา Java โดย Kent Beck และ Erich Gamma xUnit เป็นการออกแบบ unit-testing framework ที่เล็กแต่ทรงพลัง นี่คือคุณสมบัติหลัก:

• มันช่วยให้ programmers เขียน tests ในภาษาที่พวกเขากำลังพัฒนาได้

• tests ทั้งหมดรันแยกจากกัน (isolation)

• tests สามารถรวมกลุ่มเป็น suites เพื่อให้รันและรันซ้ำได้ตามต้องการ

xUnit framework ถูก port ไปยังภาษาใหญ่ ๆ ส่วนใหญ่ และอีกหลายภาษาที่เล็กกว่าและแปลก ๆ

สิ่งที่ปฏิวัติวงการที่สุดเกี่ยวกับการออกแบบของ xUnit คือความเรียบง่ายและความโฟกัสของมัน มันช่วยให้เราเขียน tests ได้โดยไม่ยุ่งยาก แม้ว่ามันจะถูกออกแบบมาสำหรับ unit testing แต่คุณก็สามารถใช้มันเขียน tests ที่ใหญ่ขึ้นได้เพราะ xUnit ไม่ได้สนใจว่า test จะใหญ่หรือเล็กแค่ไหน ถ้า test สามารถเขียนด้วยภาษาที่คุณใช้อยู่ xUnit ก็รันมันได้

ในหนังสือเล่มนี้ ตัวอย่างส่วนใหญ่ใช้ Java และ C++ ใน Java JUnit เป็น xUnit harness ที่นิยมใช้ และมันก็หน้าตาคล้ายกับ xUnits อื่น ๆ ส่วนใหญ่ ใน C++ ผมมักใช้ testing harness ที่ผมเขียนขึ้นชื่อ CppUnitLite ซึ่งหน้าตาแตกต่างออกไปพอสมควร และผมจะอธิบายมันในบทนี้ด้วย อีกอย่างนะครับ ผมไม่ได้ลดคุณค่าผู้เขียนต้นฉบับของ CppUnit ด้วยการใช้ CppUnitLite นะ เพราะผมเองก็เป็นคนนั้นเมื่อนานมาแล้ว และผมเพิ่งค้นพบหลังจากที่ปล่อย CppUnit ออกไปว่ามันสามารถทำให้เล็กลง ใช้งานง่ายขึ้น และ portable มากขึ้นได้มาก ถ้าใช้ C idioms บางอย่างและ C++ language แค่ subset เล็ก ๆ

JUnit

ใน JUnit คุณเขียน tests โดยการ subclass คลาสที่ชื่อ TestCase

import junit.framework.*;

public class FormulaTest extends TestCase {
    public void testEmpty() {
        assertEquals(0, new Formula("").value());
    }

    public void testDigit() {
        assertEquals(1, new Formula("1").value());
    }
}

แต่ละ method ใน test class จะถูกนับเป็น test ถ้ามันมี signature ในรูปแบบ void testXXX() โดยที่ XXX คือชื่อที่คุณอยากตั้งให้ test นั้น แต่ละ test method สามารถมี code และ assertions ได้ ใน testEmpty ข้างต้น มี code ที่สร้าง Formula object ใหม่และเรียก value method ของมัน นอกจากนี้ยังมี assertion code ที่ตรวจสอบว่าค่านั้นเท่ากับ 0 หรือไม่ ถ้าเท่ากัน test ก็ผ่าน ถ้าไม่ test ก็ล้มเหลว

โดยสรุป นี่คือสิ่งที่เกิดขึ้นเมื่อคุณรัน JUnit tests: JUnit test runner โหลด test class อย่างที่แสดงข้างต้น จากนั้นใช้ reflection เพื่อหา test methods ทั้งหมด สิ่งที่มันทำต่อไปค่อนจะแอบ clever มันสร้าง object ที่แยกจากกันโดยสิ้นเชิงสำหรับแต่ละ test method จาก code ข้างต้น มันสร้าง object สองตัว: object ที่มีหน้าที่เดียวคือรัน testEmpty method และ object ที่มีหน้าที่เดียวคือรัน testDigit method ถ้าคุณสงสัยว่า object เหล่านี้เป็น class อะไร คำตอบคือ ในทั้งสองกรณี มันคือ FormulaTest เหมือนกัน แต่ละ object ถูกตั้งค่าให้รัน test method เพียงตัวเดียวบน FormulaTest ประเด็นสำคัญคือเรามี object ที่แยกจากกันอย่างสมบูรณ์สำหรับแต่ละ method ไม่มีทางที่พวกมันจะส่งผลกระทบต่อกันและกัน นี่คือตัวอย่าง:

public class EmployeeTest extends TestCase {
    private Employee employee;

    protected void setUp() {
        employee = new Employee("Fred", 0, 10);
        TDate cardDate = new TDate(10, 10, 2000);
        employee.addTimeCard(new TimeCard(cardDate,40));
    }

    public void testOvertime() {
        TDate newCardDate = new TDate(11, 10, 2000);
        employee.addTimeCard(new TimeCard(newCardDate, 50));
        assertTrue(employee.hasOvertimeFor(newCardDate));
    }

    public void testNormalPay() {
        assertEquals(400, employee.getPay());
    }
}

ในคลาส EmployeeTest เรามี method พิเศษที่ชื่อ setUp method setUp ถูกกำหนดไว้ใน TestCase และจะถูกรันในแต่ละ test object ก่อนที่จะรัน test method method setUp ช่วยให้เราสร้างชุด object ที่เราจะใช้ใน test ได้ ชุด object นั้นจะถูกสร้างในลักษณะเดียวกันก่อนการทำงานของแต่ละ test ใน object ที่รัน testNormalPay employee ที่ถูกสร้างใน setUp จะถูกตรวจสอบว่ามันคำนวณเงินเดือนถูกต้องสำหรับ timecard หนึ่งใบที่ถูกเพิ่มใน setUp หรือไม่ ใน object ที่รัน testOvertime employee ที่ถูกสร้างใน setUp สำหรับ object นั้นจะได้รับ timecard เพิ่มเติม และมีการตรวจสอบว่า timecard ใบที่สองทำให้เกิด overtime condition หรือไม่ method setUp จะถูกเรียกสำหรับแต่ละ object ของคลาส EmployeeTest และแต่ละ object เหล่านั้นก็จะมีชุด object ที่สร้างผ่าน setUp เป็นของตัวเอง ถ้าคุณต้องการทำอะไรพิเศษหลังจาก test ทำงานเสร็จ คุณสามารถ override อีก method ที่ชื่อ tearDown ซึ่งถูกกำหนดใน TestCase ได้ โดยมันจะทำงานหลังจาก test method ของแต่ละ object

เมื่อคุณเห็น xUnit harness ครั้งแรก มันอาจจะดูแปลกนิดหน่อย ทำไม test-case classes ถึงต้องมี setUp และ tearDown ด้วย? ทำไมเราไม่สร้าง object ที่เราต้องการใน constructor เลยล่ะ? เราทำได้ แต่จำไว้ว่า test runner ทำอะไรกับ test case classes มันจะไปที่ test case class แต่ละตัวและสร้าง object หนึ่งชุดสำหรับแต่ละ test method ซึ่งเป็น object จำนวนมาก แต่มันก็ไม่แย่ถ้า object เหล่านั้นยังไม่ได้ allocate สิ่งที่ต้องการไว้แล้ว การวาง code ใน setUp เพื่อสร้างสิ่งที่เราต้องการเมื่อเราต้องการจริง ๆ ช่วยประหยัดทรัพยากรได้มาก นอกจากนี้ การรัน setUp แบบช้าก็ยังทำให้เราสามารถตรวจจับและรายงานปัญหาที่อาจเกิดขึ้นระหว่างการ setup ได้

CppUnitLite

ตอนที่ผม port CppUnit ครั้งแรก ผมพยายามทำให้มันใกล้เคียงกับ JUnit เท่าที่จะทำได้ ผมคิดว่าน่าจะง่ายกว่าสำหรับคนที่เคยเห็น xUnit architecture มาก่อน มันเลยดูเหมือนเป็นทางเลือกที่ดีกว่า แต่เกือบจะทันทีที่ผมเจอปัญหาหลายอย่างที่ยากหรือเป็นไปไม่ได้ที่จะ implement อย่าง clean ใน C++ เพราะความแตกต่างระหว่างความสามารถของ C++ และ Java ปัญหาหลักคือ C++ ไม่มี reflection ใน Java คุณสามารถเก็บ reference ไปยัง methods ของ derived class, ค้นหา methods ใน runtime ฯลฯ ได้ ใน C++ คุณต้องเขียน code เพื่อ register method ที่คุณต้องการเข้าถึงใน runtime ส่งผลให้ CppUnit ใช้งานและเข้าใจยากขึ้นนิดหน่อย คุณต้องเขียน suite function ของตัวเองใน test class เพื่อให้ test runner สามารถรัน objects สำหรับแต่ละ method ได้

Test *EmployeeTest::suite()
{
    TestSuite *suite = new TestSuite;
    suite.addTest(new TestCaller<EmployeeTest>("testNormalPay",
            testNormalPay));
    suite.addTest(new TestCaller<EmployeeTest>("testOvertime",
            testOvertime));
    return suite;
}

พูดได้เลยว่านี่ค่อนข้างน่าเบื่อ มันรักษาโมเมนตัมในการเขียน tests ได้ยากเมื่อคุณต้องประกาศ test methods ใน class header, กำหนดใน source file, และลงทะเบียนใน suite method มี macro schemes ต่าง ๆ ที่ใช้แก้ปัญหาเหล่านี้ได้ แต่ผมเลือกที่จะเริ่มใหม่ ผมลงเอยด้วยแนวทางที่ให้คนเขียน test ได้ง่าย ๆ แค่เขียน source file นี้:

#include "testharness.h"
#include "employee.h"
#include <memory>

using namespace std;

TEST(testNormalPay,Employee)
{
    auto_ptr<Employee> employee(new Employee("Fred", 0, 10));
    LONGS_EQUALS(400, employee->getPay());
}

test นี้ใช้ macro ที่ชื่อ LONGS_EQUAL ที่เปรียบเทียบ long integer สองตัวว่ามีค่าเท่ากันหรือไม่ มันทำงานเหมือนกับ assertEquals ใน JUnit แต่ออกแบบมาสำหรับ long

macro TEST ทำสิ่งที่น่าเกลียดอยู่เบื้องหลัง มันสร้าง subclass ของ testing class และตั้งชื่อโดยการนำสอง arguments มาต่อกัน (ชื่อของ test และชื่อของ class ที่ถูกทดสอบ) จากนั้นมันสร้าง instance ของ subclass นั้นที่ถูกตั้งค่าให้รัน code ในปีกกา instance นี้เป็น static; เมื่อโปรแกรมโหลด มันจะเพิ่มตัวเองเข้าไปใน static list ของ test objects ต่อมา test runner สามารถไล่ผ่าน list และรัน test แต่ละตัวได้

หลังจากผมเขียน framework เล็ก ๆ นี้ ผมตัดสินใจไม่ปล่อยมันออกมาเพราะ code ใน macro ไม่ได้ชัดเจนนัก และผมใช้เวลาส่วนใหญ่ไปกับการโน้มน้าวให้คนเขียน code ที่ชัดเจนกว่า เพื่อนของผม Mike Hill เจอปัญหาเดียวกันก่อนที่เราจะเจอกัน และสร้าง testing framework สำหรับ Microsoft โดยเฉพาะชื่อ TestKit ที่จัดการ registration แบบเดียวกัน เมื่อมี Mike เป็นกำลังใจ ผมเริ่มลดจำนวน late C++ features ที่ใช้ใน framework เล็ก ๆ ของผม แล้วก็ปล่อยมันออกมา (ปัญหาพวกนี้เป็นปัญหาใหญ่ใน CppUnit เกือบทุกวันผมได้รับอีเมลจากคนที่ใช้ templates หรือ standard library ไม่ได้ หรือมีปัญหา exceptions กับ C++ compiler ของพวกเขา)

ทั้ง CppUnit และ CppUnitLite ก็เพียงพอในฐานะ testing harnesses test ที่เขียนด้วย CppUnitLite จะสั้นกว่าเล็กน้อย ผมเลยใช้มันสำหรับตัวอย่าง C++ ในหนังสือเล่มนี้

NUnit

NUnit เป็น testing framework สำหรับภาษา .NET คุณสามารถเขียน tests สำหรับ C# code, VB.NET code, หรือภาษาอื่น ๆ ที่รันบน .NET platform ได้ NUnit ทำงานใกล้เคียงกับ JUnit มาก ความแตกต่างที่สำคัญคือมันใช้ attributes เพื่อ mark test methods และ test classes ไวยากรณ์ของ attributes นั้นขึ้นอยู่กับ .NET language ที่ใช้เขียน tests

นี่คือ NUnit test ที่เขียนด้วย VB.NET:

Imports NUnit.Framework

<TestFixture()> Public Class LogOnTest
    Inherits Assertion

    <Test()> Public Sub TestRunValid()
        Dim display As New MockDisplay()
        Dim reader As New MockATMReader()
        Dim logon As New LogOn(display, reader)
        logon.Run()
        AssertEquals("Please Enter Card", display.LastDisplayedText)
        AssertEquals("MainMenu",logon.GetNextTransaction().GetType.Name)
    End Sub

End Class

<TestFixture()> และ <Test()> คือ attributes ที่ mark LogonTest เป็น test class และ TestRunValid เป็น test method ตามลำดับ

Other xUnit Frameworks (เฟรมเวิร์ก xUnit อื่น ๆ)

มี xUnit ports มากมายสำหรับภาษาและแพลตฟอร์มต่าง ๆ โดยทั่วไปแล้ว พวกมันรองรับการ spec, grouping, และการรัน unit tests ถ้าคุณต้องการหา xUnit port สำหรับแพลตฟอร์มหรือภาษาของคุณ ให้ไปที่ www.xprogramming.com และดูในส่วน Downloads เว็บไซต์นี้ดำเนินการโดย Ron Jeffries และเป็น de facto repository สำหรับ xUnit ports ทั้งหมด

General Test Harnesses (เครื่องมือทดสอบทั่วไป)

xUnit frameworks ที่ผมบรรยายในหัวข้อที่แล้วถูกออกแบบมาให้ใช้สำหรับ unit testing พวกมันสามารถใช้ทดสอบหลาย classes พร้อมกันได้ แต่งานแบบนั้นเป็น domain ของ FIT และ Fitnesse มากกว่า

Framework for Integrated Tests (FIT) (เฟรมเวิร์กสำหรับการทดสอบแบบบูรณาการ)

FIT เป็น testing framework ที่กระชับและสง่างาม พัฒนาโดย Ward Cunningham แนวคิดเบื้องหลัง FIT นั้นเรียบง่ายแต่ทรงพลัง ถ้าคุณสามารถเขียนเอกสารเกี่ยวกับระบบของคุณและฝังตารางที่อธิบาย inputs และ outputs สำหรับระบบไว้ในนั้น และถ้าเอกสารเหล่านั้นสามารถบันทึกเป็น HTML ได้ FIT framework ก็สามารถรันมันเป็น tests ได้

FIT รับ HTML, รัน tests ที่กำหนดในตาราง HTML ในนั้น, และสร้าง HTML output output จะดูเหมือนกับ input โดยข้อความและตารางทั้งหมดคงไว้ อย่างไรก็ตาม เซลล์ในตารางจะถูกระบายสีเขียวเพื่อแสดงค่าที่ทำให้ test ผ่าน และสีแดงเพื่อแสดงค่าที่ทำให้ test ล้มเหลว คุณยังสามารถใช้ options เพื่อให้ข้อมูลสรุปของ test ถูกวางไว้ในผลลัพธ์ HTML ได้ด้วย

สิ่งเดียวที่คุณต้องทำเพื่อให้สิ่งนี้ทำงานคือการปรับแต่ง table-handling code บางส่วนเพื่อให้มันรู้วิธีรันส่วนของ code ของคุณและดึงผลลัพธ์จากมัน โดยทั่วไปแล้วนี่ค่อนข้างง่ายเพราะ framework มี code ที่รองรับ table types หลายแบบ

หนึ่งในสิ่งที่ทรงพลังมากเกี่ยวกับ FIT คือความสามารถในการส่งเสริมการสื่อสารระหว่างคนที่เขียนซอฟต์แวร์และคนที่ต้องการระบุว่าซอฟต์แวร์ควรทำอะไร คนที่ระบุความต้องการสามารถเขียนเอกสารและฝัง tests จริงลงไป tests จะถูกรันแต่จะไม่ผ่าน ต่อมานักพัฒนาสามารถเพิ่มฟีเจอร์ต่าง ๆ และ tests ก็จะผ่าน ทั้งผู้ใช้และนักพัฒนาสามารถมีมุมมองร่วมกันที่ทันสมัยเกี่ยวกับความสามารถของระบบได้

ยังมีอีกมากเกี่ยวกับ FIT ที่ผมไม่สามารถอธิบายได้ทั้งหมดในที่นี้ มีข้อมูลเพิ่มเติมเกี่ยวกับ FIT ได้ที่ http://fit.c2.com

Fitnesse

Fitnesse ก็คือ FIT ที่ถูกโฮสต์ใน wiki ส่วนใหญ่พัฒนาโดย Robert Martin และ Micah Martin ผมมีส่วนนิดหน่อย แต่ก็ถอนตัวเพื่อมุ่งสมาธิไปที่หนังสือเล่มนี้ ผมตั้งตารอที่จะกลับไปทำงานกับมันเร็ว ๆ นี้

Fitnesse รองรับหน้าเว็บแบบลำดับชั้นที่กำหนด FIT tests หน้าของตาราง tests สามารถรันทีละหน้าหรือเป็น suites ก็ได้ และมี options มากมายที่ทำให้การทำงานร่วมกันในทีมเป็นเรื่องง่าย Fitnesse สามารถดาวน์โหลดได้ที่ http://www.fitnesse.org เช่นเดียวกับ testing tools อื่น ๆ ทั้งหมดที่อธิบายในบทนี้ มันฟรีและได้รับการสนับสนุนจากชุมชนนักพัฒนา