Skip to content

การเขียนโค้ดเพื่อตรวจทานโค้ดของตัวเอง (Automated Testing)

  • ในตอนก่อนหน้า เราได้สร้างฟังก์ชันชื่อ getGrade:

    ดูโค้ด
    js
    let getGrade = (score) => {
      if (score >= 80) {
        return 'A'
      } else if (score >= 70) {
        return 'B'
      } else if (score >= 60) {
        return 'C'
      } else if (score >= 50) {
        return 'D'
      } else {
        return 'F'
      }
    }
  • เราทดสอบฟังก์ชันนี้โดยการเรียกใช้งานมัน ส่งอินพุตต่าง เข้าไป แล้วตรวจสอบดูว่าผลลัพธ์ถูกต้องหรือไม่

    
                
              getGrade(100)
                
                
              "A"
                
              getGrade(90)
                
                
              "A"
                
              getGrade(80)
                
                
              "A"
                
              getGrade(75)
                
                
              "B"
                
              getGrade(64)
                
                
              "C"
                
              getGrade(53)
                
                
              "D"
                
              getGrade(42)
                
                
              "F"
  • ทุกครั้งที่เราปรับปรุงโค้ด เราจะต้องทำการทดสอบใหม่อีกครั้ง เพื่อให้แน่ใจว่าเราไม่ได้ทำอะไรให้พัง นี่เป็นเรื่องน่าเบื่อมากหากเราต้องทำด้วยตัวเองทุกครั้ง ดังนั้นนักพัฒนาซอฟต์แวร์จึงใช้เทคนิคที่เรียกว่า automated testing (การทดสอบอัตโนมัติ) มาช่วยในเรื่องนี้

การทดสอบอัตโนมัติคืออะไร

  • การทดสอบอัตโนมัติ คือ การเขียนโค้ดเพื่อให้มันทดสอบโค้ดให้เรา เราจะได้ไม่ต้องทำการทดสอบด้วยตัวเองทุกครั้งที่เราแก้โค้ด

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

    js
    let testGetGrade = () => {
      let correctCount = 0
      if (getGrade(100) === 'A') {
        correctCount = correctCount + 1
      }
      if (getGrade(90) === 'A') {
        correctCount = correctCount + 1
      }
      if (getGrade(80) === 'A') {
        correctCount = correctCount + 1
      }
      if (getGrade(75) === 'B') {
        correctCount = correctCount + 1
      }
      if (getGrade(64) === 'C') {
        correctCount = correctCount + 1
      }
      if (getGrade(53) === 'D') {
        correctCount = correctCount + 1
      }
      if (getGrade(42) === 'F') {
        correctCount = correctCount + 1
      }
    
      let totalCount = 7
      if (correctCount === totalCount) {
        console.log(`✅ Score: ${correctCount}/${totalCount}`)
      } else {
        console.log(`❌ Score: ${correctCount}/${totalCount}`)
      }
    }
    
                
              testGetGrade()
              ✅ Score: 7/7
                
                
              undefined
  • หากเราลองแก้ฟังก์ชัน getGrade() ให้ทำงานผิดพลาด แล้วลองทดสอบอีกครั้ง ก็จะทำให้เราทราบได้ทันทีว่าต้องมีอะไรผิดพลาดตรงไหนสักที่

    js
    let getGrade = (score) => {
      if (score >= 80) {
        return 'A'
      } else if (score >= 70) {
        return 'B'
      } else if (score >= 60) {
        return 'C'
      } else if (score >= 50) {
        return 'D'
      } else {
        return 'E' // แก้จาก 'F' เป็น 'E'
      }
    }
    
                
              testGetGrade()
              ❌ Score: 6/7
                
                
              undefined
  • เราสามารถปรับปรุง “test runner” นี้ โดยทำให้มันรายงานว่ากรณีไหนที่ฟังก์ชันของเราให้คำตอบผิด

    ดูโค้ด
    js
    let testGetGrade = () => {
      // เก็บจำนวนกรณีที่ไม่ถูกต้องแทน
      let incorrectCount = 0
    
      // เช็คเฉพาะกรณีที่ไม่ถูกต้อง
      if (getGrade(100) !== 'A') {
        console.log('❌ getGrade(100) should be A')
        incorrectCount = incorrectCount + 1
      }
      if (getGrade(90) !== 'A') {
        console.log('❌ getGrade(90) should be A')
        incorrectCount = incorrectCount + 1
      }
      if (getGrade(80) !== 'A') {
        console.log('❌ getGrade(80) should be A')
        incorrectCount = incorrectCount + 1
      }
      if (getGrade(75) !== 'B') {
        console.log('❌ getGrade(75) should be B')
        incorrectCount = incorrectCount + 1
      }
      if (getGrade(64) !== 'C') {
        console.log('❌ getGrade(64) should be C')
        incorrectCount = incorrectCount + 1
      }
      if (getGrade(53) !== 'D') {
        console.log('❌ getGrade(53) should be D')
        incorrectCount = incorrectCount + 1
      }
      if (getGrade(42) !== 'F') {
        console.log('❌ getGrade(42) should be F')
        incorrectCount = incorrectCount + 1
      }
    
      // หากไม่มีกรณีที่ผิด แสดงว่าทุกอย่างถูกต้อง
      if (incorrectCount === 0) {
        console.log('✅ All test cases passed')
      }
    }
    
                
              testGetGrade()
              ❌ getGrade(42) should be F
                
                
              undefined
  • อย่างที่เห็นไป การสร้าง test runner ใช้เองอาจต้องใช้เวลาและความพยายามอย่างมาก เพื่อให้ได้ประโยชน์จากมันอย่างเต็มที่ ดังนั้นนักพัฒนาซอฟต์แวร์จึงใช้สิ่งที่เรียกว่า testing framework มาช่วยในเรื่องนี้

