JavaScript: UnitTest ด้วย QUnit กันเถอะ…Action!

เกริ่นนำ

UnitTest ความหมายในที่นี้ คือการทดสอบองค์ประกอบ(components)ของซอฟแวร์ชิ้นหนึ่งที่เราสนใจต้องการพิสูจน์ หรือทดลองวัตถุใดๆภายในองค์ประกอบนั้น ทั้งสถานะ และคุณสมบัติของมัน โดยองค์ประกอบส่วนอื่นที่เกี่ยวข้องกับองค์ประกอบนี้เราจะต้องจำลอง หรือกำหนดสมมุติ สถานะ และคุณสมบัติของพวกมันตามที่เราต้องการได้

QUnit คือเครื่องมือที่ถูกสร้างมาช่วยเรากำหนดค่าสถานะ,  run components, ตรวจสอบผล และแสดงผลของ UnitTest กับองค์ประกอบของเราที่พัฒนาด้วย JavaScripts เท่านั้น ในส่วนของการ ตั้งสมมุติฐานทดสอบส่วนขององค์ประกอบ ส่วนของการจำลอง สมมุติองค์ประกอบอื่นๆที่เกี่ยวข้องกับมัน ยังเป็นสิ่งที่เราต้องทำ และหาเครื่องมืออื่นๆมาช่วยอยู่ เช่น ในการจำลององค์ประกอบอื่นๆภายใต้สมมติฐานที่เราต้องการควบคุม(stub/mock)จะใช้ library JsMockito ช่วยทำเป็นต้น

แนวคิดของการทำงาน UnitTest เพื่อตรวจสอบองค์ประกอบ เป้าหมายที่เราสนใจ ตามสมมุติฐาน หรือความคาดหวังต้องการ หรือที่เราจะต้องได้รับจากองค์ประกอบของเรา ดังนั้น มันจึงมีขั้นตอน ที่เป็นหลักการต้องกระทำอยู่เสมอๆ ในการทำ UnitTest ทุกๆครั้ง เลยก็คือ

  • ตั้งสมมุติฐาน การตั้งสมมติฐาน จะมีการกำหนด ผลที่เราคาดหวังว่าจะต้องได้รับ(expected) กับ การจำกัดขอบเขตสภาวะแวดล้อมต่างๆทั้งหมดที่จะทำให้ components เป้าหมายของเราบรรลุผลเป็นไปตามที่เราคาดหมายไว้ได้ มักจะเรียกขั้นตอนนี้ว่า assign หรือ setup
  • ดำเนินการกระทำกับ components เป้าหมายของเรา ตามสมมุติฐานที่ได้กำหนดไว้แล้ว มักจะเรียกขั้นตอนนี้ว่า action หรือ exercise
  • ตรวจสอบผล และแสดงผล มักจะเรียกขั้นตอนนี้ว่า assert หรือ verify

การตรวจสอบผลที่ถูกต้องตามสมมุติฐานต้องให้ผ่านทั้งหมด แต่ถ้าไม่ถูกต้องทั้งหมดให้กลับไปแก้ไข องค์ประกอบเป้าหมายของเรา แล้วกลับไปเริ่มต้นที่ขั้นตอนที่ 1 ใหม่ วนไปเรื่อยๆจนกระทั้งองค์ประกอบเป้าหมายของเราผ่านทุกๆสมมุติฐานที่เราตั้งไว้ทั้งหมดแล้ว ขั้นตอนก็มีเพียงแค่นี้ เอาละ เรามาเริ่มต้นเขียน UnitTest ด้วย QUnit กันเลย

ทำ UnitTest ด้วย QUnit… Action!

1. สร้าง folder ที่เก็บ project web application ของเราขึ้นมา ตั้งชื่อว่า ScrumStudio  ซึ่งในนี้จะเก็บทุกสิ่งอย่างสำหรับสร้าง web application ของเราทั้งหมด เช่น JavaScript, HTML และ CSS ดังนั้น ให้สร้าง folder ย่อยๆแบ่งโครงสร้างไว้ตามผมไปเลย เป็นโครงสร้างตามผม แบบนี้

\ScrumStudio
	\css
	\scripts
	\test

