Mock Object ในภาษา Python
Mock Object คืออะไร
Mock Object คือวิธีการที่ใช้ในการกำหนดพฤติกรรม Object เพื่อให้ได้ผลลัพธ์ที่ใช้ในการทำ Unit Test ตาม Environment ที่ต้องการ โดยจะเป็นการสมมุติว่า ฟังก์ชันทำงานถูกต้องและจะทำการ Mock ข้อมูลขึ้นมาเพื่อใช้ทำการทดสอบ
สาเหตุที่ต้องใช้หลัก ๆ Python Mock Object เพื่อควบคุมสิ่งที่เกิดขึ้นในการทำ UnitTest
ในบางครั้ง ต้องทำการทดสอบฟังก์ชัน เช่น get_coin_price ซึ่งจะทำการแสดงข้อมูลราคาของเหรียญ โดยดึงจากฐานข้อมูล ซื่งถ้าใช้ฐานข้อมูลจริงในบางครั้งผลลัพธ์ที่ได้อาจจะไม่ถูกต้อง เพราะฐานข้อมูลอาจจะมีการเปลี่ยนแปลงซึ่งยากที่จะควบคุม ดังนั้น จึงเป็นสาเหตุที่ดีกว่าที่จะทำการทดสอบโค้ดในสถานการที่ควบคุมได้ โดยการใช้ Mock Object เพื่อทำการจะให้ฟังก์ชันได้ผลลัพธ์จะเป็นทั้ง successful or failures response
ภาษา Python นั้นได้มี Library สำหรับการทำ unittest mock โดยสามารถทำการติดตั้งผ่าน Pip package manager ได้เลย
$ pip install mock
การประกาศ Mock Object
from unittest.mock import Mock
mock = Mock()
print(mock)>> <Mock id='140519256300896'>
การประกาศ Mock Object สำหรับ Mock Function
from unittest.mock import Mock
mock = Mock()
print(mock.get_coin_price()) >> <Mock name='mock.get_coin_price()' id='140379447928048'>
assert_called_once เป็นการตรวจสอบว่า mock object นั้นได้ถูกเรียก 1 ครั้ง
โดยจากโค้ดทำการ Mock Object ให้ทำการเรียกใช้ a_plus_b() โดยจะทำการตรวจสอบว่าฟังก์ชันนี้ได้ทำการเรียกใช้แล้ว 1 ครั้งหรือไม่
from unittest.mock import Mock
def a_plus_b(a,b): return a+b
mock = Mock()
mock.a_plus_b()
mock.a_plus_b.assert_called_once()
หากทำการลบ mock.a_plus_b() ก็จะทำให้มันไม่ถูกเรียกใช้ และการทดสอบจะ fail
assert_called_with เป็นการตรวจสอบว่า Mock Object นั้นได้ถูกเรียกด้วยข้อมูลชุดนี้หรือไม่
โดยจากโค้ดทำการ Mock Object ขี้นมา mock.a_plus_b(a=20, b=25) โดยให้รับตัวแปร 2 ตัว จากนั้นบรรทัดถัดไปจึงมาทำการตรวจสอบว่าฟังก์ขั่นถูกเรียกด้วยข้อมูลที่เหมือนกันหรือไม่
from unittest.mock import Mock
def a_plus_b(a,b): return a+b
mock = Mock()
mock.a_plus_b(a=20, b=25)
mock.a_plus_b.assert_called_with(a=20, b=25)
return_value ใช้ในการตั้งค่าข้อมูลที่จะถูกส่งกลับมาจาก Mock Object ของฟังก์ชัน โดยจากโค้ดจะทำการตั้งค่าให้ข้อมูลที่ถูกส่งกลับมาคือ 30
from unittest.mock import Mock
def a_plus_b(a,b): return a+b
value = 30
mock = Mock()# ทำการ set ค่าให้ส่ง 30 กลับมาเมื่อเรียกใช้
mock_value = mock.a_plus_b().return_value = value
assert mock_value == value
print(mock.a_plus_b().return_value)>> 30
side_effect เป็นการ Mock Object ให้ฟังก์ชันที่โดน Mock นั้นเกิดผลลัพธ์ได้หลายแบบ เช่นจากโด้ดจะตั้งค่าให้หากเรียกใช้ครั้งแรกให้ใช้ค่าเลข 3 หากถูกเรียกครั้งถัดมาจะเป็น 2 ตามลำดับ และในฟังก์ชันสามารถรับค่าเป็น list หรือ ฟังก์ชันได้
from unittest.mock import Mock
mock = Mock()
mock.side_effect = [3, 2, 1]
print(mock(), mock(), mock())>> 3 2 1
call_count เป็นฟังก์ชันที่ใช้ในการนับจำนวนครั้งที่ฟังก์ชันที่ทำการ Mock Object ถูกเรียกใช้งาน
mock = Mock(return_value=None)
print(mock.call_count)>> 0
mock()
mock()
mock.call_count
print(mock.call_count)>> 2
ตัวอย่างการใช้ mock object ใน unittest
ถัดไปจะเป็นตัวอย่างการ Mock Object ทั้ง Request และ Response เนื่องจากต้องการทดสอบว่าหากทำการเรียกใช้ API ไป 2 ครั้ง โดยครั้งแรกจะควบคุมให้เมื่อเรียกใช้ API ให้บังคับให้เกิดเหตุการณ์ request timeout ส่วนครั้งที่ 2 สามารถเรียกใช้ ได้ปกติและได้ข้อมูลกลับมา
import unittest
from unittest.mock import Mock
from requests.exceptions import Timeout# Mock request
requests = Mock()
def get_bitcoin_price(): r = requests.get('http://localhost/api/crypto') if r.status_code == 200: return r.json() return None
class TestCrypto(unittest.TestCase): def test_crypto(self): # Mock response response_mock = Mock() response_mock.status_code = 200 response_mock.json.return_value = { 'name': 'Bit Coin', 'price': 40000, 'rank': 1, } # Set side_effect สำหรับ .get method requests.get.side_effect = [Timeout, response_mock] # ทดสอบให้การเรียกใช้ครั้งแรกติด Timeout with self.assertRaises(Timeout): get_bitcoin_price() # ทดสอบครั้งที่สองสำเร็จให้ตรวจสอบข้อมูล price มีค่าเท่ากีบ 40000 assert get_bitcoin_price()['price'] == 40000 # ทำการทดสอบ .get method ถูกเรียกใช้ไป 2 ครั้ง assert requests.get.call_count == 2
if __name__ == '__main__': unittest.main()
แหล่งอ้างอิง
• Unittest.mock — Mock object library, Python Development Tool.
• Understanding the Python Mock Object Library, by Alex Ronquillo.