Kitphon Poonyapalang
odds.team
Published in
4 min readAug 29, 2018

--

Angular の Unit Testing — Part 1 มาลองเขียนเทสกัน

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

เขียนเทสแล้วดียังไง ?

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

สิ่งที่เราต้องเตรียมก่อนเริ่มทำ

  • ติดตั้ง Angular
  • ติดตั้ง Angular Cli
  • ติดตั้ง Editor สำหรับการเขียนโค้ด

(คาดว่าทุกคนน่าจะทำได้อยู่แล้ว เพราะฉะนั้นจะไม่เขียนถึง)

เริ่มต้นด้วย พิมพ์คำสั่ง ng new test-ang เพื่อให้ Angular Cli สร้างโปรเจ็คชื่อ test-ang ขึ้นมา

ng new test-ang

เสร็จแล้วเราจะได้หน้าตาแบบข้างล่างนี้

โปรเจ็คที่เราสร้างขึ้นมาจะอยู่ในโฟลเดอร์ test-ang เนาะ เพราะฉะนั้นให้เรา พิมพ์คำสั่ง cd test-ang เพื่อเข้าไปในโฟลเดอร์นี้ เมื่อเราพิมพ์คำสั่ง ls เพื่อลิสต์รายการไฟล์ดู จะเห็นว่ามีไฟล์และโฟลเดอร์ที่มาจากการสร้างโปรเจ็คแล้ว

คราวนี้เรามาลองสร้าง component เพื่อจะมาลองเขียนยูนิตเทสกัน โดยพิมพ์คำสั่ง

ng g c components/for-test 

ก็คือสร้าง component ขึ้นมาใหม่ชื่อ for-test อยู่ภายใต้โฟลเดอร์ components

หลังจาก generate component มาแล้วจะมีไฟล์ถูกสร้างมาเพิ่มในโฟลเดอร์ชื่อ for-test อยู่ 4 ไฟล์นั่นคือ

  • for-test.component.css (ไฟล์ css ของ component)
  • for-test.component.html (ไฟล์ html ของ component)
  • for-test.component.spec.ts (ไฟล์ test ของ component)
  • for-test.component.ts (ไฟล์ component หลัก)

เนื่องจากบทความนี้เราจะเน้นที่การเขียน unit test ซึ่งจริงๆควรจะทำ TDD แต่อันนี้อยากจะโฟกัสที่การ Mockup พวกค่าต่างๆ สำหรับการทำเทสมากกว่า เพราะฉะนั้น จะมาเขียนโค้ดก่อน แล้วค่อยไปดูวิธีการ Mockup สำหรับการเทสทีหลัง

เรามาลองเปิดไฟล์ for-test.component.ts เพื่อที่จะมาเขียนโค้ดกัน (บทความนี้ผมจะใช้ Visual Studio Code ในการเขียนโค้ดนะ)

อันนี้จะเขียนฟังก์ชั่นชื่อ increaseValue เพิ่มเข้ามาโดยรับตัวพารามิเตอร์ 2 ตัวคือ val กับ inc ที่เป็นตัวแปรชนิด number และมีการ return ค่าจากฟังก์ชั่นนี้เป็น number เช่นกัน

ngOnInit() {     this.increaseValue(10, 1);}increaseValue(val: number, inc: number): number {     return val + inc;}

โดยจะเพิ่มโค้ดเข้าไปหน้าตาประมาณนี้ครับ

เสร็จแล้ว คราวนี้เราไปลองเขียนเทสกันดูดีกว่า คราวนี้ให้เราเปิดไฟล์ for-test.component.spec.ts (ไฟล์ที่เราจะเขียนเทส) เราจะเห็นว่าไฟล์เราจะมีหน้าตาประมาณข้างล่างนี้ ซึ่งเป็นสิ่งที่ Angular cli มัน generate มาให้เราอยู่แล้ว โดยอย่างที่บอกไว้ว่า default framework สำหรับการเขียนเทสที่ Angular เนรมิตมาให้เราแล้วนั่นก็คือ Jasmine

สำหรับคนที่ไม่เคยเขียนมาก่อนเลยก็จะงงๆ ว่าไอที่มันหน้าตาเหมือนฟังก์ชั่นพวกนี้คืออะไรนะ มีทั้ง describe, beforeEach, beforeEach async, it แค่เห็นก็ท้อแล้ว

เอางี้ เรามาไล่กันไปทีละส่วนเลย

ตรงแรกเลย เป็นส่วนของ describe อารมณ์เหมือนกับการแบ่งว่าเรากำลังจะเทสใน Scenario ไหน สมมุติว่าใน component นึงเนี่ย ประกอบไปด้วยการทำงานหลายๆส่วน อาจจะมีทั้งส่วนการบันทึกข้อมูล การโหลดข้อมูลมาแสดงผล การทำ Validation เราก็อาจจะแบ่ง describe เป็นไปตามนี้ เช่น
- describe(‘ForTestComponent Part Of Initial Data’)
- describe(‘ForTestComponent Part Of Save Data’)

ต่อไปขอข้ามมาอธิบายตรง it(‘should create’) ก่อนครับ

it('should create', () => {    expect(component).toBeTruthy();});

ตรงส่วนน้ีจะเป็นเทสเคส โดยตรง ‘should create’ นี่จะเป็นเหมือนชื่อเทสเคสนั้นๆ โดยสมมุติถ้าเราอยากจะสร้างเทสเคสขึ้นมาสักอัน แล้วเราอยากจะเช็คว่าฟังก์ชั่นที่เราไปเทสนั้นควรจะรีเทิร์นค่าออกมาเป็น “Hello World” เราอาจจะเขียนเทสได้ว่า

