feature-image-python-testdouble
feature-image-python-testdouble

บทความวันนี้จะมาพูดถึงเรื่องการทํา Unit Testing แบบ Solitary Test ที่จะจัดการพวก replace production object ออก เพื่อจุดประสงค์ในการเทส ซึ่งจะมาดูวิธีเขียนด้วย Python นั้นเอง

โดยหลายๆคนคงรู้จําคําว่า TestDouble ที่ Martin Fowler เคยพูดถึงกันอยู่แล้วแหละ 🙂 เพราะมันคือเรื่องพื้นฐานของการทําเทส โดยหลักการของ TestDouble นั้นง่ายเพียงนิดเดียวคือ “Replace a production object for testing purpose” แค่นี้เลย …. อะไรที่มันต่อของจริงก็ให้ replace มันออกเพื่อทําการทดสอบ โดยมันมีหลากหลายรูปแบบดังข้างล่างนี้

testdouble-testing
testdouble-testing (https://martinfowler.com/bliki/TestDouble.html)

แต่คําที่ได้ยินบ่อยๆในการทํางานคือ Mocks และ Stubs ซึ่งจะมีคนสับสนอยู่บ่อยๆซึ่งจริงๆแล้วมันไม่เหมือนกัน อ่านบทความเต็มได้ที่นี้

สั้นๆคือ Mock ทดสอบ Behavior verification แต่ Stubs ทดสอบเรื่อง States verification คําว่าทดสอบ Behavior หมายถึง พยายามทําให้ SUT เชื่อว่ามันทํางานจริงๆ เช่น

  • Behavior เราอาจจะทดสอบว่าระบบลงทะเบียนนักศึกษาจะต้องสามารถให้นักศึกษาลงทะเบียนได้ เพราะฉะนั้นเวลาระบบทํางานต้องมีการเรียก save() แน่ๆ เราก็เช็คว่ามันเรียก save() จริงมั้ยแค่นั้น
  • States มันจะหมายถึงค่า ณ ช่วงเวลานึงของสิ่งๆนั้นเช่น ระบบลงทะเบียนนักศึกษาเหมือนอันเมื่อกี้มันจะต้องส่งค่ากลับมาก่อน save() ว่ามีนักศึกษาคนนี้จริงๆหรือไม่ โดยที่มันจะส่งค่า TRUE หรือ FALSE กลับมาให้ ทีนี้เราก็ไป stub ค่าให้มันแล้วก็เรียก save() ไป มันก็ต้อง return ค่าที่ stub ไว้ออกมานั้นเอง

ทีนี้เราก็เข้าใจเรื่องของ TestDouble คร่าวๆกันล่ะ อยากได้รายละเอียดเต็มๆอ่านบล๊อคของพี่ปุ๋ย (Somkiat) ก็ได้น่ะ จะได้ไม่ซํ้ากัน แต่เราจะมาดูวิธีการใช้งานจริงด้วย Python กัน

Python Mock Library

เจ้า python นี้มันมันได้เตรียมการทํา Unit Testing ไว้อยู่แล้วล่ะ และก็โค้ดสวยงามกว่าในการทํางานด้วน Java พวก EasyMock และ Mockito มาก (ความเห็นส่วนตัว)

โดยอย่างที่รู้กัน python จะแบ่งเป็นสองแบบคือ python 2.7 กับ python 3.0 โดย Python 3.0 จะถูก Bundle เข้ามาพร้อมกับ Python อยู่แล้ว ไม่ต้องลง Library เพิ่มเลย แต่ถ้า python 2.7 ก็ต้องลงเพิ่มหน่อยน่ะโดยใช้ “pip install mock” ง่ายๆแค่นี้แหละ

แล้วถ้ากังวลเรื่อง version ของ python ที่จะนําไปรัน เราอาจจะใส่ try-catch ให้มันเพื่อที่จะได้ป้องกันการ break compatibility

python-unittesting-import-way
python-unittesting-import-way (http://krzysztofzuraw.com/blog/2016/mocks-monkeypatching-in-python.html)

ส่วน document mock ของ python 2.7 ใช้ ตัวนี้ น่าจะสะดวกกว่าน่ะ

ที้นี้มาเข้าเรื่องการทํา test กัน โดยหลักๆของ Python เลย เราจะใช้อยู่แค่ 3 อย่างเลยง่ายๆก็คือ

  1. Mock
  2. MagicMock
  3. Patch

Mock

Mock มันเป็น Object ที่เกิดมาเพื่อใช้ในการ stubs (state verification) และ test doubles อื่นๆในโค้ดของเราเลย มันสามารถ set ค่าต่างๆ เพื่อให้เราสามารถ assertion ได้สบายๆเลย

เป็นคลาสหลักของการทำ testing เลย ตัวอย่างของการใช้งานแบบนี้ สร้าง Mock(…) ขึ้นมาเลย

mock-python-example
mock-python-example

แต่ถึงเวลาจริงๆเวลาเราทํา Patch decorator จะง่ายกว่า รอดูข้างล่างกัน

MagicMock

MagicMock ใครไม่รู้ก็คงจะงงๆว่ามันคืออะไร จริงๆแล้วมันก็คือ subclass ตัวนึงของ Mock แหละ

แต่เชื่อเลยว่าทุกคนจะงงว่า Magic อะไรหว่า? MagicMock? จริงๆแล้วใน Python มี method ที่เรียกว่า Magic Methods อยู่แหละ นั้นก็คือพวก method ที่มี dobule underscores อยู่นั้นเอง เช่น

  • __init__
  • __new__
  • อื่นๆอีกมากมาย

ความ Magic ของมันก็คือ เราไม่จําเป็นต้อง Invoke It Directly นั้นเอง มันจะทํางานของมันเองในเบื้องหลังเลย เช่นน่ะ สมมุติเราสร้าง object x = A() ด้วยแค่บรรทัดนี้มันก็จะไป invoke 2 magic method นั้นก็คือ __init__ และ __new__ นั้นเอง (บางทีก็ใส่คําภาษาอังกฤษคําไทยคํา ไม่ได้กระแดะน่ะ แต่มันแปลความหมายได้ดีกว่าภาษาไทย 🙂 ไม่งั้นคง งง อะ ถ้าพูดว่า เมธอดเวทย์มนตร์ ไรแบบนั้น)

ทีนี้กลับมาที่ MagicMock มันคืออะไร? มันก็คือตัวลดเวลาของเรา เพราะถ้าใช้ Mock ทั่วไป เราจะต้องมานั่ง set ค่าของ Magic Method เองและมันเสียเวลา แต่ในทางกลับกันถ้าเราใช้ MagicMock มันจะทําให้เราสามารถจัดการได้เอง โยไม่จําเป็นต้องมา configure the magic methods เองเลย

python-mock-with-magic-method
python-mock-with-magic-method (http://www.voidspace.org.uk/python/mock/magicmock.html)

แต่ถ้าเป็น MagicMock จะมีค่าของ magicmock มาเลยตามแบบด้านล่างนี้ 🙂

python-magicmock-example
python-magicmock-example

ความแตกต่างมีแค่นี้แหละ เราสามารถไปอ่านที่เค้าคุยกันใน stackoverflow ได้เหมือนกันนะ

รูปแบบของการใช้งานตรงๆจะเป็นงี้ สร้าง MagicMock(…) ขึ้นมาเลย 🙂

python-magicmock-example
python-magicmock-example

แต่ถึงเวลาจริงๆเวลาเราทํา Patch decorator จะง่ายกว่า รอดูข้างล่างกัน

Patch

Patch นี้ตัวเด็ดๆเลย ซึ่งเราจะใช้มันบ่อยสุดๆ มันก็คือ function decorator ตัวนึง ไว้จัดการ mock ทุกอย่างเลย เด็ดมากๆ โดยหลักๆการใช้ patch จะเป็นหน้าตางี้

python-unittesting-patch
python-unittesting-patch (https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch)

จะเห็นว่ามันรับ arguments หลากหลายมากๆ แต่ตัวที่เป็น require คือ target ซึ่งหมายถึง string ที่ระบุถึง จุดที่เราจะนําไปใช้ เช่น ‘package.module.ClassName’

example-mock-patch-python
example-mock-patch-python (http://krzysztofzuraw.com/blog/2016/mocks-monkeypatching-in-python.html)

ตัวอย่างคือเรา Import มาจาก function มา 2 ตัวคือ square และ cube จากไฟล์ function เพราะฉะนั้นเวลาเรา Patch ก็ต้องใช้ function.square นั้นเอง

แต่จุดน่าสนใจคือ @mock.patch(‘__main__.square,return_value=1) แล้วทําไมเจ้าตัวนี้ถึงไม่เหมือน function.square ล่ะ??? นั้นเป็นเพราะว่า …. ใน Python เราต้อง Mock ในจุดที่เราใช้ๆ

“Mock an item where it is used,not where it came from.”

ถ้าเห็นใน method แรกจะพบว่าเราเรียก square จากในนี้ตรงๆ เพราะฉะนั้นเราต้อง Mock ในนี้ซึ่งเป็นจุดที่เราเรียกใช้นั้นเอง

จุดที่น่าสนใจของ Arguments คือ

  • argument new  ถ้าไม่ได้ส่งไรไป มันจะส่งไปโดยพื้นฐานคือ MagicMock (mock พวก magic method ทั้งหมด)
  • argument spec และ spec_set คือ MagicMock จะ create แค่เฉพาะที่มีใน spec
  • argument autospec ถ้า set เป็น True ไว้ แปลว่า mock จะถูกสร้างโดยมี spec เหมือนกับตัวที่ถูก Mock ทั้งหมดเลย เช่น มี attribute เหมือนกันหมด
  • อ่านตัวเต็มที่นี้เลย

จากตัวอย่างน่ะ จะเห็นว่าเรา Patch function.square และ function.cube ไว้ แล้วก็ตั้ง return_value ให้มันเพื่อเช็ค state ต่างๆ เวลา Method ทํางานจะได้ assert ได้ถูกต้องนั้นเอง

ข้อควรระวังคือ เวลา nesting patch decorators ยังงี้ Order สําคัญมากๆ เพราะ python มันจะส่งค่าตามลําดับอย่างรูปข้างล่าง

nesting-patch-decorator
nesting-patch-decorator (http://www.voidspace.org.uk/python/mock/patch.html#nesting-patch-decorators)

ถ้าไปสลับจะเห็นเลยว่าได้ Object คนละตัวกัน นั้นเป็นเพราะ Python ทํางานโดยการไล่ลําดับแบบนี้ patch_class_method(patch_static_method(test_somethings)) นั้นเอง

หลังจากรู้คร่าวๆๆว่า Patch Decorator ทําหน้าที่ไรแล้ว @mock.patch ไม่ได้มีตัวเดียวน่ะมันแยกออกไปหลากหลายแบบมาก เช่น

ซึ่งแต่ละตัวก็มีจุดประสงค์แตกต่างกันไปอะ อย่าง Object ก็ไว้ใช้กับพวก Method, Dict ก็ Dictionary, multiple ก็เพื่อ patch ทีเดียวหลายๆๆๆตัวเลยในการเรียกครั้งเดียว

แต่จริงๆแล้วเราใช้กันบ่อยๆแค่ @mock.path() และ @mock.patch.object() เท่านั้นแหละ ฮ๋าๆ

สรุปแล้ววว

มันไม่ได้ยากเลยการใช้ Mock Library เพื่อจัดการทํา Unit test บน python อาจจะงงๆหน่อย แต่เจ้าบทความนี้จะช่วยเปิดโลกให้เข้าใจภาพรวม แล้วไปเขียน ไปฝึกกันจริงๆอีกทีจะช่วยได้แหละ 🙂

จาก 3 ตัวนี้ mock, magicmock, patch เราจะใช้ patch decorator เป็นตัวหลัก เพราะมันง่ายกว่าการมาสร้าง Mock object เยอะมากๆ โดยใช้แค่ @mock.patch() แค่นี้เลย

เรื่องที่ควรจำก็คือ เวลา @patch นี่ object mock จะถูกส่งเข้ามาผ่าน argument ตามลำดับ แล้วเราก็มากำหนด state ตามนั้นไป เพื่อ assert ค่าของมัน

Noted

  • http://krzysztofzuraw.com/blog/2016/mocks-monkeypatching-in-python.html
  • https://www.toptal.com/python/an-introduction-to-mocking-in-python
  • http://manishamde.github.io/blog/2013/10/06/mocking-python-with-kung-fu-panda/
  • http://stackoverflow.com/questions/2665812/what-is-mocking
  • http://www.voidspace.org.uk/python/mock/magicmock.html
  • http://ibiblio.org/g2swap/byteofpython/read/module-name.html
  • http://stackoverflow.com/questions/419163/what-does-if-name-main-do

Leave a Reply

avatar

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