Cross-Functional Testing (Cross-Functional Testing)
เนื้อหา ส่วนใหญ่ของบทนี้เน้นไปที่การทดสอบฟังก์ชันการทำงานเฉพาะเจาะจง และดูว่ามันแตกต่างกันอย่างไรเมื่อคุณกำลังทดสอบระบบที่ใช้ microservice อย่างไรก็ตาม ยังมีการทดสอบอีกหมวดหนึ่งที่สำคัญต้องพูดถึง Nonfunctional requirements เป็นคำเรียกรวมที่ใช้อธิบายคุณลักษณะที่ระบบของคุณแสดงออกมา ซึ่งไม่สามารถ implement ได้ง่ายๆ เหมือนฟีเจอร์ปกติ มันครอบคลุมแง่มุมต่างๆ เช่น latency ที่ยอมรับได้ของหน้าเว็บ จำนวนผู้ใช้ที่ระบบควรรองรับ user interface ของคุณควรเข้าถึงได้สำหรับคนพิการมากแค่ไหน หรือข้อมูลลูกค้าของคุณควรปลอดภัยแค่ไหน
คำว่า nonfunctional ไม่เคยเข้ากับผมได้เลย บางสิ่งที่ถูกครอบคลุมด้วยคำนี้ดูจะมีลักษณะ functional มากๆ! เพื่อนร่วมงานเก่าของผม Sarah Taraporewalla คิดวลี cross-functional requirements (CFR) ขึ้นมาแทน ซึ่งผมชอบมากกว่ามาก มันสื่อได้ดีกว่าถึงความจริงที่ว่าพฤติกรรมของระบบเหล่านี้เกิดขึ้นจริงๆ ก็เป็นผลมาจากงาน cross-cutting จำนวนมากเท่านั้น
CFR หลายตัว ถ้าไม่ใช่ส่วนใหญ่ สามารถบรรลุได้จริงๆ ก็แค่ใน production เท่านั้น ถึงอย่างนั้น เราก็สามารถกำหนดกลยุทธ์การทดสอบเพื่อช่วยให้เราเห็นว่าเรากำลังก้าวไปสู่การบรรลุเป้าหมายเหล่านี้อย่างน้อยที่สุดหรือไม่ เทสต์ประเภทนี้จัดอยู่ใน quadrant ของ Property Testing ตัวอย่างที่ดีของเทสต์ประเภทนี้คือ performance test ซึ่งเราจะพูดถึงเชิงลึกมากขึ้นในไม่ช้า
คุณอาจต้องการติดตาม CFR บางตัวในระดับ microservice แต่ละตัว ตัวอย่างเช่น คุณอาจตัดสินใจว่า durability ของ service ที่คุณต้องการจาก payment service ของคุณสูงกว่าอย่างมีนัยสำคัญ แต่คุณพอใจกับ downtime ที่มากกว่าสำหรับ music recommendation service ของคุณ โดยรู้ว่าธุรกิจหลักของคุณจะอยู่รอดได้ ถ้าคุณไม่สามารถแนะนำศิลปินที่คล้ายกับ Metallica ได้ประมาณ 10 นาที การแลกเปลี่ยนเหล่านี้จะจบลงด้วยการส่งผลกระทบใหญ่ต่อวิธีที่คุณออกแบบและพัฒนาระบบของคุณ และอีกครั้งที่ธรรมชาติที่ละเอียดของระบบแบบ microservice ให้โอกาสคุณมากขึ้นในการทำการแลกเปลี่ยนเหล่านี้ เมื่อมองที่ CFR ที่ microservice หรือทีมใดทีมหนึ่งอาจต้องรับผิดชอบ มันเป็นเรื่องปกติที่พวกมันจะปรากฏขึ้นเป็นส่วนหนึ่งของ service-level objective (SLO) ของทีม ซึ่งเป็นหัวข้อที่เราจะสำรวจต่อไปใน “Are We Doing OK?”
เทสต์ที่เกี่ยวกับ CFR ก็ควรทำตาม pyramid เช่นกัน เทสต์บางตัวจำเป็นต้องเป็น end-to-end อย่าง load test แต่ตัวอื่นไม่จำเป็นต้องเป็น ตัวอย่างเช่น เมื่อคุณพบ performance bottleneck ใน end-to-end load test แล้ว ให้เขียนเทสต์ขอบเขตเล็กกว่าเพื่อช่วยจับปัญหานั้นในอนาคต CFR อื่นๆ เข้ากับเทสต์ที่เร็วกว่าได้ค่อนข้างง่าย ผมจำได้ว่าเคยทำงานในโปรเจกต์หนึ่งที่เรายืนกรานให้แน่ใจว่า HTML markup ของเราใช้ accessibility feature ที่ถูกต้อง เพื่อช่วยให้คนพิการใช้เว็บไซต์ของเราได้ การตรวจสอบ markup ที่สร้างขึ้นเพื่อให้แน่ใจว่ามี control ที่เหมาะสมอยู่นั้นสามารถทำได้อย่างรวดเร็วมาก โดยไม่ต้องมี networking round trip ใดๆ เลย
บ่อยครั้งเกินไปที่การพิจารณา CFR มาช้าเกินไปมาก ผมขอแนะนำอย่างยิ่งให้พิจารณา CFR ของคุณให้เร็วที่สุดเท่าที่จะทำได้ และทบทวนมันอย่างสม่ำเสมอ
Performance Tests (Performance Test)
Performance test ควรถูกเรียกให้ชัดเจน ในฐานะวิธีการยืนยันว่า cross-functional requirement บางตัวของเราสามารถบรรลุได้ เมื่อแตกระบบออกเป็น microservices ขนาดเล็กลง เราก็เพิ่มจำนวนการเรียกที่จะเกิดขึ้นข้ามขอบเขตของเครือข่าย ที่ซึ่งแต่ก่อนการดำเนินการหนึ่งอาจเกี่ยวข้องกับการเรียกฐานข้อมูลครั้งเดียว ตอนนี้อาจเกี่ยวข้องกับการเรียกสามหรือสี่ครั้งข้ามขอบเขตเครือข่ายไปยัง service อื่น พร้อมกับจำนวนการเรียกฐานข้อมูลที่ตรงกัน ทั้งหมดนี้สามารถลดความเร็วในการทำงานของระบบของเราได้ การตามหาแหล่งที่มาของ latency นั้นสำคัญเป็นพิเศษ เมื่อคุณมี call chain ของการเรียกแบบ synchronous หลายครั้ง ถ้าส่วนใดส่วนหนึ่งของ chain เริ่มทำงานช้า ทุกอย่างก็จะได้รับผลกระทบ ซึ่งอาจนำไปสู่ผลกระทบที่มีนัยสำคัญ สิ่งนี้ทำให้การมีวิธีทำ performance test กับ application ของคุณสำคัญยิ่งกว่ากับระบบแบบ monolithic มากขึ้น บ่อยครั้งที่เหตุผลที่การทดสอบประเภทนี้ถูกเลื่อนออกไปคือเพราะในตอนแรกยังไม่มีระบบมากพอที่จะทดสอบ ผมเข้าใจปัญหานี้ แต่บ่อยเกินไปที่มันนำไปสู่การผลักปัญหาออกไปเรื่อยๆ โดย performance testing มักถูกทำแค่ก่อนขึ้น live ครั้งแรกเท่านั้น ถ้าทำเลยด้วยซ้ำ! อย่าตกหลุมพรางนี้
เช่นเดียวกับ functional test คุณอาจต้องการการผสมผสาน คุณอาจตัดสินใจว่าต้องการ performance test ที่แยก service แต่ละตัวออกจากกัน แต่เริ่มต้นด้วยเทสต์ที่ตรวจสอบ เส้นทาง หลักในระบบของคุณ คุณอาจสามารถเอา end-to-end journey test มาแล้วรันมันในปริมาณมากได้เลย
เพื่อให้ได้ผลลัพธ์ที่คุ้มค่า คุณมักจะต้องรัน scenario ที่กำหนดด้วยจำนวนลูกค้าจำลองที่เพิ่มขึ้นทีละน้อย สิ่งนี้ช่วยให้คุณเห็นว่า latency ของการเรียกเปลี่ยนแปลงอย่างไรเมื่อ load เพิ่มขึ้น นี่หมายความว่า performance test อาจใช้เวลานานในการรัน นอกจากนี้ คุณจะต้องการให้ระบบใกล้เคียงกับ production มากที่สุดเท่าที่จะทำได้ เพื่อให้แน่ใจว่าผลลัพธ์ที่คุณเห็นจะบ่งชี้ถึง performance ที่คุณคาดหวังได้บนระบบ production สิ่งนี้อาจหมายความว่าคุณจะต้องได้มาซึ่งปริมาณข้อมูลที่คล้าย production มากขึ้น และอาจต้องใช้เครื่องมากขึ้นเพื่อให้ตรงกับ infrastructure ซึ่งเป็นงานที่อาจท้าทาย แม้ว่าคุณจะพยายามอย่างหนักเพื่อทำให้ performance environment เหมือน production จริงๆ เทสต์เหล่านี้ก็ยังคงมีคุณค่าในการตามหา bottleneck แค่ต้องตระหนักว่าคุณอาจได้ false negative หรือแย่กว่านั้นคือ false positive
เนื่องจากเวลาที่ใช้ในการรัน performance test ทำให้บางครั้งไม่สามารถรันมันได้ในทุก check-in เป็นเรื่องปกติที่จะรันส่วนหนึ่งทุกวัน และชุดที่ใหญ่กว่าทุกสัปดาห์ ไม่ว่าคุณจะเลือกแนวทางไหน ต้องแน่ใจว่าคุณรันเทสต์อย่างสม่ำเสมอที่สุดเท่าที่จะทำได้ ยิ่งคุณไม่รัน performance test นานเท่าไร ก็ยิ่งยากขึ้นที่จะตามหาตัวการ ปัญหาด้าน performance นั้นยากเป็นพิเศษที่จะแก้ ดังนั้นถ้าคุณสามารถลดจำนวน commit ที่คุณต้องดูเพื่อหาปัญหาที่เพิ่งเกิดขึ้นได้ ชีวิตของคุณก็จะง่ายขึ้นมาก
และต้องแน่ใจว่าคุณดูผลลัพธ์ด้วย! ผมประหลาดใจมากกับจำนวนทีมที่ผมเคยเจอ ที่ทุ่มเทงานมากมายในการ implement เทสต์และ รัน มัน แต่ไม่เคยตรวจสอบตัวเลขจริงๆ เลย บ่อยครั้งเป็นเพราะคนไม่รู้ว่าผลลัพธ์ที่ "ดี" หน้าตาเป็นอย่างไร คุณจำเป็นต้องมีเป้าหมายจริงๆ เมื่อคุณจัดหา microservice เพื่อใช้เป็นส่วนหนึ่งของสถาปัตยกรรมที่กว้างขึ้น เป็นเรื่องปกติที่จะมีความคาดหวังเฉพาะเจาะจงที่คุณให้คำมั่นว่าจะส่งมอบ นั่นคือ SLO ที่ผมพูดถึงก่อนหน้านี้ ถ้าเป็นส่วนหนึ่งของสิ่งนี้ที่คุณให้คำมั่นว่าจะส่งมอบ performance ในระดับหนึ่ง ก็สมเหตุสมผลที่ automated test ใดๆ จะให้ feedback แก่คุณว่าคุณมีแนวโน้มที่จะบรรลุ (และหวังว่าจะเกินกว่า) เป้าหมายนั้นหรือไม่
แม้ไม่มีเป้าหมาย performance เฉพาะเจาะจง automated performance test ก็ยังคงมีประโยชน์มากในการช่วยให้คุณเห็นว่า performance ของ microservice ของคุณเปลี่ยนแปลงอย่างไรเมื่อคุณทำการเปลี่ยนแปลง มันสามารถเป็น safety net ที่คอยจับคุณไว้ ถ้าคุณทำการเปลี่ยนแปลงที่ทำให้ performance แย่ลงอย่างมาก ดังนั้นทางเลือกอื่นสำหรับเป้าหมายเฉพาะเจาะจงอาจเป็นการทำให้เทสต์ล้มเหลว ถ้า delta ของ performance จาก build หนึ่งไปอีก build เปลี่ยนแปลงมากเกินไป
performance testing จำเป็นต้องทำควบคู่ไปกับความเข้าใจ performance ของระบบจริง (ซึ่งเราจะพูดถึงเพิ่มเติมใน Chapter 10 ) และโดยหลักการแล้วคุณควรใช้เครื่องมือเดียวกันใน performance test environment ของคุณ สำหรับการแสดงภาพพฤติกรรมของระบบ เหมือนกับที่คุณใช้ใน production แนวทางนี้สามารถทำให้การเปรียบเทียบแบบเทียบเท่ากันง่ายขึ้นมาก
Robustness Tests (Robustness Test)
สถาปัตยกรรมแบบ microservice มักมีความน่าเชื่อถือเท่ากับจุดที่อ่อนแอที่สุดของมันเท่านั้น และผลก็คือเป็นเรื่องปกติที่ microservices ของเราจะสร้างกลไกเข้าไปเพื่อให้พวกมันปรับปรุงความทนทานได้ เพื่อปรับปรุงความน่าเชื่อถือของระบบ เราจะสำรวจหัวข้อนี้เพิ่มเติมใน “Stability Patterns” แต่ตัวอย่างรวมถึงการรัน microservice หลาย instance อยู่หลัง load balancer เพื่อทนทานต่อความล้มเหลวของ instance หนึ่ง หรือการใช้ circuit breaker เพื่อจัดการกับสถานการณ์ที่ downstream microservices ไม่สามารถติดต่อได้ในเชิงโปรแกรม
ในสถานการณ์แบบนี้ การมีเทสต์ที่ช่วยให้คุณจำลองความล้มเหลวบางอย่างขึ้นมาใหม่ เพื่อให้แน่ใจว่า microservice ของคุณยังคงทำงานได้โดยรวมนั้นมีประโยชน์ โดยธรรมชาติแล้วเทสต์เหล่านี้อาจ implement ได้ยุ่งยากกว่าเล็กน้อย ตัวอย่างเช่น คุณอาจต้องสร้าง network time-out ปลอมขึ้นมาระหว่าง microservice ที่กำลังถูกทดสอบกับ external stub ถึงอย่างนั้น มันก็คุ้มค่า โดยเฉพาะอย่างยิ่งถ้าคุณกำลังสร้างฟังก์ชันการทำงานที่ใช้ร่วมกันซึ่งจะถูกใช้ข้าม microservices หลายตัว ตัวอย่างเช่น การใช้ default service mesh implementation เพื่อจัดการ circuit breaking