it('should return Hello World', () => {     expect(component.myFunction()).toBe('Hello World');});

โดยตรงส่วนเทสเคสนี้ เราสามารถสร้างได้หลายๆเทสเคส ประมาณนี้

it('should create', () => {     expect(component).toBeTruthy();});it('should return Hello World', () => {     expect(component.sayHi()).toBe('Hello World');});it('should return Hello Girl', () => {     expect(component.sayHi()).toBe('Hello Girl');});

ต่อไปจะมาที่ beforeEach ฟังก์ชั่นตรงส่วนนี้จะถูกทำงานทุกๆครั้งก่อนที่ เทสเคสแต่ละเคส จะถูกเรียกทำงาน โดยเราจะเห็นว่ามี beforeEach ทั้งแบบ async() และแบบธรรมดา

ลองดูตัวอย่างได้ตามนี้ สมมุติเรามี 3 เทสเคส มันจะมีการทำงานตามลำดับดังนี้- beforeEach()- it(‘should create’)- beforeEach()- it('should return Hello World')- beforeEach()- it('should return Hello Girl')

ตัว beforeEach async() เนี่ย จะเป็นการทำงานแบบ Asynchronous โดยตัวฟังก์ชั่นนี้จะถูกเรียกทำงานจนเสร็จก่อนจะไปเรียก beforeEach แบบธรรมดาให้ทำงานถัดไป

beforeEach(async(() => {     TestBed.configureTestingModule({           declarations: [ ForTestComponent ]     })     .compileComponents();}));

เราจะเห็นว่าใน beforeEach แบบ async จะมีการโค้ดส่วนของการ Setup คือ TestBed ConfigureTestingModule ซึ่งไอเจ้า TestBed เนี่ยมันเป็นเหมือน tool สำหรับการทำ unit testing ที่ Angular เนรมิตมาให้เราเช่นกัน เดี๋ยวเราจะกลับมาพูดถึงกันอีกที ว่าตรงส่วนนี้มีความสำคัญอย่างไร

คราวนี้ฟังก์ชั่น beforeEach ก็จะทำงานต่อจาก

beforeEach(() => {     fixture = TestBed.createComponent(ForTestComponent);     component = fixture.componentInstance;     fixture.detectChanges();});

โดยตรงส่วนนี้จะเป็นการจำลอง component ของเราขึ้นมาจาก TestBed ด้วยคำสั่ง TestBed.createComponent(ForTestComponent)

เมื่อเรานำตัวแปรชื่อ component ที่ได้จาก fixture.componentInstance ไปใช้งาน จะเหมือนกับว่าเราสามารถเรียกใช้ ตัวแปรและฟังก์ชั่นที่อยู่ใน ForTestComponent ได้เลย อารมณ์น่าจะคล้ายๆ การสร้าง Instance มาจากคลาสนั่นเอง

ส่วน fixture.detectChanges() จะเหมือนการ trigger ให้ component ทำงาน โดยเมื่อเจอบรรทัดนี้ ตัวเทสจะไปจัดเตรียมการทำงานของ component และฟังก์ชั่น ngOnInit ของ component นั้นจะถูกทำงานตามทันที

เอาล่ะ คราวนี้มาลองเขียนเทสแบบจริงๆกัน โดยเราจะเขียนเทสเคสเพิ่มขึ้นมาต่อจาก เทสเคสตัวนี้ it(‘should create’)

ตั้งชื่ออธิบายเทสเคสนี้ไปว่า 'should increase value from 10 to 25' ก็คือ เราคาดหวังว่าเมื่อเทสเคสนี้ถูกรันจะมีการเพิ่มค่าจาก 10 เป็น 15 และสิ่งที่เราคาดหวังจากการทำงานข้างในเทสเคสนั้นจะต้องถูกต้องทั้งหมด

โดยฟังก์ชั่นที่เราจะใช้หลักของเทสเคสนี้เลยคือ expect().toBe() จากตัวอย่างโค้ดข้างบน expect(component.increaseValue(10, 15)).toBe(25) หมายความว่า สิ่งที่เรา expect จากการเรียกใช้ฟังก์ชั่น increaseValue ใน component นี้ โดยการใส่พารามิเตอร์ 10 กับ 15 เข้าไป จะได้ผลลัพธ์​เป็น 25 ซึ่งถ้าหากฟังก์ชั่น increaseValue ทำงานถูกต้องตามที่คาดไว้ผลลัพธ์จะต้องได้ 25 แน่นอน

เอาล่ะ คราวนี้เรามาเริ่มรันเทสกันเลย โดยเข้าไปที่โฟลเดอร์ของโปรเจ็คที่เราสร้าง /Users/kitphon/Documents/test-angular/test-ng แล้วพิมพ์คำสั่ง ng test

พอพิมพ์ ng test เสร็จแล้วเราจะเห็นหน้าตาประมาณนี้ มันจะมีรายละเอียดต่างๆโชว์ขึ้นมา และผลการรันเทสตามที่กรอบสีแดงครอบไว้

นั่นหมายความ เรามีเทสเคสทั้งหมด 5 เทสเคสในโปรเจ็ค ซึ่งผลการรันนั้น SUCCESS ทุกเทสเคสเลย (จริงๆที่เราเห็นในไฟล์ for-test.component.spec.ts จะมี 2 เทสเคส แต่จะมีอีก 3 เทสเคสที่เราไม่ได้เห็นอยู่ที่ไฟล์อื่นๆในโปรเจ็คอีก)

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

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

ใครรันเทสแล้ว FAILED ไปอ่านต่อในบทความข้างล่างนะครับ

--

--