SOFTWARE DEVELOPMENT | 4 mins read

Mock Object ในภาษา Python

By Nat Nattaphon on 24 Mar 2022
sennalabs-blog-banner

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.

Written By
A full-time backend-developer at Sennalabs. Love to learn new skill + tech and a crypto geek.
A full-time backend-developer at Sennalabs. Love to learn new skill + tech and a crypto geek.

Please Tell Us Your Ideas

We will get back to you within 24 hours!

Something went wrong. Please try to verify again