Testing Framework

  • Testing framework เป็นชุดเครื่องมือที่ช่วยให้คุณสร้างชุดทดสอบได้สะดวกยิ่งขึ้น ในภาษา JavaScript มี Testing framework ให้เลือกใช้มากมาย อย่างเช่น QUnit, Mocha, Jasmine, Jest และ Vitest

    Testing Framework ต่าง

    • Jest เป็นเครื่องมือที่ได้รับความนิยมมากที่สุด (ข้อมูลจากปี 2022)
    • Vitest เป็นเครื่องมือที่สร้างขึ้นมาในปี 2021 ซึ่งเป็นเครื่องมือที่ออกแบบมาสำหรับ JavaScript ในยุคใหม่ ซึ่งกำลังได้รับความนิยมเพิ่มขึ้นอย่างต่อเนื่อง
    • QUnit เป็นเครื่องมือที่มีมาตั้งแต่ปี 2011 ใช้งานค่อนข้างง่ายสำหรับมือใหม่

    ในตอนนี้เราจะยกตัวอย่างการใช้ QUnit ในการทดสอบ

  • Testing framework จะกำหนดรูปแบบวิธีการเขียนชุดทดสอบไว้ให้ ตัวอย่างเช่น ถ้าเราใช้ QUnit เราเขียนโค้ดแบบนี้:

    js
    QUnit.test('getGrade', (assert) => {
      assert.equal(getGrade(100), 'A')
      assert.equal(getGrade(90), 'A')
      assert.equal(getGrade(80), 'A')
      assert.equal(getGrade(75), 'B')
      assert.equal(getGrade(64), 'C')
      assert.equal(getGrade(53), 'D')
      assert.equal(getGrade(50), 'D')
      assert.equal(getGrade(49), 'F')
      assert.equal(getGrade(42), 'F')
      assert.equal(getGrade(0), 'F')
    })
  • เมื่อทำการ run test จะได้ผลลัพธ์ดังนี้:

  • จะเห็นว่า QUnit สร้างหน้าเว็บเพื่อแสดงผลการทดสอบให้เราดูได้ง่าย ด้วย และเมื่อมีกรณีที่ผิดพลาด ก็จะช่วยให้เราเห็นจุดที่ไม่ถูกต้องได้ง่ายและชัดเจน