2. download CSS กับ JavaScript ของ QUnit ได้ที่ qunitjs ซึ่งจะได้ 2 file ที่ชื่อว่า qunit-1.12.0.js กับ qunit-1.12.0.css เอามาแยกเก็บไว้ใน folder ตามผมไปก่อนดังนี้

\ScrumStudio
	\css
		qunit-1.12.0.css
	\scripts
		qunit-1.12.0.js
	\test

3. ถึงขั้นนี้ให้เพิ่ม HTML สำหรับ run test หรือใช้พิสูจน์เนื้อหาในงานของเราเข้าไปก่อน ในที่นี้ผมตั้งชื่อว่า spike-scrum-studiot.html ครับ เพิ่มเข้าไปไว้ที่ folder test มีโครงสร้างแบบนี้

\ScrumStudio
	\css
		qunit-1.12.0.css
	\scripts
		qunit-1.12.0.js
	\test
		spike-scrum-studio.html

4. เปิด file spike-scrum-studio.html ด้วย text editor อะไรก็ได้ แล้วเขียน code ตามข้างล่างนี้ลงไป

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Spike for Scrum Studio</title>
  <link rel="stylesheet" href="./css/qunit-1.12.0.css">
</head>
<body>
  <div id="qunit"></div>
  <div id="qunit-fixture"></div>
  <script src="./scripts/qunit-1.12.0.js" type="text/javascript"></script>
  <script type="text/javascript">
      window.onload = function () {
          test("QUnit setup", function () {
              equal(true, true, "OK");
          });
      }
  </script>
</body>
</html>

จาก code HTML ข้างต้นนั้น ผมได้ include CSS กับ JavaScript สำหรับ QUnit เข้ามาไว้ด้วยแล้วไฮไลท์เป็นสีแดง

ส่วนที่ผม ไฮไลท์เป็นสีส้มนั้นคือ code ส่วนที่อธิบายว่าเราได้ ติดตั้ง QUnit เป็นที่เรียบร้อบแล้วนะ เมื่อแสดงผล HTML นี้ด้วย Chrome หรือ IE จะแสดงผลคล้ายๆผมตามข้างล่างนี้

ก็เป็นอันว่าเราได้ติดตั้ง QUnit เสร็จเรียบร้อยแล้วพร้อมที่จะ UnitTest กันละ

5. ถึงตรงนี้เราก็เริ่มงานกันต่อ สิ่งที่เราจะทำคือ Scrum Studio งั้นเริ่มจาก entity Product กับ ProductBacklog กันเลยก็ละกัน โดยแนวคิด Scrum มาจากที่นี่ ScrumPrimer

\ScrumStudio
	\css
		qunit-1.12.0.css
	\scripts
		\domain-model
		   product.js
		   product-backlog.js
		   qunit-1.12.0.js
	\test
		spike-scrum-studiot.html

6. สำหรับ product.js เขียน JavaScript ตามข้างล่างนี้ลงไป

function Product(properties) {
    properties = properties || {};

    this.Name = properties.Name;
    this.ProductBacklogs = properties.ProductBacklogs;
}

7. สำหรับ product-backlog.js เขียน JavaScripts ตามข้างล่างนี้ลงไป

function ProductBacklog(properties) {
    properties = properties || {};

    this.Priority = properties.Priority;
    this.Item = properties.Item;
    this.Details = properties.Details;
    this.InitialSizeEstimate = properties.InitialSizeEstimate;
}

8. เขียน UnitTest ทดสอบ properties ของ Product กับ ProductBacklog ต่อเลย เปิด file spike-scrum-studio.html แก้ไข code ตรงจุดที่ผมไฮไลท์ข้างล่างนี้เข้าไปครับ

