Python - ExitStack แบบสั้นๆ
วันที่ 7 ของ #PythonTricksEveryday วันนี้ขอเสนอ ExitStack() ครับ
มาช้าหน่อยนะครับวันนี้ มัวแต่ตื่นเต้น 1,000 likes
โอเค เข้าเรื่องดีกว่า ก่อนจะไปรู้จัก ExitStack เราต้องรู้จักสิ่งที่เรียกว่า context manager ก่อนครับ context manager เนี่ยจะเป็นตัวจำกัดการเข้าถึงและปล่อย Resource หนึ่งๆ ซึ่ง keyword ที่เราคุ้นเคยที่สุดน่าจะเป็น with ใช่มั้ยครับ ตัว Resource ที่เราพูดถึงกันนี้ง่ายที่สุดก็น่าจะเป็นไฟล์ หรือการต่อ database อะไรเทือกนี้ใช่มั้ยครับ
ทีนี้เวลาเราจะเปิดไฟล์หลายๆ ไฟล์ สิ่งที่เกิดขึ้นคือมันก็จะเกิดอาการ with ซ้อน with ไปเรื่อยๆ จนกระทั่ง Python 3.3 ได้เพิ่ม ExitStack() เข้าไปใน contextlib ครับโดย ExitStack() ตัวนี้จะช่วยจัดการเกี่ยวกับ context manager ได้ดียิ่งขึ้นกว่า nested with ที่เคยใช้กันมา
แต่ในเคสที่ผมกำลังพูดถึงนี่คือการเขียน Unit Test ครับและเป็นเทสที่มี Mock เข้ามาเกี่ยวข้องด้วย และที่พิเศษกว่านั้นคือต้อง Mock เยอะๆ ด้วยครับ
จะเห็นว่า example_function เรามีของที่มี side effect แล้วเราต้อง Mock() อยู่สองอย่างใช่มั้ยครับคือ randint และ datetime.now() แต่ก่อนตอนผมเขียน Unit Test ที่ต้อง Mock อะไรแบบนี้ ผมจะไปเหมือน test case แรกเลยครับ คือใช้เป็น decorator patch() แปะอยู่บนหัวของ test case วิธีนี้ใช้ง่ายครับ แต่จะเริ่มมีปัญหาตามมาเมื่อเริ่ม Mock() มากกว่าหนึ่งตัว
ปัญหาแรกคือ มือใหม่จะสับสนมากว่าที่ patch เรียงๆ กันเนี่ย argument ของ test case มันคือตัวไหน สำหรับคนที่ยังไม่รู้นะครับ argument ของ decorator จะเรียงจากในไปนอกครับ
ปัญหาที่สองคือ ถ้า decorator เยอะมากขึ้นเรื่อยๆ เราจะเริ่มหา test case ไม่เจอครับ เพราะ function signature เรามีแต่ decorator แถมยิ่ง patch decorator มากตัว arguments ของ test case ก็มากตามไปด้วยครับ
จะเห็นว่าในเคสที่สองผมใช้ ExitStack() ร่วมกับ context manager ในการประกาศ patch แทน ซึ่งถามว่าบรรทัดมันน้อยลงมั้ย ก็ไม่ แต่สิ่งที่เราได้กลับมาคือชื่อ test case หรือ function signature เรา clean มากครับ ทำให้มองหาเทสที่จะเจอได้ง่าย และเรายังสามารถแบ่ง statement ในเทสเป็น Arrange / Act / Assert ได้เหมือนเดิมครับ
ต้องบอกว่า Trick นี้ผมไปเจอโดยบังเอิญ เพราะเกิดจากปัญหาที่ผมพูดถึงข้างต้นนั่นแหละครับ จริงๆ พี่ @zkan เคยเขียน Blog พูดถึงเรื่องนี้ตอนผมเอามาใช้กับทีม Pronto Tools ได้ซักพัก ถ้าใครอ่านตรงนี้แล้วงงๆ ลองไปอ่านที่พี่กานต์เขียนได้ครับ
Reference
- Blog เกี่ยวกับ ExitStack() โดย @zkan https://bit.ly/2Kegi3D
- https://docs.python.org/3/library/contextlib.html
Original post at: https://www.facebook.com/writepythontoscarecow/photos/a.206798910040303/233059164080944/?type=3&theater