จัดการ test ที่ต้อง assert หลายครั้งยังไงดี?

Kemakorn Sudchukiat
2 min readFeb 10, 2020

--

TL;DR

Problem — ถ้า assert หลายครั้งในข้อเดียว อาจจะมีบางส่วนของผลลัพธ์ที่ไม่ถูกทดสอบ

Solution (ในที่นี้จะพูดถึง Jest)

  1. เขียน test ให้ assert เป็นอิสระต่อกัน โดยการแยกข้อ, ในส่วนของการ ทำ action ต่างๆเพื่อเตรียมผล ให้ย้ายไปอยู่ใน before
  2. ใช้ method toMatchObject ( https://jestjs.io/docs/en/expect#tomatchobjectobject)
  3. ใช้ method toMatchSchema ( https://github.com/americanexpress/jest-json-schema)
  4. เขียน extension ให้ jest โดย ให้สามารถ รับ array ของ assertion และทำการ เก็บผล assert ทั้งหมดก่อนจะ สรุป passed/failed ทีเดียว

ในการเขียน automation test นั้น สิ่งที่เราทุกคนควรระลึกถึงอยู่เสมอๆคือ objective ของ case นั้นต้อง clear ฉนั้นโดยปกติแล้ว เรามักจะเขียน test ให้ 1 ข้อนั้น มีการ assert เพียงแค่ครั้งเดียว

แต่ในความเป็นจริงนั้น บางครั้งเราก็มักจะมีการ assert ที่มากกว่า 1 เนื่องจากผลจาก action นั้นๆ มันมีหลายอย่างที่ต้องตรวจสอบ เราจึงเขียน assert หลายครั้งใน test case ข้อเดียว ซึ่งผลที่ตามมาก็คือ หากมี assert ใดๆ failed ระหว่างทาง assert ตัวที่เหลือ (ซึ่งอาจจะ ดีหรือร้าย) จะไม่ถูกทดสอบ ยกตัวอย่างเช่น หากเราทำ api test ซึ่งผลจาก response นั้นเป็น object ที่ มีหลาย field แต่เราต้องการทดสอบแค่บาง field เป็นต้น

และจากการที่เรา assert ผลของ action นั้นๆไม่ครบ ก็จะเกิดการส่งไปส่งมาระหว่าง dev-qa ได้ เพื่อลดปัญหา ping-pong นี้ automation test ของเราจึงควรมีความสามารถในการ test ให้ครบ หรือ มีความ independent กันระหว่าง assert ซึ่งในที่นี้ผมจะยกตัวอย่างโดยใช้ Jest

สมมุติ response ที่ได้จาก api เป็นดังนี้

เขียน test ให้การ assertion เป็น อิสระต่อกัน

การเขียน ให้ assertion เป็น อิสระต่อกันก็คือ ใน 1 test ก็จะมี 1 assert และ ยก action ไปไว้ใน beforeAll

ซึ่งวิธีนี้นั้นเป็นวิธีที่ test โดยทั่วไปนั้นควรจะทำแต่ก็มีข้อเสียตรงที่ต้องเขียนค่อนข้างเยอะ (แต่ก็ดีคิดมันจะทำให้ description ของแต่ละ assert ชัดเจนมาก) ซึ่งถ้าเลือกใช้วิธีนี้ก็มักจะได้ test spec ที่ยาวมากๆ และอาจจะมีหลายชั้นของ `describe` มากๆ

เขียน test ด้วย toMatchObject

ใน jest เองนั้นก็มี assertion ที่ช่วยเรา ในการ test object อยู่บ้าง เช่น toMatchObject ซึ่งการใช้วิธีนี้ก็จะทำให้เรา assert หลายๆ field ใน object ได้พร้อมๆกัน

วิธีนี้นั้น มีข้อดีตรงที่เห็นภาพของ สิ่งที่เราจะ assert ชัดเจน และ เขียนไม่ยาวนัก แต่มีข้อเสียที่ log ตอน failed นั้นอ่านค่อนข้างยาก (ไม่มีตัวอย่างนะไปลองเอง) เพราะบางครั้งมันก็ ขึ้นบาง field ที่ไม่ได้ผิดมาด้วย

ข้อเสียอีกข้อคือ มี matcherให้ใช้น้อยกว่า เช่นจะทดสอบว่า field นี้มีค่ามากกว่า 3 ก็คือต้องไปเขียน extend มาเพิ่มเองนะ toBeGreaterThan ไม่อยู่ใน expect อีกอย่างคือ handle พวก array object ยาก อย่างเช่นในตัวอย่าง ถ้าเราอยาก test response.data.records[i].a งี้ยากเลยจร้าาา

เขียน test ด้วย toMatchSchema

วิธีนี้นั้นจะเหมอะกับการทดสอบ โครงสร้างมากกว่า value เช่น ถ้าเราต้องการทดสอบว่า status นั้นเป็น integer แต่เราไม่ได้ต้องการ ทดสอบว่า status เป็น 200 แต่จริงๆแล้วก็ให้ ทดสอบ value ได้เช่นกันหละ

วิธีนี้เราจะต้องมีความเข้าใจ json-schema ด้วยนะครับ สามารถทำ validation ได้หลากหลาย แต่ในลักษณะ ของการทดสอบ โครงสร้างนะ ถ้าเอาทดสอบ match value มันอาจจะฝืนๆหน่อย

เขียน test ให้สามารถทำ assertion ทั้งหมดได้ก่อน แล้วจึง รายงานผลทีเดียว

วิธีนี้ก็คือ ใช้การเขียน extend matcher เอาเลย เช่น

วิธีนี้จะช่วยให้เราทำการทดสอบสิ่งที่เราต้องการได้เหมือนเขียน assertion แยกกัน แต่สิ่งที่ toPassedAllAssert ทำนั้นจะทำการเก็บผลละรายงานทีเดียว ทำให้เราสามารถทำการ assert ผลลัพธ์ได้หลากหลาย

วิธีนี้นั้นก็จะมีข้อด้อยตรงที่ log ของผล assert อาจจะอ่านยากซักหน่อยแต่ก็ จะช่วยให้เราเขียน test ได้ กระชับมากขึ้น และก็อาจจะทำให้เรานิสัยเสียหน่อยๆในการเขียน test เพิ่มขึ้น เช่น action a ทำให้ ผลลัพธ์ของ get api b และ c เปลี่ยน ถ้าเราขี้เกียจ เราก็จะเขียนสิ่งเหล่านี้อยู่ใน test ข้อเดียว ซึ่งไม่ใช่ลักษณะนิสัยที่ควรเท่าไหร่

จากตัวอย่าง และวิธีการขั้นต้น จะเห็นได้ว่า แต่ละวิธีนั้นมีการใช้งาน ข้อดี ข้อเสียต่างกันไป เราจึงต้องเลือกใช้งานอยา่งเหมอะสม ส่วนตัวของผู้เขียนเองมักใช้ทุกวิธีขึ้นอยู่กับสิ่งที่เราต้องการทดสอบครับ โดยจะสรุปไว้ (จากที่สังเกตเอาเอง) ดังนี้

  1. แยก assertion: ข้อดีทดสอบได้ครบ และหลากหลายตามที่ต้องการ ข้อเสียเขียนยาว เยอะ
  2. toMatchObject: ข้อดีเห็น expected result ชัดเจน ข้อเสีย log result ยังเข้าใจ้ได้ยาก มีข้อจำกัดในการทดสอบ และ ข้อจำกัดเกี่ยวกับ array
  3. toMatchSchema: ข้อดี ใช้ทดสอบโครงสร้าง และสามารถรับมือกับ array ได้ดี ข้อเสีย ไม่เหมอะกับการทดสอบ value
  4. เขียน extend: ข้อดีสามารถทดสอบ สิ่งต่างๆได้หลากหลาย เหมือนแยก assert ข้อเสีย อาจจะทำให้ติดกับการเขียน test ที่ ยุ่งเยิงเกินไป ยาวเกินไป

นอกจากการเขียน extend ใน jest แล้วเราอาจใช้ lib soft-assert ก็ได้เช่นกัน ( https://www.npmjs.com/package/soft-assert)

ใน java ก็มีสิ่งที่คล้ายๆกัน เรียกว่า soft assertion ของ testng ( https://www.seleniumeasy.com/testng-tutorials/soft-asserts-in-testng-example)

สนุกสนานกับการเขียนเทสครับ :)

--

--