feature-image-microservices-testing
feature-image-microservices-testing

จริงๆแล้ว ผมเชื่อว่าทุกคนน่ะ ทํางานอยู่บน สถาปัตยกรรม Microservices มานานละ แต่ไม่รู้ชื่อเรียกมันมากกว่า จากการที่สังเกตุจากหลายๆบริษัทที่ทํางานมา เพราะเจ้าสถาปัตยกรรมนี้มันก็คือแค่ แยกแต่ละ services ออกจากกันโดยชัดเจน ก็แค่นั้น 🙂 ถ้าใครทันสมัยก่อน เราจะนิยม Deploy ด้วย ear file ของ JavaEE ซึ่งก้อนนึงนี้ใหญ่กว่าอุกกาบาตที่วิ่งชนโลกสมัยก่อนซะอีก เพราะฉะนั้นสถาปัตยกรรมใหม่ คือแยกแต่ละส่วนให้ชัดเจน เพื่อที่จะได้ง่ายต่อการ develop และ delopy

monolith-vs-microservices-architecture
monolith-vs-microservices-architecture (https://www.techtalkthai.com/introduction-to-microservices-architecture/)

อย่างตัวอย่างข้างบนคือ ความแตกต่างระหว่างระบบเก่าและระบบใหม่ โดยการแยกแต่ละ Module ออกให้ชัดเจน ซึ่งแต่ละ Module ก็จะแยก tier ของตัวเองออกไปอีกแบบข้างล่างนี้

three-tier-architecture-web
three-tier-architecture-web (https://www.techtalkthai.com/introduction-to-microservices-architecture/)

ที่แยกออกไปอย่างนี้เพราะ เราแบ่งตาม Business Requirement นั้นแหละ เช่น เราแยก Payment Services ออกมาเพื่อใช้ Data tier เป็น NoSQL เพื่อรองรับ transaction จํานวนมหาศาล และไม่เป็นรูปแบบ แต่ถ้า Account Balance Service เรามีสัญญากับ OracleDB ในการ Maintain ต่อไปเลยต้องใช้คนละตัว แบบนี้ ซึ่งนอกจาก Business Requirement ก็เรื่องของ Performance และอื่นๆอีกมากมาย โดยสรุปปกติแล้วก็จะแบ่ง 3 tier ยังงี้แหละ

โดนหลักๆของ Objective

  • สามารถ Build and Deploy ได้ด้วยตัวเอง ไม่ไปยึดติดใคร
  • สามารถ Integrate กับส่วนอื่นผ่าน Interface ที่กําหนด โดยส่วนใหญ่จะใช้ REST นั้นแหละ
  • ทํางานบน process ของตัวเอง ไม่ยึดติดกับ server หรือ VM ของใคร
  • เก็บข้อมูลแยกกันเด็ดขาด ไม่เอาทุก Microservices มารวมกันที่ Database กลาง

โดยเราสามารถดูภาพรวมของ Module ต่างๆของ Microservices ได้ดังนี้

overview-microservices-module
overview-microservices-module (https://martinfowler.com/articles/microservice-testing/#anatomy-modules)

จากรูปจะเห็นได้ว่ามันแยกออกเป็นส่วนๆดังนี้ซึ่งแปลว่าเวลาเราออกแบบ testing strategy เราควรจะคิดถึงแต่ละจุดไว้ แต่ละ Layer ไว้ ซึ่งหลักๆของ Microservices คือ (Microservices ถึงจะแยกกันก็จริง แต่ไม่ได้จําเป็นต้องมี user interface ให้น่ะ บางที Micro ของเราก็เป็น services ลอยๆ รอเรียกก็ได้)

  1. Resource Layer ทําหน้าที่เป็นตัว Mapper ระหว่าน application protocol กับ services ต่างๆของตัว Microservices นั้นเอง ซึ่งปกติมันจะไม่มีอะไรเลย ทําหน้าที่แค่ Mapๆๆ แล้วส่งต่อไปที่ Domain model
  2. Domain Model คือส่วนของ service layer, domain, repositories เจ้าพวกนี้คือรวม service logic ไว้ข้างในทั้งหมด เช่น Logic ในการคํานวณอัตราผ่อนบ้านในระยะเวลา 30 ปีของลูกค้าอสังหา เป็นต้น
  3. Gateway ถ้า services ของเราจะไปเรียก external services มันก็จะถูกส่งผ่านตัว Gateways,Http client ที่เป็นตัว encapsulatest message และส่งต่อไปที่อื่นๆ เช่น ระบบจัดการ ลูกค้าอสังหา แต่ต้องการ calculate อัตรากู้ยืม มันก็จะใช้ส่วนนี้ยิงไปที่ external services และส่งผลกลับมาให้เรา เป็นต้น
  4. Data Mappers โดยเคสปกติทั่วๆไปแล้ว microservices จะทําการ persist object จาก domain model ในระหว่าง request เช่น เราเก็บข้อมูลของลูกค้านั้นแหละ แล้วก็อาจจะเก็บลง Datastore เป็นต้น

ที่พูดถึงเจ้าพวกนี้ ไม่ใช่อะไรหรอก …. เพราะพวกนี้คือสิ่งที่เราต้องคิดถึงเวลาในการทํา automate testing นั้นเอง เวลาเรา design อะ เราต้องคิดถึงจัดต่างๆของ 4 ตัวบนนี้ เพราะว่ามันคือส่วนนึงของการทําให้มั่นใจว่า Application สามารถทํางานได้ถูกต้องนั้นเอง ไม่ใช่แค่เราทําเทสผ่าน User Interface คลิ๊กๆแล้ว Functional ผ่าน เราก็โอเค การทําแบบนั้นเป็น Black-box testing คือ เราไม่รู้อะไรเลย เราแค่กดตามหน้าจอไปเรื่อยๆๆ

microservices-work-together
microservices-work-together (https://martinfowler.com/articles/microservice-testing/#architecture)

ทีนี้อย่างที่บอกว่าหลักการณ์ของ Microservices คือ การแยก servicse เล็กๆๆเนอะ แล้วติดต่อกับ microservices อื่นๆด้วย Interface โดยส่วนมากจะเป็น REST อะ นี้ก็เลยเป็นตัวอย่างของ servicse ติดต่อกันนั้นเอง ผ่าน REST โดยใช้ JSON ที่เป็นตัว Popular สุดละ 🙂

แต่ก็อย่างที่รู้น่ะการติดต่อกันผ่าน HTTP พวก JSON หรืออะไรแบบนี้มันต้องมีการระวัง failure ของพวก network ด้วย เช่น timeouts uptime ของแต่ละตัวในการออกแบบ automate test เป็นต้น

ทีน้ก็มาดูกันว่าการจัดการเทสควรทําส่วนไหนบ้าง

โดยหลักๆการทํา test ที่จะพูดถึงจะพูดถึง 5 ตัวนี้

  1. Unit
  2. Integration
  3. Component
  4. Contract
  5. End-to-end

Unit Testing

เจ้า unit testing นี้คือตัวหลักเลยในการการเขียนเทส ที่ทุกคนควรจะเขียนกัน โดยมีคํานิยามว่า

A unit test exercises the smallest piece of testable software in the application to determine whether it behaves as expected.

จริงๆการเขียน unit testing นี้ดีมากน่ะ มันแสดง behave ของ method ออกมาในบางทีเราสามารถใช้เป็น document ได้เลยด้วยซํ้าอย่าง appium เนี้ย document มันยืดยาว และ อธิบายไม่ละเอียด แต่พอไปอ่าน Unit testing ของมันแล้วเข้าใจกว่าเยอะเลย เช่น

appium-install-app-doc
appium-install-app-doc
appium-python-client-install-app-unit-testing
appium-python-client-install-app-unit-testing

เห็นมั้ย ความแตกต่างชัดเจน Unit testing เป็น document ได้ดีเลย ซึ่ง unit testing แบ่งออกมาเป็นสองแบบคือ Sociable หรือ Solitary test

unit-testing-type
unit-testing-type (https://martinfowler.com/bliki/UnitTest.html)

แบ่งกันโดยว่าจะทําเทสโดยการให้มันรันจริง ทํางานจริงเลยมั้ยเราเรียก sociable tests แต่ถ้าทํางานโดยการ replace object depencies อื่นๆ เราเรียก solitary test (ใช้พวก testdoubles)

แต่จริงๆแล้วในความเห็นหลายๆคนเรา เราไม่ควรใช้ Sociable tests เพราะการใช้ Unit test แบบนั้นมันก็คือไม่ใช่ unit เดียวแล้ว มันจะไปเกี่ยวข้องกับ path อื่นๆ เพราะฉะนั้นเราควรใช้แค่ Soliatry tests มากกว่าเช่น เขียน Mock Stub ก็ว่าไป 🙂

เหตุผลที่พูดถึงเรื่องของประเภท Unit test เพราะว่าในเรื่องของ Microservice เค้าได้แยกรายละเอียดไว้ว่าส่วนไหนควรใช้อะไร

microservices-unit-testing
microservices-unit-testing (https://martinfowler.com/articles/microservice-testing/#testing-unit-diagram)

เค้าจะแยกไว้ว่าพวกที่ไปติดต่อกับ เช่น Service Layer,Gateway, etc. ให้ใช้ Unit Solitary หรือพวก testdoubles เพื่อตัดไม่ให้ยุ่งส่วนอื่น แต่ถ้า Domain หรือตัว Logic จริงๆให้มันคํานวณจริงๆ

แต่ Unit testing อย่างเดียวไม่ได้ช่วยยืนยันเรื่องของ system behaviour น่ะ เราเลยต้องมาดูเรื่องของ integration ต่อ

Integration Testing

คํานิยามมัน

An integration test verifies the communication paths and interactions between components to detect interface defects

integration testing มองได้สองแบบคือ test กับ submodule หรือ กับ external services ตามรูปข้างล่างจะเข้าใจง่ายกว่า

submodule-integration-testing-diagram
submodule-integration-testing-diagram

แบบรวบรวม sub module แล้ว test

external-integration-testing
external-integration-testing

แบบทดสอบกับ external module

ซึ่งเจ้าตัวนี้อย่างที่บอกว่าเราต้องทดสอบ 2 แบบเพราะฉะนั้นหลักๆเลยที่จะทดสอบก็จะไม่พ้นส่วนของ Data Mappers และ Gateways นั้นเอง เลยจะออกมาเป็นแบบรูปข้างล่าง

microservices-unit-testing-and-integration-testing
microservices-unit-testing-and-integration-testing

หลังจากมองเรื่องของ Integration testing ไปแล้ว สิ่งที่มองต่อมาก็คือเรื่องของ component testing

Component Testing

มันคือการเทส service ของเรา หรือว่าง่ายๆก็คือการเทส component ของเราโดยตัด dependencies ออกทั้งหมด ให้มองแค่ component ของเราเท่านั้น มองแค่ SUT เลย

A component test limits the scope of the exercised software to a portion of the system under test, manipulating the system through internal code interfaces and using test doubles to isolate the code under test from other components

ถามว่าทํายังไง? ให้เทสเฉพาะ component เราได้ ง่ายๆก็แค่ใช้ TestDoubles เพื่อ isolate code under test จาก other component นั้นเอง

microservices-component-testing
microservices-component-testing (https://martinfowler.com/articles/microservice-testing/#testing-component-in-process-diagram)

จะเห็นว่า boundary ของการเทส component จะครอบคลุมเฉพาะ component ตัวเอง ไม่ได้ไปแต่ external แม้แต่นิดเดียว นี้แหละคือการเทส component

ซึ่งตัว Tests ของเราที่เขียนขึ้นมาจะติดต่อกับ Microservices ด้วย internal interface เท่านั้น โดยให้ resources เป็นตัว dispatched ไปมา แล้วส่วนอื่นๆใช้ testdoubles จัดการคุมเอา ส่วนถ้าเป็น Datastore ให้เราเใช้ in-memory datastore ในการจําลองเอา

ถ้าสั่งเกตุดีๆใน Diagram เราจะเห็นคําว่า internal resources อยู่ เจ้าตัวนี้ไม่มีไรมาก พูดง่ายๆก็คือมันคือ resources เพื่อใช้ในการเทสแหละ เช่นช่วยให้เราสามารถทํา health check, logs, databased command เพื่อให้เช็คได้แบบนั้น ซึ่งเจ้านี้มัน expose ข้อมูลเรา เพราะฉะนั้นต้องมี Authentication อย่างดีด้วย Network และอื่นๆเพื่อป้องกันไว้ + firewall และอื่นๆ 🙂

ในบางกรณีเราอยากจะใช้ Logic จริงๆของ component และอยากลองใช้ network จริงหรืออื่นๆมากมาย เราสามารถใช้ท่าไม้ตาย เรียกจริงก็ได้ แต่ Stub Services ไว้

full-component-testing-diagram
full-component-testing-diagram (https://martinfowler.com/articles/microservice-testing/#testing-component-out-of-process-diagram)

ด้วยวิธีนี้เราจะได้การเรียก call ผ่าน Network ของจริง ใช้ Datastore ของจริง แต่ไม่ยึดติดกับ external services เพราะเรา stub ไว้

โอเค….หลังจากเรารวม 3 อย่างน่ะ unit testing – ส่วนเล็กสุดๆของ software เลย, integration testing – จุดเชื่อมต่อกับ external services ส่วนเจ้า component testing ก็คือเรื่องของการ test ทั้งหมดของ SUT โดยตัด external services ไปแต่ใช้ stub แทนเพื่อทดสอบ Logic

มันจะได้รูปแบบนี้ออกมาาาา

high-test-coverage-microservices
high-test-coverage-microservices (https://martinfowler.com/articles/microservice-testing/#testing-progress-3)

จากภาพคือการรวมของ Unit, Integration, Component สุดยอด High test coverage ละ 🙂

Contract Testing

ฟังดูเหมือนมี 3 ตัวนั้นก็จบแล้วน่ะ ครอบคลุมทุกอย่าง เทสได้สบายสุดๆ แต่จริงแล้วไม่ใช่เลย… มันยังมีเรื่องของ Contract testing อีก!!!

Problem statement: เราจะมั่นใจได้อย่างไรว่า external services ที่เราเรียกไม่มีปัญหา เมื่อมีการเปลี่ยนแปลง แล้วเราจะรู้ได้อย่างไร

contract-test-problem-statement
contract-test-problem-statement

เห็นเจ้า Diagram นี้ม่ะ? มันคือเรื่องของ microservices แต่ละตัวไปเรียก external services ตัวนึง โดยที่เจา้ตัวนี้ provide data ที่มี id,age ออกมาให้กับ microservices ต่างๆ แต่อยู่มาวันนึงเกิดมีการเปลี่ยนแปลงระบบขึ้นมา! เค้าเอา Field name,age ออกเป็น firstname,lastname แทน !!! แปลว่าคราวนี้ตายกันยกแผงเลย ที่จะรอดคนเดียวคือ… Contract B นั้นเอง ทีนี้ยุ่งเลย (ต้องแก้ไขด้วย Parallel Change)

มันเลยเกิดการทํา contract test ขึ้นมา เพื่อป้องกันปัญหานี้ จริงๆแล้วการทํา Integration test ก็ช่วยได้แหละ แต่จริงๆแล้วการ Integration test มันไม่ได้ตอบโจทย์ได้ดี เพราะมัน Focus หลายจุดไม่ใช่แค่การ contract ดังนั้นการทํา contract testin น่าจะทําแล้ว build pipeline หรือ CI ทิ้งไว้เลย ให้มันเล็กและเบาที่สุด โดยสร้างตามการใช้งานของผู้ใช้

End-To-End Testing

เจ้าตัวนี้คงง่ายสุดแล้วมั้งในการทํา test อะ แต่ก็ยากสุดในการ Define จุดที่เกิดปัญหาด้วยเช่นกัน

An end-to-end test verifies that a system meets external requirements and achieves its goals, testing the entire system, from end to end

เราจะจัดการ system แบบ black box เลยน่ะ ไม่รู้ไรเลย เล่นผ่าน public interfaces แบบ GUI ไรแบบนั้น หรือ services APIs โดยยึดตาม Business facing issues แล้วกดตามหน้า cases เลย 🙂

อย่างที่บอกอะน่ะ ว่า end-to-end มันใหญ่โตมาก เพราะฉะนั้นการ test ละกษณะนี้จะไปโดน microservices หลายตัวมากๆๆ จนกว่าได้ผลลัพธ์ออกมา เช่นสมมุติเราเป็นคนซื้อของ online เราซื้อของผ่านหน้าเว็ปนี้มันต้องวิ่งผ่าน datawarehouse system, payment services และ email system หรืออื่นๆอีกมากมาย กว่าจะได้ผลลัพธ์ว่าเราซื้อของผ่านมั้ย

สรุปกันซักที

หลักการ design automation tests จริงๆทั้งหมดก็มีแค่นี้แหละ วิ่งวนไปครับ แค่นี้เลย เพียงแต่เราต้องมองภาพรวมให้ออกว่ามีอะไรบ้าง จะได้ออกแบบ test ได้ถูก และทั้งหมดก็มีแค่ตามที่เห็นในบทความนี้เลย

แต่สิ่งนึงที่อยากให้นึกถึงเสมอคือเรื่องของ  test pyramid อันสุดยอดนี้เอง เราจะได้ maintaince เรื่องของ test ได้ถูกในแต่ละ type

test-pyramid
test-pyramid

unit testing คือสิ่งที่สําคัญที่สุดเลยละ แล้วก็ไล่ขึ้นมาเรื่อยๆ 🙂 อย่าไปสนใจกับ UI testing เยอะน่ะ บางทีมันไม่ได้ประโยชน์มากนักหรอก เราคือ Software Engineer In Test เพราะฉะนั้นเราต้องควบคุม behavioral ของ SUT ให้ดีที่สุด

summary-microservices-testing
summary-microservices-testing

เพราะฉะนั้นจงจำเรื่องนี้ให้ขึ้นใจเสมอเลยน่ะ 🙂 การออกแบบจะได้สวยงามที่สุดเลย

 

Noted: ขอบคุณบทความพวกนี้

  • https://martinfowler.com/articles/microservice-testing/
  • https://www.techtalkthai.com/introduction-to-microservices-architecture/
  • http://www.somkiat.cc/contract-testing/
  • http://www.somkiat.cc/introduction-to-microservice/
9gag-long-post-potato
9gag-long-post-potato (https://memegenerator.net/instance/58194044)

Leave a Reply

avatar

This site uses Akismet to reduce spam. Learn how your comment data is processed.