diff --git a/1-js/03-code-quality/05-testing-mocha/article.md b/1-js/03-code-quality/05-testing-mocha/article.md index 1e3ef14db..84775eca1 100644 --- a/1-js/03-code-quality/05-testing-mocha/article.md +++ b/1-js/03-code-quality/05-testing-mocha/article.md @@ -1,193 +1,193 @@ -## ทดสอบโค้ดแบบอัตโนมัติกับ Mocha +# การทดสอบอัตโนมัติด้วย Mocha -โค้ดที่เราจะเขียนต่อไปนี้ จะมีการทดสอบอัตโนมัติด้วยนะ ซึ่งเป็นเทคนิคที่ใช้กันทั่วไปในโปรเจ็กต์จริงด้วย +ในงานต่อๆ ไปจะมีการใช้การทดสอบอัตโนมัติ ซึ่งเป็นสิ่งที่ใช้กันอย่างแพร่หลายในโปรเจ็กต์จริงด้วย -## ทำไมเราต้องทดสอบ? +## ทำไมต้องมีการทดสอบ? -เวลาเขียนฟังก์ชัน เรานึกออกว่ามันควรจะทำงานยังไง ใส่อะไรเข้าไป แล้วได้อะไรออกมา +เวลาเขียนฟังก์ชัน เรามักจะจินตนาการได้ว่ามันควรจะทำงานอย่างไร พารามิเตอร์แบบไหนให้ผลลัพธ์แบบไหน -ตอนพัฒนา เราก็จะทดสอบโดยการรันฟังก์ชัน แล้วดูว่าผลลัพธ์เป็นอย่างที่คาดหวังไว้หรือเปล่า เช่น รันใน console อะไรแบบนั้น +ระหว่างพัฒนา เราตรวจสอบฟังก์ชันได้โดยการรันและเปรียบเทียบผลลัพธ์กับสิ่งที่คาดหวังไว้ เช่น อาจทำในคอนโซล -ถ้าอะไรผิด ก็แก้โค้ด รันใหม่ ดูผลลัพธ์... วนๆ ไปจนกว่ามันจะเวิร์ค +ถ้ามีอะไรผิดพลาด ก็แก้โค้ด รันใหม่ เช็คผลลัพธ์ วนไปแบบนี้จนกว่าจะใช้ได้ -แต่การ "รันซ้ำ" แบบแมนนวลเนี่ย มันไม่ค่อยเวิร์คอะดิ +แต่การ "รันซ้ำ" ด้วยมือแบบนั้นยังไม่สมบูรณ์แบบ -**เวลาทดสอบโค้ดแบบรันซ้ำเอง มันง่ายมากที่จะพลาดอะไรบางอย่าง** +**เมื่อทดสอบโค้ดโดยการรันซ้ำเอง มักจะมีบางอย่างหลุดไปได้ง่าย** -เช่น เราสร้างฟังก์ชัน `f` เขียนโค้ดเสร็จ ทดสอบ: `f(1)` ทำงาน แต่ `f(2)` ไม่ทำงาน เราแก้โค้ด แล้วตอนนี้ `f(2)` ทำงานแล้ว ดูเหมือนเสร็จแล้วใช่ไหม? แต่เราลืมทดสอบ `f(1)` อีกที อาจจะเจอ error ก็ได้ +ยกตัวอย่างเช่น สมมติเรากำลังเขียนฟังก์ชัน `f` เขียนโค้ดนิดหน่อย ลองทดสอบดู `f(1)` ใช้ได้ แต่ `f(2)` ไม่ทำงาน เราแก้โค้ดแล้วตอนนี้ `f(2)` ก็ใช้ได้แล้ว ดูเหมือนจะครบแล้วใช่ไหม แต่เราลืมย้อนกลับไปเทสต์ `f(1)` ใหม่ ซึ่งอาจจะมีข้อผิดพลาดโผล่ขึ้นมาก็ได้ -นี่เป็นเรื่องปกติมาก เวลาเราพัฒนาอะไร เรามักจะคิดถึงกรณีต่างๆ ที่อาจเกิดขึ้น แต่การคาดหวังให้นักเขียนโค้ดทดสอบทุกกรณีด้วยตัวเองหลังจากทุกการเปลี่ยนแปลง แต่มันเป็นไปได้ยาก ดังนั้น จึงง่ายที่จะแก้สิ่งหนึ่ง แล้วดันไปทำอีกสิ่งเสีย +นี่เป็นเรื่องปกติมาก ตอนที่เรากำลังพัฒนาอะไรบางอย่าง มักจะมีหลายกรณีการใช้งานที่เราต้องคำนึงถึง แต่การที่จะให้โปรแกรมเมอร์มานั่งเช็คทุกกรณีด้วยตัวเองทุกครั้งที่มีการเปลี่ยนแปลง เป็นเรื่องยาก เลยกลายเป็นว่าแก้ไขอย่างหนึ่ง แล้วไปทำให้อีกอย่างพังได้ง่าย -**การทดสอบอัตโนมัติ หมายถึงการเขียนทดสอบแยกต่างหากจากโค้ด พวกมันจะรันฟังก์ชันของเราในหลายๆ กรณี แล้วเปรียบเทียบผลลัพธ์กับที่คาดหวัง** +**การทดสอบอัตโนมัติหมายถึงการเขียนเทสต์แยกออกมาต่างหาก เป็นส่วนเสริมของโค้ด เทสต์เหล่านั้นจะรันฟังก์ชันของเราในหลากหลายวิธี และเปรียบเทียบผลลัพธ์กับสิ่งที่คาดหวัง** ## Behavior Driven Development (BDD) -เราจะเริ่มต้นด้วยเทคนิคที่เรียกว่า Behavior Driven Development: [http://en.wikipedia.org/wiki/Behavior-driven_development](http://en.wikipedia.org/wiki/Behavior-driven_development) หรือเรียกสั้นๆ ว่า BDD +เรามาเริ่มต้นด้วยเทคนิคที่เรียกว่า [Behavior Driven Development](http://en.wikipedia.org/wiki/Behavior-driven_development) หรือเรียกสั้นๆ ว่า BDD กัน -**BDD คือสามสิ่งในหนึ่งเดียว: ทดสอบ + เอกสาร + ตัวอย่าง** +**BDD คือ 3 สิ่งในหนึ่งเดียว ทั้งเทสต์ เอกสาร และตัวอย่างการใช้งาน** -เพื่อให้เข้าใจ BDD เราจะดูตัวอย่างการพัฒนาในทางปฏิบัติ +เพื่อทำความเข้าใจ BDD เราจะมาดูตัวอย่างกรณีศึกษาในการพัฒนาซอฟต์แวร์กัน -## ปฏิบัติการสร้าง "pow": รู้จัก "spec" ก่อน +## การพัฒนา "pow": ข้อกำหนด -สมมติว่าเราอยากสร้างฟังก์ชัน `pow(x, n)` ที่เอา x ยกกำลัง n โดยที่ n ต้องเป็นเลขเต็มบวกนะ +สมมติว่าเราอยากเขียนฟังก์ชัน `pow(x, n)` ที่คืนค่า `x` ยกกำลังด้วยเลขชี้กำลังจำนวนเต็ม `n` โดยเราสมมติว่า `n≥0` -ฟังดูน่าเบื่อใช่มั้ย? จริงๆ แล้วเจ้านี่มีตัว `**` ใน JavaScript ที่ทำได้อยู่แล้ว แต่เดี๋ยวก่อน! เราจะใช้ตัวอย่างนี้ฝึกฝนวิธีคิดในการสร้างฟังก์ชันที่ซับซ้อนกว่านี้ได้ด้วยนะ +จริงๆ แล้วนี่เป็นแค่ตัวอย่าง เพราะในจาวาสคริปต์มีตัวดำเนินการ `**` ที่ทำแบบนี้ได้อยู่แล้ว แต่ตรงนี้เราจะมุ่งเน้นไปที่วิธีการพัฒนาที่สามารถนำไปประยุกต์ใช้กับงานที่ซับซ้อนกว่านี้ได้ด้วย -ก่อนเขียนโค้ด `pow` เราต้องรู้จักมันก่อนว่ามันจะทำอะไรบ้าง และอธิบายมันออกมาเป็นคำพูด +ก่อนจะเขียนโค้ดของ `pow` เราสามารถจินตนาการถึงสิ่งที่ฟังก์ชันควรจะทำและบรรยายมันไว้ก่อนได้ -คำอธิบายนี้เรียกว่า *specification* หรือเรียกสั้นๆ ว่า *spec* ซึ่งจะรวมถึงวิธีการใช้งานและการทดสอบว่ามันทำงานถูกต้องหรือเปล่า +คำบรรยายแบบนี้เรียกว่า *ข้อกำหนด (specification)* หรือเรียกสั้นๆ ว่า spec ซึ่งจะประกอบด้วยรายละเอียดกรณีการใช้งานต่างๆ ร่วมกับเทสต์สำหรับกรณีเหล่านั้น ดังนี้ ```js describe("pow", function() { - it("ยกกำลัง n ได้", function() { + it("ยกกำลัง n", function() { assert.equal(pow(2, 3), 8); }); }); ``` -spec จะมี 3 ส่วนหลักๆ ตามที่เห็นเลย: +spec มีองค์ประกอบหลัก 3 ส่วนตามที่เห็นข้างบน -`describe("ชื่อ", function() { ... })` -: บอกเราว่ากำลังอธิบายอะไร ในกรณีนี้เรากำลังอธิบายฟังก์ชัน `pow` นั่นเอง เหมือนหัวข้อของกลุ่มคนงานที่กำลังจะมาทำงานในบล็อก `it` +`describe("ชื่อหัวข้อ", function() { ... })` +: เรากำลังอธิบายฟังก์ชันอะไร? ในกรณีนี้คือฟังก์ชัน `pow` ใช้เพื่อจัดกลุ่มบล็อก `it` ที่เป็น "ผู้ทำงาน" -`it("คำอธิบายการใช้งาน", function() { ... })` -: ในหัวข้อของ `it` เราจะ *อธิบายด้วยภาษาคน* ว่าฟังก์ชันนี้ควรทำงานอย่างไรในสถานการณ์นั้นๆ และตัว argument ที่สองเป็นฟังก์ชันทดสอบ +`it("คำอธิบายกรณีการใช้งาน", function() { ... })` +: ในชื่อของ `it` จะบรรยาย *ในรูปแบบที่คนอ่านแล้วเข้าใจ* ถึงกรณีการใช้งานเฉพาะ และอาร์กิวเมนต์ตัวที่สองก็จะเป็นฟังก์ชันที่ทดสอบกรณีดังกล่าว -`assert.equal(ค่า1, ค่า2)` -: โค้ดภายในบล็อก `it` ถ้าการทำงานของฟังก์ชันถูกต้อง จะต้องทำงานโดยไม่มี error +`assert.equal(value1, value2)` +: โค้ดภายในบล็อก `it` ถ้าอิมพลีเมนต์ถูกต้อง ควรจะรันได้โดยไม่เกิดข้อผิดพลาด - ฟังก์ชัน `assert.*` ใช้ตรวจสอบว่า `pow` ทำงานตามที่คาดหวังหรือไม่ ในตัวอย่างนี้เราใช้ `assert.equal` เพื่อเปรียบเทียบค่าสองค่า ถ้าไม่เท่ากัน จะมี error แสดงขึ้นมา ที่นี่เราทดสอบว่าผลลัพธ์ของ `pow(2, 3)` เท่ากับ `8` หรือเปล่า นอกจากนี้ยังมีฟังก์ชันอื่นๆ สำหรับการเปรียบเทียบและตรวจสอบ อันนี้ไว้ค่อยมาดูกันทีหลัง + ฟังก์ชัน `assert.*` ถูกใช้เพื่อเช็คว่า `pow` ทำงานได้ตามที่คาดหวังหรือไม่ ตรงนี้เราใช้ตัวหนึ่งในนั้นคือ `assert.equal` มันจะเปรียบเทียบอาร์กิวเมนต์และโยนข้อผิดพลาดถ้าไม่เท่ากัน ซึ่งตรงนี้เช็คว่าผลลัพธ์ของ `pow(2, 3)` เท่ากับ `8` รึเปล่า นอกจากนี้ยังมีชนิดอื่นๆ ของการเปรียบเทียบและตรวจสอบด้วย ซึ่งเราจะมาเพิ่มภายหลัง -spec สามารถเรียกใช้เพื่อทดสอบฟังก์ชันของเราได้ เราจะมาดูวิธีการนั้นในตอนต่อไป! +spec นี้สามารถรันได้ และมันจะไปรันเทสต์ที่อยู่ในบล็อก `it` เราจะมาดูเรื่องนี้กันในภายหลัง ## ขั้นตอนการพัฒนา -ขั้นตอนการพัฒนาโดยทั่วไปมีหน้าตาแบบนี้: +ขั้นตอนการพัฒนามักจะเป็นแบบนี้: -1. เขียน spec เบื้องต้นพร้อมทดสอบสำหรับฟังก์ชันเบสิก -2. สร้างการทำงานเบื้องต้น -3. เพื่อตรวจสอบว่ามันทำงานหรือเปล่า เราจะรันเฟรมเวิร์คทดสอบ Mocha: [http://mochajs.org/](http://mochajs.org/) (เดี๋ยวจะอธิบายเพิ่มเติม) ซึ่งจะรัน spec ตอนนี้ฟังก์ชันยังไม่เสร็จ ก็อาจจะมี error ขึ้นมา เราก็แก้โค้ดไปเรื่อยๆ จนกว่ามันจะเวิร์ค -4. ตอนนี้เรามีการทำงานเบื้องต้นที่ใช้ได้พร้อมกับทดสอบแล้ว -5. เราเพิ่มกรณีการใช้งานเพิ่มเติมลงใน spec ซึ่งอาจจะยังไม่รองรับโดยการทำงานตอนนี้ ทดสอบก็จะล้มเหลวก่อน -6. กลับไปข้อ 3 อัปเดตการทำงานจนกระทั่งทดสอบผ่าน -7. ทำซ้ำข้อ 3-6 จนกว่าฟังก์ชันพร้อมใช้งาน +1. เขียนข้อกำหนด (spec) เบื้องต้น พร้อมกับเทสต์สำหรับฟังก์ชันที่พื้นฐานที่สุด +2. สร้างการอิมพลีเมนต์เบื้องต้น +3. เพื่อตรวจสอบว่ามันใช้ได้ไหม เราจะรันเฟรมเวิร์กการทดสอบ [Mocha](https://mochajs.org/) (จะมีรายละเอียดเพิ่มเติมเร็วๆ นี้) ที่จะรันข้อกำหนด ถ้าฟังก์ชันยังไม่ครบถ้วน ข้อผิดพลาดก็จะถูกแสดง เราจะทยอยแก้ไขจนกว่าทุกอย่างจะทำงาน +4. ตอนนี้เรามีการอิมพลีเมนต์เบื้องต้นที่ใช้ได้จริงพร้อมกับเทสต์แล้ว +5. เราเพิ่มกรณีการใช้งานเข้าไปในข้อกำหนด ซึ่งอาจยังไม่ได้รองรับโดยการอิมพลีเมนต์ เทสต์เริ่มล้มเหลว +6. กลับไปทำข้อ 3 ปรับแก้การอิมพลีเมนต์จนกระทั่งเทสต์ไม่มีข้อผิดพลาด +7. ทำข้อ 3-6 ซ้ำไปเรื่อยๆ จนกระทั่งฟังก์ชันลิตี้เสร็จสมบูรณ์ -ดังนั้นการพัฒนาเป็นแบบ *วนซ้ำ* เราเขียน spec ทำงาน ทดสอบ จนผ่าน แล้วก็เขียนทดสอบเพิ่ม ทำให้มันผ่านอีก จนสุดท้ายเราก็ได้ทั้งการทำงานที่ใช้ได้และทดสอบสำหรับมัน +ดังนั้น การพัฒนาจึงเป็นแบบ *วนซ้ำ (iterative)* เราเขียนข้อกำหนด อิมพลีเมนต์มัน ตรวจสอบให้แน่ใจว่าเทสต์ผ่าน จากนั้นเขียนเทสต์เพิ่ม ตรวจว่าเทสต์ทำงานไหม แล้วก็ทำแบบนี้ต่อไป ท้ายที่สุดเราจะได้ทั้งการอิมพลีเมนต์ที่ใช้งานได้จริง พร้อมกับเทสต์สำหรับมัน -มาดูขั้นตอนการพัฒนานี้ในกรณีตัวอย่างของเรา +มาดูกระบวนการพัฒนานี้ในกรณีของเราเป็นรูปธรรมกันดีกว่า -ขั้นตอนแรกเสร็จไปแล้ว: เรามี spec เบื้องต้นสำหรับ `pow` ตอนนี้ก่อนที่จะสร้างการทำงาน ลองใช้ไลบรารี่ JavaScript สักสองสามตัวเพื่อรันทดสอบดู แค่เพื่อดูว่ามันทำงาน (ก็จะล้มเหลวหมดแหละ) +ตอนนี้เราทำขั้นตอนแรกเสร็จแล้ว คือมีข้อกำหนดเบื้องต้นของ `pow` ซึ่งก่อนจะลงมือเขียนอิมพลีเมนต์ เรามาลองใช้ไลบรารี JavaScript บางตัวเพื่อรันเทสต์ดู แค่เพื่อจะได้เห็นว่าเทสต์เหล่านั้นทำงานอยู่ (ตอนนี้เทสต์ทั้งหมดน่าจะล้มเหลว) -## ทดสอบ spec กัน! +## ข้อกำหนดที่ใช้งานจริง -ในบทช่วยสอนนี้ เราจะใช้ไลบรารี JavaScript สำหรับทดสอบดังต่อไปนี้: +ในบทเรียนนี้ เราจะใช้ไลบรารี JavaScript ต่อไปนี้ในการทดสอบ: -- Mocha: [http://mochajs.org/](http://mochajs.org/) -- เฟรมเวิร์คหลัก: มันให้ฟังก์ชันการทดสอบทั่วไปรวมถึง `describe` และ `it` และฟังก์ชันหลักที่รันทดสอบ -- Chai: [http://chaijs.com](http://chaijs.com) -- ไลบรารีที่มี assertions มากมาย มันช่วยให้ใช้ assertions ที่แตกต่างกันได้มากมาย ตอนนี้เราต้องการแค่ `assert.equal` -- Sinon: [http://sinonjs.org/](http://sinonjs.org/) -- ไลบรารีสำหรับสอดแนมฟังก์ชัน เลียนแบบฟังก์ชันในตัวและอื่นๆ เราจะใช้มันในภายหลัง +- [Mocha](https://mochajs.org/) -- เป็นเฟรมเวิร์กหลัก ให้ฟังก์ชันทดสอบทั่วไปต่างๆ รวมทั้ง `describe` และ `it` ตลอดจนฟังก์ชันหลักที่ใช้รันการทดสอบ +- [Chai](https://www.chaijs.com/) -- ไลบรารีที่มี assertions มากมาย ช่วยให้ใช้ assertions หลากหลายแบบได้ สำหรับตอนนี้เราต้องการแค่ `assert.equal` +- [Sinon](https://sinonjs.org/) -- ไลบรารีที่ใช้เฝ้าดูฟังก์ชัน จำลองการทำงานของฟังก์ชันในตัว และอื่นๆ เราจะต้องใช้มันในภายหลัง -ไลบรารีเหล่านี้เหมาะสำหรับการทดสอบทั้งในเบราว์เซอร์และฝั่งเซิร์ฟเวอร์ ในที่นี้เราจะพิจารณาตัวแปรเบราว์เซอร์ +ไลบรารีเหล่านี้สามารถใช้ทดสอบได้ทั้งฝั่งเบราว์เซอร์และเซิร์ฟเวอร์ แต่ที่นี่เราจะพูดถึงกรณีบนเบราว์เซอร์ -หน้า HTML เต็มรูปแบบพร้อมเฟรมเวิร์คเหล่านี้และ spec ของ `pow`: +หน้า HTML เต็มรูปแบบที่มีเฟรมเวิร์กเหล่านี้พร้อมกับข้อกำหนดของ `pow`: ```html src="index.html" ``` -หน้าแบ่งออกเป็น 5 ส่วน: +หน้านี้ประกอบด้วย 5 ส่วน: -1. `` -- เพิ่มไลบรารีของบุคคลที่สามและสไตล์สำหรับการทดสอบ -2. `