ตัวอย่างการใช้ QUnit

  • เริ่มจากโค้ดเริ่มต้นนี้

    html
    <!doctype html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>Test Suite</title>
        <link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.20.0.css">
      </head>
      <body>
    
        <div id="qunit"></div>
        <div id="qunit-fixture"></div>
        <script src="https://code.jquery.com/qunit/qunit-2.20.0.js"></script>
        <script>
    
          let add = (a, b) => {
            return a + b
          }
    
          QUnit.test('add 2 numbers', (assert) => {
            assert.equal(add(1, 2), 3)
            assert.equal(add(20, 30), 50)
          })
    
        </script>
    
      </body>
    </html>

    สร้างโปรเจกต์ใน Glitch

    สามารถทำตามได้โดยการ Remix โปรเจกต์นี้บน Glitch: https://glitch.com/~hello-qunit

  • จะได้ผลลัพธ์ดังนี้:

    index.html
  • นำฟังก์ชัน getGrade มาแทนที่ฟังก์ชัน add และเขียนชุดทดสอบใหม่เพื่อทดสอบฟังก์ชัน getGrade

    ดูโค้ด
    html
    <!doctype html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>Test Suite</title>
        <link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.20.0.css">
      </head>
      <body>
    
        <div id="qunit"></div>
        <div id="qunit-fixture"></div>
        <script src="https://code.jquery.com/qunit/qunit-2.20.0.js"></script>
        <script>
    
          let getGrade = (score) => {
            if (score >= 80) {
              return 'A'
            } else if (score >= 70) {
              return 'B'
            } else if (score >= 60) {
              return 'C'
            } else if (score >= 50) {
              return 'D'
            } else {
              return 'F'
            }
          }
    
          QUnit.test('getGrade', (assert) => {
            assert.equal(getGrade(100), 'A')
            assert.equal(getGrade(90), 'A')
            assert.equal(getGrade(80), 'A')
            assert.equal(getGrade(75), 'B')
            assert.equal(getGrade(64), 'C')
            assert.equal(getGrade(53), 'D')
            assert.equal(getGrade(50), 'D')
            assert.equal(getGrade(49), 'F')
            assert.equal(getGrade(42), 'F')
            assert.equal(getGrade(0), 'F')
          })
    
        </script>
    
      </body>
    </html>
    ดูผลลัพธ์
    index.html
  • ลองแก้ฟังก์ชัน getGrade ให้ทำงานผิดพลาด

    ดูโค้ด
    html
    <!doctype html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>Test Suite</title>
        <link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.20.0.css">
      </head>
      <body>
    
        <div id="qunit"></div>
        <div id="qunit-fixture"></div>
        <script src="https://code.jquery.com/qunit/qunit-2.20.0.js"></script>
        <script>
    
          let getGrade = (score) => {
            if (score >= 80) {
              return 'A'
            } else if (score >= 70) {
              return 'B'
            } else if (score >= 60) {
              return 'C'
            } else if (score >= 50) {
              return 'D'
            } else {
              return 'E' // แก้จาก 'F' เป็น 'E'
            }
          }
    
          QUnit.test('getGrade', (assert) => {
            assert.equal(getGrade(100), 'A')
            assert.equal(getGrade(90), 'A')
            assert.equal(getGrade(80), 'A')
            assert.equal(getGrade(75), 'B')
            assert.equal(getGrade(64), 'C')
            assert.equal(getGrade(53), 'D')
            assert.equal(getGrade(50), 'D')
            assert.equal(getGrade(49), 'F')
            assert.equal(getGrade(42), 'F')
            assert.equal(getGrade(0), 'F')
          })
    
        </script>
    
      </body>
    </html>
    ดูผลลัพธ์
    index.html

    QUnit จะแสดงรายงานให้ดูว่ากรณีไหนที่ฟังก์ชันของเราให้คำตอบผิด

การทำ automated testing ช่วยให้เรามีความเชื่อมั่นในโค้ดของเรามากขึ้น

  • ครั้งหนึ่งผมเคยสร้างเกม Bemuse เป็นเกมแนวดนตรี เกมนี้สร้างด้วยโค้ดหลายพันบรรทัด และมี logic ต่าง ที่ซับซ้อนมาก โดยแต่ละเกม จะใช้เวลาเล่นประมาณ 2-3 นาที

  • ระหว่างที่ผมพัฒนาเกมนี้ เวลาผมแก้โค้ดจุดหนึ่ง ผมต้องการความมั่นใจ ว่าผมไม่ได้เผลอไปทำให้ส่วนอื่น พัง ดังนั้นระหว่างที่พัฒนาเกม ผมจึงเขียน automated test ให้โค้ดชิ้นส่วนต่าง ไปด้วย ชุดทดสอบของเกมนี้ มีเทสต์อยู่ประมาณ 200 ข้อ ซึ่งใช้เวลารันไม่ถึง 3 วินาที

  • ทำให้เวลาผมแก้โค้ด กดเซฟ ผมรอให้เทสต์รันประมาณ 3 วินาที ผมก็ค่อนข้างมั่นใจได้แล้ว ว่าทุกอย่างน่ายังใช้งานได้เหมือนเดิม โดยไม่ต้องเสียเวลา 2–3 นาทีเพื่อเล่นเกมใหม่ทุกครั้งที่แก้โค้ดครับ