...
  <div id="qunit"></div>
  <div id="qunit-fixture"></div>
  <script src="./scripts/qunit-1.12.0.js" type="text/javascript"></script>
  <script src="./scripts/domain-model/product.js" type="text/javascript"></script>
  <script src="./scripts/domain-model/product-backlog.js" type="text/javascript"></script>
  <script type="text/javascript">
      window.onload = function () {
          test("QUnit setup", function () {
              equal(true, true, "OK");
          });

          test("Check Product Properties", function () {
              var product = new Product({
                  Name: "ScrumStudio"
              });

              equal(product.Name, "ScrumStudio", "Product.Name is ScrumStudio");

              product.ProductBacklogs.push(new ProductBacklog());
              product.ProductBacklogs.push(new ProductBacklog());

              equal(product.ProductBacklogs.length, 2, "Product.ProductBacklogs have 2 Items");
          });

          test("Check ProductBacklog Properties", function () {
              var productBacklog = new ProductBacklog({
                  Priority: 1,
                  Item: "As a ScrumMaster, I want to add a product backlog to my product",
                  Details: "...",
                  InitialSizeEstimate: 10
              });

              equal(productBacklog.Priority, 1, "productBacklog.Priority is 1");
              equal(productBacklog.Item, "As a ScrumMaster, I want to add a product backlog to my product", "productBacklog.Item is OK");
              equal(productBacklog.Details, "...", "productBacklog.Details is OK");
              equal(productBacklog.InitialSizeEstimate, 10, "productBacklog.InitialSizeEstimate = 10");
          });
      }
  </script>
..

เมื่อ run file spike-scrum-studio.html นี้ดูอีกครั้งก็จะแสดงผลแบบนี้

9. ถึงตรงนี้เราจะเห็นว่า สถานะทดสอบเป็นเขียวหมดนั่นหมายความว่า domain model ของเราทั้ง Product กับ ProductBacklog พร้อมไปใช้งานได้แล้ว

สมมุติต่อมา เราได้ค้นพบว่า entity Product มันไม่ถูกต้อง เพราะยังไม่ได้ protect ProductBacklog ที่เพิ่มเข้าไปเลย จาก code เดิมเราเพิ่ม product backlog โดยการเพิ่มไปที่ list ของ product backlog ที่อยู่ใน entity Product ได้โดยตรง ผมจึงกลับไปดำเนินการแก้ไข class Product ที่ file product.js ได้เป็นดังนี้

function Product(properties) {
    properties = properties || {};

    this.Name = properties.Name;

    var productBacklogs = properties.ProductBacklogs || [];

    this.AddBacklog = function (backlog) {
        productBacklogs.push(backlog);
    };

    this.TotalProductBacklog = function () {
        return productBacklogs.length;
    };
}

เมื่อแก้ไข class Product เสร็จแล้วก็ กลับไปแก้ไข UnitTest เพื่อเรียก function ที่เราเพิ่มเข้ามาใหม่ดูอีกที ซิว่า method ที่เพิ่มเข้ามานั้น protect ProductBacklogs และ มันยังคงคุณสมบัติเดิมอยู่รึเปล่า แบบนี้

..
          test("Check Product Properties", function () {
              var product = new Product({
                  Name: "ScrumStudio"
              });

              equal(product.Name, "ScrumStudio", "Product.Name is ScrumStudio");

              equal(product.ProductBacklogs, undefined, "Product.ProductBacklogs have protected");

              product.AddBacklog(new ProductBacklog());
              product.AddBacklog(new ProductBacklog());

              equal(product.TotalProductBacklog(), 2, "Product.ProductBacklogs have 2 Items");
          });
..

เมื่อ run spike-scrum-studio.html อีกทีก็จะแสดงผลใหม่แบบนี้

ก็ขอจบบล็อค UnitTest ด้วย QUnit กันเถอะ…Action! ไว้เพียงเท่านี้นะขอรับ หวังว่าคงจะนำ QUnit ไปประยุกต์ใช้ทดสอบกับงาน JavaScript ของท่านได้เป็นประโยชน์ช่วยเพิ่มความมั่นใจให้กับชิ้นงาน และคุณภาพขององค์ประกอบส่วนหนึ่ง ก่อนปล่อยนำไปประกอบกับชิ้นงานของผู้อื่นเพื่อใช้ประกอบขึ้นมาเป็นซอฟแวร์โปรดักส์ที่สมบูรณ์ใช้ประโยชน์ได้ต่อ

ขอบคุณครับ

#:P

Advertisements

#javascript, #qunit, #unittest