Hướng dẫn how does mocking work in python? - làm thế nào để chế nhạo hoạt động trong python?

Bài đăng này được viết bởi Mike Lin.

Chào mừng bạn đến với một hướng dẫn về những điều cơ bản của chế giễu trong Python. Nó được sinh ra từ nhu cầu của tôi để kiểm tra một số mã đã sử dụng nhiều dịch vụ mạng và kinh nghiệm của tôi với

Gomock, cho tôi thấy chế giễu mạnh mẽ như thế nào khi được thực hiện chính xác (cảm ơn, Tyler). Tôi sẽ bắt đầu với một cuộc thảo luận triết học về việc chế giễu bởi vì chế giễu tốt đòi hỏi một tư duy khác với sự phát triển tốt. Phát triển là về việc làm mọi thứ, trong khi chế giễu là về việc giả mạo mọi thứ. Điều này có vẻ rõ ràng, nhưng khía cạnh "giả mạo" của các bài kiểm tra chế giễu chạy sâu và hiểu điều này hoàn toàn thay đổi cách người ta nhìn vào thử nghiệm. Sau đó, chúng tôi sẽ xem xét các công cụ chế giễu mà Python cung cấp, và sau đó chúng tôi sẽ kết thúc với một ví dụ đầy đủ. Tìm hiểu thêm về mã kiểm tra cho bảo mật Python với tờ cheat của chúng tôi., which showed me how powerful mocking can be when done correctly (thanks, Tyler). I'll begin with a philosophical discussion about mocking because good mocking requires a different mindset than good development. Development is about making things, while mocking is about faking things. This may seem obvious, but the "faking it" aspect of mocking tests runs deep, and understanding this completely changes how one looks at testing. After that, we'll look into the mocking tools that Python provides, and then we'll finish up with a full example. Learn more about testing code for python security with our cheat-sheet.

Chế giễu có thể khó hiểu. Khi tôi kiểm tra mã mà tôi đã viết, tôi muốn xem liệu mã có thực hiện những gì nó phải làm từ đầu đến cuối hay không. Tôi thường bắt đầu nghĩ về một bài kiểm tra tích hợp, chức năng, trong đó tôi nhập vào đầu vào thực tế và nhận được đầu ra thực tế. Tôi truy cập mọi hệ thống thực mà mã của tôi sử dụng để đảm bảo các tương tác giữa các hệ thống đó hoạt động đúng, sử dụng các đối tượng thực và các cuộc gọi API thực. Mặc dù các loại thử nghiệm này rất cần thiết để xác minh rằng các hệ thống phức tạp đang làm việc tốt, nhưng chúng không phải là những gì chúng ta muốn từ các bài kiểm tra đơn vị.

Các bài kiểm tra đơn vị là về việc kiểm tra lớp ngoài cùng của mã. Các thử nghiệm tích hợp là cần thiết, nhưng các bài kiểm tra đơn vị tự động mà chúng tôi chạy không nên đạt đến độ sâu tương tác của hệ thống. Điều này có nghĩa là bất kỳ cuộc gọi API nào trong chức năng chúng tôi đang thử nghiệm có thể và nên bị chế giễu. Chúng ta nên thay thế mọi cuộc gọi API hoặc tạo đối tượng không cần thiết bằng một cuộc gọi hoặc đối tượng giả. Điều này cho phép chúng tôi tránh sử dụng tài nguyên không cần thiết, đơn giản hóa việc khởi tạo các bài kiểm tra của chúng tôi và giảm thời gian chạy của chúng. Hãy nghĩ về việc kiểm tra một chức năng truy cập API HTTP bên ngoài. Thay vì đảm bảo rằng một máy chủ thử nghiệm có sẵn để gửi các phản hồi chính xác, chúng tôi có thể chế giễu thư viện HTTP và thay thế tất cả các cuộc gọi HTTP bằng các cuộc gọi giả. Điều này làm giảm sự phức tạp và phụ thuộc của thử nghiệm, và cung cấp cho chúng tôi sự kiểm soát chính xác đối với những gì thư viện HTTP trả về, điều này có thể khó thực hiện khác.

Hướng dẫn how does mocking work in python? - làm thế nào để chế nhạo hoạt động trong python?

Chúng ta có nghĩa là gì khi chế giễu? & Nbsp; & nbsp;

Thuật ngữ chế giễu được ném xung quanh rất nhiều, nhưng tài liệu này sử dụng định nghĩa sau:

"Việc thay thế một hoặc nhiều cuộc gọi hoặc đối tượng chức năng bằng các cuộc gọi hoặc đối tượng giả"

Một cuộc gọi hàm giả trả về một giá trị được xác định trước ngay lập tức, mà không thực hiện bất kỳ công việc nào. Các thuộc tính và phương thức của đối tượng giả được xác định tương tự hoàn toàn trong thử nghiệm, mà không tạo đối tượng thực hoặc thực hiện bất kỳ công việc nào. Thực tế là người viết bài kiểm tra có thể xác định các giá trị trả về của mỗi cuộc gọi hàm mang lại cho anh ta hoặc cô ta một sức mạnh to lớn khi thử nghiệm, nhưng điều đó cũng có nghĩa là anh ta cần phải thực hiện một số công việc nền tảng để thiết lập mọi thứ đúng.

Trong Python, chế giễu được thực hiện thông qua mô -đun

[in my_module.py] 
from module import ClassA
0. Mô -đun chứa một số lớp và chức năng hữu ích, trong đó quan trọng nhất là hàm
[in my_module.py] 
from module import ClassA
1 (với tư cách là trình trang trí và trình quản lý bối cảnh) và lớp
[in my_module.py] 
from module import ClassA
2. Việc chế giễu trong Python phần lớn được thực hiện thông qua việc sử dụng hai thành phần mạnh mẽ này.

Chúng ta không có nghĩa là gì khi chế giễu?

Các nhà phát triển sử dụng rất nhiều đối tượng hoặc mô -đun "giả", là thay thế cục bộ đầy đủ chức năng cho các dịch vụ và API được nối mạng. Ví dụ: thư viện

[in my_module.py] 
from module import ClassA
3 là thư viện giả
[in my_module.py] 
from module import ClassA
4 thu thập tất cả các cuộc gọi API
[in my_module.py] 
from module import ClassA
4 và xử lý chúng tại địa phương. Mặc dù các giả này cho phép các nhà phát triển kiểm tra các API bên ngoài cục bộ, nhưng chúng vẫn yêu cầu tạo các đối tượng thực. Đây không phải là loại chế giễu trong tài liệu này. Tài liệu này cụ thể về việc sử dụng các đối tượng
[in my_module.py] 
from module import ClassA
2 để quản lý đầy đủ luồng điều khiển của hàm được kiểm tra, cho phép thử nghiệm dễ dàng các lỗi và xử lý ngoại lệ.

Làm thế nào để chúng ta chế giễu trong Python?

Việc chế giễu trong Python được thực hiện bằng cách sử dụng

[in my_module.py] 
from module import ClassA
1 để chiếm đoạt chức năng API hoặc cuộc gọi tạo đối tượng. Khi
[in my_module.py] 
from module import ClassA
1 chặn cuộc gọi, nó sẽ trả về một đối tượng
[in my_module.py] 
from module import ClassA
2 theo mặc định. Bằng cách đặt các thuộc tính trên đối tượng
[in my_module.py] 
from module import ClassA
2, bạn có thể chế giễu cuộc gọi API để trả về bất kỳ giá trị nào bạn muốn hoặc tăng
@patch('module.ClassB')
@patch('module.functionA')

def test_some_func(self, mock_A, mock_B): 
...
1.

Quy trình tổng thể như sau:

  1. Viết bài kiểm tra như thể bạn đang sử dụng API bên ngoài thực sự.
  2. Trong chức năng được kiểm tra, xác định cuộc gọi API nào cần được chế giễu; Đây phải là một số nhỏ.
  3. Trong chức năng kiểm tra, vá các cuộc gọi API.
  4. Thiết lập các phản hồi đối tượng
    [in my_module.py] 
    from module import ClassA
    2.
  5. Chạy bài kiểm tra của bạn.

Nếu bài kiểm tra của bạn vượt qua, bạn đã hoàn thành. Nếu không, bạn có thể gặp lỗi trong chức năng được kiểm tra hoặc bạn có thể đã thiết lập phản hồi

[in my_module.py] 
from module import ClassA
2 của mình không chính xác. Tiếp theo, chúng tôi sẽ đi sâu vào chi tiết hơn về các công cụ mà bạn sử dụng để tạo và định cấu hình giả.

import unittest 
from unittest.mock import patch

[in my_module.py] 
from module import ClassA
1 có thể được sử dụng làm chất trang trí cho chức năng thử nghiệm, lấy một chuỗi đặt tên hàm sẽ được vá làm đối số. Để
[in my_module.py] 
from module import ClassA
1 định vị chức năng được vá, nó phải được chỉ định bằng tên đủ điều kiện, có thể không phải là những gì bạn mong đợi. Nếu một lớp được nhập bằng cách sử dụng câu lệnh
@patch('module.ClassB')
@patch('module.functionA')

def test_some_func(self, mock_A, mock_B): 
...
6,
@patch('module.ClassB')
@patch('module.functionA')

def test_some_func(self, mock_A, mock_B): 
...
7 sẽ trở thành một phần của không gian tên của mô -đun mà nó được nhập.

Ví dụ: nếu một lớp được nhập trong mô -đun

@patch('module.ClassB')
@patch('module.functionA')

def test_some_func(self, mock_A, mock_B): 
...
8 như sau:

[in my_module.py] 
from module import ClassA

Nó phải được vá là

@patch('module.ClassB')
@patch('module.functionA')

def test_some_func(self, mock_A, mock_B): 
...
9, thay vì
[in test_my_module]
@patch('external_module.api_call')
def test_some_func(self, mock_api_call): 
mock_api_call.return_value = MagicMock(status_code=200,response=json.dumps({'key':'value'})) 
my_module.some_func()
[in my_module]import external_module
def some_func(): 
response = external_module.api_call()  
#normally returns a Response object, but now returns a MagicMock
#response == mock_api_call.return_value == MagicMock(status_code=200, response=json.dumps({'key':'value'}))
0, do ngữ nghĩa của câu lệnh
[in test_my_module]
@patch('external_module.api_call')
def test_some_func(self, mock_api_call): 
mock_api_call.return_value = MagicMock(status_code=200,response=json.dumps({'key':'value'})) 
my_module.some_func()
[in my_module]import external_module
def some_func(): 
response = external_module.api_call()  
#normally returns a Response object, but now returns a MagicMock
#response == mock_api_call.return_value == MagicMock(status_code=200, response=json.dumps({'key':'value'}))
1, nhập các lớp và chức năng vào không gian tên hiện tại.

Thông thường

[in my_module.py] 
from module import ClassA
1 được sử dụng để vá một cuộc gọi API bên ngoài hoặc bất kỳ lệnh gọi chức năng hoặc chức năng sử dụng nhiều tài nguyên nào khác hoặc việc tạo đối tượng. Bạn chỉ nên vá một vài thiết bị gọi cho mỗi bài kiểm tra. Nếu bạn thấy mình đang cố gắng
[in my_module.py] 
from module import ClassA
1 nhiều hơn một số lần, hãy xem xét tái cấu trúc bài kiểm tra của bạn hoặc chức năng bạn đang kiểm tra.

Sử dụng trình trang trí

[in my_module.py] 
from module import ClassA
1 sẽ tự động gửi một đối số vị trí đến chức năng bạn đang trang trí (tức là chức năng kiểm tra của bạn). Khi vá nhiều chức năng, người trang trí gần chức năng được trang trí nhất được gọi là đầu tiên, vì vậy nó sẽ tạo ra đối số vị trí đầu tiên.

@patch('module.ClassB')
@patch('module.functionA')

def test_some_func(self, mock_A, mock_B): 
...

Theo mặc định, các đối số này là các trường hợp của

[in my_module.py] 
from module import ClassA
2, đó là đối tượng chế giễu mặc định của ____ 10. Bạn có thể xác định hành vi của hàm được vá bằng cách đặt các thuộc tính trên thể hiện
[in my_module.py] 
from module import ClassA
2 đã trả lại.

Hướng dẫn how does mocking work in python? - làm thế nào để chế nhạo hoạt động trong python?

MagicMock

[in my_module.py] 
from module import ClassA
2 Các đối tượng cung cấp một giao diện chế giễu đơn giản cho phép bạn đặt giá trị trả về hoặc hành vi khác của chức năng hoặc cuộc gọi tạo đối tượng mà bạn đã vá. Điều này cho phép bạn xác định đầy đủ hành vi của cuộc gọi và tránh tạo các đối tượng thực, có thể gây khó chịu. Ví dụ: nếu chúng tôi đang khắc một cuộc gọi đến
[in test_my_module]
@patch('external_module.api_call')
def test_some_func(self, mock_api_call): 
mock_api_call.return_value = MagicMock(status_code=200,response=json.dumps({'key':'value'})) 
my_module.some_func()
[in my_module]import external_module
def some_func(): 
response = external_module.api_call()  
#normally returns a Response object, but now returns a MagicMock
#response == mock_api_call.return_value == MagicMock(status_code=200, response=json.dumps({'key':'value'}))
9, cuộc gọi thư viện HTTP, chúng tôi có thể xác định phản hồi cho cuộc gọi đó sẽ được trả về khi cuộc gọi API được thực hiện trong chức năng được kiểm tra, thay vì đảm bảo rằng máy chủ kiểm tra là Có sẵn để trả lại phản hồi mong muốn.

Hai thuộc tính quan trọng nhất của một ví dụ

[in my_module.py] 
from module import ClassA
2 là
m = MagicMock()m.foo() 
#no error raised
# Response objects have a status_code attributem = MagicMock(spec=Response, status_code=200, response=json.dumps({‘key’:’value’}))m.foo() 
#raises AttributeErrorm.status_code #no error raised
1 và
m = MagicMock()m.foo() 
#no error raised
# Response objects have a status_code attributem = MagicMock(spec=Response, status_code=200, response=json.dumps({‘key’:’value’}))m.foo() 
#raises AttributeErrorm.status_code #no error raised
2, cả hai đều cho phép chúng tôi xác định hành vi trả lại của cuộc gọi được vá.

return_value

Thuộc tính

m = MagicMock()m.foo() 
#no error raised
# Response objects have a status_code attributem = MagicMock(spec=Response, status_code=200, response=json.dumps({‘key’:’value’}))m.foo() 
#raises AttributeErrorm.status_code #no error raised
1 trên thể hiện
[in my_module.py] 
from module import ClassA
2 được chuyển vào chức năng kiểm tra của bạn cho phép bạn chọn những gì mà các bản trả lại có thể gọi được vá. Trong hầu hết các trường hợp, bạn sẽ muốn trả lại một phiên bản giả của những gì mà người ta có thể gọi thường sẽ trở lại. Đây có thể là JSON, một giá trị, một giá trị, một ví dụ của đối tượng phản hồi thực,
[in my_module.py] 
from module import ClassA
2 giả vờ là đối tượng phản hồi hoặc bất cứ điều gì khác. Khi vá các đối tượng, cuộc gọi được vá là cuộc gọi tạo đối tượng, vì vậy
m = MagicMock()m.foo() 
#no error raised
# Response objects have a status_code attributem = MagicMock(spec=Response, status_code=200, response=json.dumps({‘key’:’value’}))m.foo() 
#raises AttributeErrorm.status_code #no error raised
1 của
[in my_module.py] 
from module import ClassA
2 phải là một đối tượng giả, có thể là một
[in my_module.py] 
from module import ClassA
2 khác.

Nếu mã bạn đang thử nghiệm là Pythonic và gõ vịt thay vì gõ rõ ràng, sử dụng

[in my_module.py] 
from module import ClassA
2 làm đối tượng phản hồi có thể thuận tiện. Thay vì trải qua những rắc rối trong việc tạo một thể hiện thực của một lớp, bạn có thể xác định các cặp giá trị khóa thuộc tính tùy ý trong hàm tạo
[in my_module.py] 
from module import ClassA
2 và chúng sẽ được áp dụng tự động cho phiên bản.

[in test_my_module]
@patch('external_module.api_call')
def test_some_func(self, mock_api_call): 
mock_api_call.return_value = MagicMock(status_code=200,response=json.dumps({'key':'value'})) 
my_module.some_func()
[in my_module]import external_module
def some_func(): 
response = external_module.api_call()  
#normally returns a Response object, but now returns a MagicMock
#response == mock_api_call.return_value == MagicMock(status_code=200, response=json.dumps({'key':'value'}))

Lưu ý rằng đối số được chuyển cho

[in test_my_module]
@patch('external_module.api_call')

def test_some_func(self, mock_api_call): 
mock_api_call.side_effect = SomeException() 
my_module.some_func()[in my_module]def some_func(): 
try:  
	external_module.api_call() 

except SomeException:  
	print(“SomeException caught!”) 
	# this code is executed 
	except SomeOtherException:  
	print(“SomeOtherException caught!”) 
	# not executed[in test_my_module]
@patch('external_module.api_call')

def test_some_func(self, mock_api_call): 
	mock_api_call.side_effect = [0, 1] 
	my_module.some_func()[in my_module]

def some_func(): 
	rv0 = external_module.api_call() 
	# rv0 == 0 
	rv1 = external_module.api_call() 
	# rv1 == 1
1, tức là,
[in test_my_module]
@patch('external_module.api_call')

def test_some_func(self, mock_api_call): 
mock_api_call.side_effect = SomeException() 
my_module.some_func()[in my_module]def some_func(): 
try:  
	external_module.api_call() 

except SomeException:  
	print(“SomeException caught!”) 
	# this code is executed 
	except SomeOtherException:  
	print(“SomeOtherException caught!”) 
	# not executed[in test_my_module]
@patch('external_module.api_call')

def test_some_func(self, mock_api_call): 
	mock_api_call.side_effect = [0, 1] 
	my_module.some_func()[in my_module]

def some_func(): 
	rv0 = external_module.api_call() 
	# rv0 == 0 
	rv1 = external_module.api_call() 
	# rv1 == 1
2, là một
[in my_module.py] 
from module import ClassA
2 và chúng tôi đang đặt
m = MagicMock()m.foo() 
#no error raised
# Response objects have a status_code attributem = MagicMock(spec=Response, status_code=200, response=json.dumps({‘key’:’value’}))m.foo() 
#raises AttributeErrorm.status_code #no error raised
1 cho một
[in my_module.py] 
from module import ClassA
2 khác. Khi chế giễu, mọi thứ đều là
[in my_module.py] 
from module import ClassA
2.

Specing a MagicMock

Mặc dù tính linh hoạt của ____ 12 là thuận tiện cho các lớp chế giễu nhanh chóng với các yêu cầu phức tạp, nhưng nó cũng có thể là một nhược điểm. Theo mặc định,

[in my_module.py] 
from module import ClassA
2s hành động giống như chúng có bất kỳ thuộc tính nào, thậm chí các thuộc tính mà bạn không muốn họ có. Trong ví dụ trên, chúng tôi trả về một đối tượng
[in my_module.py] 
from module import ClassA
2 thay vì đối tượng
[inside some_func]someAPI.API_call(foo, bar='baz')[inside test_some_func]some_func()mock_api_call.assert_called_with(foo, bar='baz')
0. Tuy nhiên, giả sử chúng tôi đã phạm sai lầm trong cuộc gọi
[in my_module.py] 
from module import ClassA
1 và vá một hàm được cho là sẽ trả về một đối tượng
[inside some_func]someAPI.API_call(foo, bar='baz')[inside test_some_func]some_func()mock_api_call.assert_called_with(foo, bar='baz')
2 thay vì đối tượng
[inside some_func]someAPI.API_call(foo, bar='baz')[inside test_some_func]some_func()mock_api_call.assert_called_with(foo, bar='baz')
0.
[in my_module.py] 
from module import ClassA
2 chúng ta trở lại vẫn sẽ hoạt động giống như tất cả các thuộc tính của đối tượng
[inside some_func]someAPI.API_call(foo, bar='baz')[inside test_some_func]some_func()mock_api_call.assert_called_with(foo, bar='baz')
2, mặc dù chúng ta muốn mô hình hóa một đối tượng
[inside some_func]someAPI.API_call(foo, bar='baz')[inside test_some_func]some_func()mock_api_call.assert_called_with(foo, bar='baz')
0. Điều này có thể dẫn đến các lỗi kiểm tra khó hiểu và hành vi kiểm tra không chính xác.

Giải pháp cho điều này là

[inside some_func]someAPI.API_call(foo, bar='baz')[inside test_some_func]some_func()mock_api_call.assert_called_with(foo, bar='baz')
7
[in my_module.py] 
from module import ClassA
2 khi tạo nó, sử dụng đối số từ khóa
[inside some_func]someAPI.API_call(foo, bar='baz')[inside test_some_func]some_func()mock_api_call.assert_called_with(foo, bar='baz')
7:
import unittestfrom unittest.mock 
import patchclass TestClient(unittest.TestCase):
def setUp(self): 
	self.vars_client = VarsClient()

@patch('pyvars.vars_client.VarsClient.get')
@patch('requests.post')def test_update_retry_works_eventually(self, mock_post, mock_get): 
	mock_get.side_effect = [VarsResponse(),VarsResponse()] 
	mock_post.side_effect = [requests.ConnectionError('Test error'),  
	MagicMock(status_code=200, 
	headers={'content-type':"application/json"}, 
	text=json.dumps({'status':True})) ] 

response = self.vars_client.update('test', '0') 
self.assertEqual(response, response)

@patch('pyvars.vars_client.VarsClient.get')
@patch('requests.post') 

def test_update_retry_works_eventually(self, mock_post, mock_get):
0. Điều này tạo ra một
[in my_module.py] 
from module import ClassA
2 sẽ chỉ cho phép truy cập vào các thuộc tính và phương thức trong lớp mà
[in my_module.py] 
from module import ClassA
2 được chỉ định. Cố gắng truy cập một thuộc tính không có trong đối tượng gốc sẽ tăng
import unittestfrom unittest.mock 
import patchclass TestClient(unittest.TestCase):
def setUp(self): 
	self.vars_client = VarsClient()

@patch('pyvars.vars_client.VarsClient.get')
@patch('requests.post')def test_update_retry_works_eventually(self, mock_post, mock_get): 
	mock_get.side_effect = [VarsResponse(),VarsResponse()] 
	mock_post.side_effect = [requests.ConnectionError('Test error'),  
	MagicMock(status_code=200, 
	headers={'content-type':"application/json"}, 
	text=json.dumps({'status':True})) ] 

response = self.vars_client.update('test', '0') 
self.assertEqual(response, response)

@patch('pyvars.vars_client.VarsClient.get')
@patch('requests.post') 

def test_update_retry_works_eventually(self, mock_post, mock_get):
3, giống như đối tượng thực. Một ví dụ đơn giản là:

m = MagicMock()m.foo() 
#no error raised
# Response objects have a status_code attributem = MagicMock(spec=Response, status_code=200, response=json.dumps({‘key’:’value’}))m.foo() 
#raises AttributeErrorm.status_code #no error raised

side_effect

Đôi khi bạn sẽ muốn kiểm tra rằng chức năng của bạn xử lý chính xác một ngoại lệ hoặc nhiều cuộc gọi của chức năng bạn đang vá được xử lý chính xác. Bạn có thể làm điều đó bằng cách sử dụng

m = MagicMock()m.foo() 
#no error raised
# Response objects have a status_code attributem = MagicMock(spec=Response, status_code=200, response=json.dumps({‘key’:’value’}))m.foo() 
#raises AttributeErrorm.status_code #no error raised
2. Cài đặt
m = MagicMock()m.foo() 
#no error raised
# Response objects have a status_code attributem = MagicMock(spec=Response, status_code=200, response=json.dumps({‘key’:’value’}))m.foo() 
#raises AttributeErrorm.status_code #no error raised
2 thành một ngoại lệ nâng ngoại lệ đó ngay lập tức khi hàm được vá được gọi.

Cài đặt

m = MagicMock()m.foo() 
#no error raised
# Response objects have a status_code attributem = MagicMock(spec=Response, status_code=200, response=json.dumps({‘key’:’value’}))m.foo() 
#raises AttributeErrorm.status_code #no error raised
2 cho một ITEBLEBLE sẽ trả lại mục tiếp theo từ Itable mỗi khi hàm được vá được gọi. Cài đặt
m = MagicMock()m.foo() 
#no error raised
# Response objects have a status_code attributem = MagicMock(spec=Response, status_code=200, response=json.dumps({‘key’:’value’}))m.foo() 
#raises AttributeErrorm.status_code #no error raised
2 cho bất kỳ giá trị nào khác sẽ trả về giá trị đó.

[in test_my_module]
@patch('external_module.api_call')

def test_some_func(self, mock_api_call): 
mock_api_call.side_effect = SomeException() 
my_module.some_func()[in my_module]def some_func(): 
try:  
	external_module.api_call() 

except SomeException:  
	print(“SomeException caught!”) 
	# this code is executed 
	except SomeOtherException:  
	print(“SomeOtherException caught!”) 
	# not executed[in test_my_module]
@patch('external_module.api_call')

def test_some_func(self, mock_api_call): 
	mock_api_call.side_effect = [0, 1] 
	my_module.some_func()[in my_module]

def some_func(): 
	rv0 = external_module.api_call() 
	# rv0 == 0 
	rv1 = external_module.api_call() 
	# rv1 == 1

assert_called_with

import unittestfrom unittest.mock 
import patchclass TestClient(unittest.TestCase):
def setUp(self): 
	self.vars_client = VarsClient()

@patch('pyvars.vars_client.VarsClient.get')
@patch('requests.post')def test_update_retry_works_eventually(self, mock_post, mock_get): 
	mock_get.side_effect = [VarsResponse(),VarsResponse()] 
	mock_post.side_effect = [requests.ConnectionError('Test error'),  
	MagicMock(status_code=200, 
	headers={'content-type':"application/json"}, 
	text=json.dumps({'status':True})) ] 

response = self.vars_client.update('test', '0') 
self.assertEqual(response, response)

@patch('pyvars.vars_client.VarsClient.get')
@patch('requests.post') 

def test_update_retry_works_eventually(self, mock_post, mock_get):
8 khẳng định rằng hàm được vá được gọi với các đối số được chỉ định là đối số cho
import unittestfrom unittest.mock 
import patchclass TestClient(unittest.TestCase):
def setUp(self): 
	self.vars_client = VarsClient()

@patch('pyvars.vars_client.VarsClient.get')
@patch('requests.post')def test_update_retry_works_eventually(self, mock_post, mock_get): 
	mock_get.side_effect = [VarsResponse(),VarsResponse()] 
	mock_post.side_effect = [requests.ConnectionError('Test error'),  
	MagicMock(status_code=200, 
	headers={'content-type':"application/json"}, 
	text=json.dumps({'status':True})) ] 

response = self.vars_client.update('test', '0') 
self.assertEqual(response, response)

@patch('pyvars.vars_client.VarsClient.get')
@patch('requests.post') 

def test_update_retry_works_eventually(self, mock_post, mock_get):
8.

[inside some_func]someAPI.API_call(foo, bar='baz')[inside test_some_func]some_func()mock_api_call.assert_called_with(foo, bar='baz')

Một ví dụ đầy đủ

Trong ví dụ này, tôi đang thử nghiệm hàm

mock_get.side_effect = [ VarsResponse(), VarsResponse()]
mock_post.side_effect = [ requests.ConnectionError('Test error'),' MagicMock(status_code=200, headers={'content-type':"application/json"}, text=json.dumps({'status':True}))]
0 trên
mock_get.side_effect = [ VarsResponse(), VarsResponse()]
mock_post.side_effect = [ requests.ConnectionError('Test error'),' MagicMock(status_code=200, headers={'content-type':"application/json"}, text=json.dumps({'status':True}))]
1. Điều này có nghĩa là các cuộc gọi API trong
mock_get.side_effect = [ VarsResponse(), VarsResponse()]
mock_post.side_effect = [ requests.ConnectionError('Test error'),' MagicMock(status_code=200, headers={'content-type':"application/json"}, text=json.dumps({'status':True}))]
2 sẽ được thực hiện hai lần, đây là thời điểm tuyệt vời để sử dụng
mock_get.side_effect = [ VarsResponse(), VarsResponse()]
mock_post.side_effect = [ requests.ConnectionError('Test error'),' MagicMock(status_code=200, headers={'content-type':"application/json"}, text=json.dumps({'status':True}))]
3.

Mã đầy đủ của ví dụ là ở đây:

import unittestfrom unittest.mock 
import patchclass TestClient(unittest.TestCase):
def setUp(self): 
	self.vars_client = VarsClient()

@patch('pyvars.vars_client.VarsClient.get')
@patch('requests.post')def test_update_retry_works_eventually(self, mock_post, mock_get): 
	mock_get.side_effect = [VarsResponse(),VarsResponse()] 
	mock_post.side_effect = [requests.ConnectionError('Test error'),  
	MagicMock(status_code=200, 
	headers={'content-type':"application/json"}, 
	text=json.dumps({'status':True})) ] 

response = self.vars_client.update('test', '0') 
self.assertEqual(response, response)

@patch('pyvars.vars_client.VarsClient.get')
@patch('requests.post') 

def test_update_retry_works_eventually(self, mock_post, mock_get):

Tôi đang vá hai cuộc gọi trong chức năng được kiểm tra (

mock_get.side_effect = [ VarsResponse(), VarsResponse()]
mock_post.side_effect = [ requests.ConnectionError('Test error'),' MagicMock(status_code=200, headers={'content-type':"application/json"}, text=json.dumps({'status':True}))]
4), một đến
mock_get.side_effect = [ VarsResponse(), VarsResponse()]
mock_post.side_effect = [ requests.ConnectionError('Test error'),' MagicMock(status_code=200, headers={'content-type':"application/json"}, text=json.dumps({'status':True}))]
5 và một đến
mock_get.side_effect = [ VarsResponse(), VarsResponse()]
mock_post.side_effect = [ requests.ConnectionError('Test error'),' MagicMock(status_code=200, headers={'content-type':"application/json"}, text=json.dumps({'status':True}))]
6. Vì tôi đang vá hai cuộc gọi, tôi nhận được hai đối số cho chức năng kiểm tra của mình, mà tôi đã gọi là
mock_get.side_effect = [ VarsResponse(), VarsResponse()]
mock_post.side_effect = [ requests.ConnectionError('Test error'),' MagicMock(status_code=200, headers={'content-type':"application/json"}, text=json.dumps({'status':True}))]
7 và
mock_get.side_effect = [ VarsResponse(), VarsResponse()]
mock_post.side_effect = [ requests.ConnectionError('Test error'),' MagicMock(status_code=200, headers={'content-type':"application/json"}, text=json.dumps({'status':True}))]
8. Đây là cả hai đối tượng
[in my_module.py] 
from module import ClassA
2. Trong trạng thái mặc định của họ, họ không làm được gì nhiều. Chúng ta cần chỉ định một số hành vi phản ứng cho họ.

mock_get.side_effect = [ VarsResponse(), VarsResponse()]
mock_post.side_effect = [ requests.ConnectionError('Test error'),' MagicMock(status_code=200, headers={'content-type':"application/json"}, text=json.dumps({'status':True}))]

Các thử nghiệm này để đảm bảo một cơ sở thử lại cuối cùng hoạt động, vì vậy tôi sẽ gọi cập nhật nhiều lần và thực hiện nhiều cuộc gọi đến

mock_get.side_effect = [ VarsResponse(), VarsResponse()]
mock_post.side_effect = [ requests.ConnectionError('Test error'),' MagicMock(status_code=200, headers={'content-type':"application/json"}, text=json.dumps({'status':True}))]
5 và
mock_get.side_effect = [ VarsResponse(), VarsResponse()]
mock_post.side_effect = [ requests.ConnectionError('Test error'),' MagicMock(status_code=200, headers={'content-type':"application/json"}, text=json.dumps({'status':True}))]
6.

Ở đây tôi đã thiết lập những cái

m = MagicMock()m.foo() 
#no error raised
# Response objects have a status_code attributem = MagicMock(spec=Response, status_code=200, response=json.dumps({‘key’:’value’}))m.foo() 
#raises AttributeErrorm.status_code #no error raised
2 mà tôi muốn. Tôi muốn tất cả các cuộc gọi đến
mock_get.side_effect = [ VarsResponse(), VarsResponse()]
mock_post.side_effect = [ requests.ConnectionError('Test error'),' MagicMock(status_code=200, headers={'content-type':"application/json"}, text=json.dumps({'status':True}))]
5 hoạt động (trả lại một
response = self.vars_client.update('test', '0')self.assertEqual(response, response)
4 trống là tốt cho bài kiểm tra này), cuộc gọi đầu tiên đến
mock_get.side_effect = [ VarsResponse(), VarsResponse()]
mock_post.side_effect = [ requests.ConnectionError('Test error'),' MagicMock(status_code=200, headers={'content-type':"application/json"}, text=json.dumps({'status':True}))]
6 để thất bại với một ngoại lệ và cuộc gọi thứ hai đến
mock_get.side_effect = [ VarsResponse(), VarsResponse()]
mock_post.side_effect = [ requests.ConnectionError('Test error'),' MagicMock(status_code=200, headers={'content-type':"application/json"}, text=json.dumps({'status':True}))]
6 để làm việc. Loại kiểm soát hạt mịn này đối với hành vi chỉ có thể thông qua việc chế giễu.

response = self.vars_client.update('test', '0')self.assertEqual(response, response)

Khi tôi đã thiết lập các

m = MagicMock()m.foo() 
#no error raised
# Response objects have a status_code attributem = MagicMock(spec=Response, status_code=200, response=json.dumps({‘key’:’value’}))m.foo() 
#raises AttributeErrorm.status_code #no error raised
2, phần còn lại của bài kiểm tra là đơn giản. Hành vi này là: cuộc gọi đầu tiên đến
mock_get.side_effect = [ VarsResponse(), VarsResponse()]
mock_post.side_effect = [ requests.ConnectionError('Test error'),' MagicMock(status_code=200, headers={'content-type':"application/json"}, text=json.dumps({'status':True}))]
6 không thành công, vì vậy cơ sở thử lại
response = self.vars_client.update('test', '0')self.assertEqual(response, response)
9 sẽ bị lỗi và mọi thứ sẽ hoạt động lần thứ hai. Hành vi này có thể được xác minh thêm bằng cách kiểm tra lịch sử cuộc gọi của
mock_get.side_effect = [ VarsResponse(), VarsResponse()]
mock_post.side_effect = [ requests.ConnectionError('Test error'),' MagicMock(status_code=200, headers={'content-type':"application/json"}, text=json.dumps({'status':True}))]
8 và
mock_get.side_effect = [ VarsResponse(), VarsResponse()]
mock_post.side_effect = [ requests.ConnectionError('Test error'),' MagicMock(status_code=200, headers={'content-type':"application/json"}, text=json.dumps({'status':True}))]
7.

Sự kết luận

Sử dụng các đối tượng giả một cách chính xác đi ngược lại trực giác của chúng tôi để thực hiện các bài kiểm tra thực tế và kỹ lưỡng nhất có thể, nhưng làm như vậy cho chúng tôi khả năng viết các bài kiểm tra độc lập chạy nhanh, không có sự phụ thuộc. Nó cho chúng ta sức mạnh để kiểm tra các trường hợp xử lý ngoại lệ và cạnh mà không thể kiểm tra. Quan trọng nhất, nó cho chúng tôi tự do tập trung các nỗ lực kiểm tra vào chức năng của mã, thay vì khả năng thiết lập môi trường thử nghiệm. Bằng cách tập trung vào việc kiểm tra những gì quan trọng, chúng tôi có thể cải thiện phạm vi kiểm tra và tăng độ tin cậy của mã của chúng tôi, đó là lý do tại sao chúng tôi kiểm tra ở nơi đầu tiên.

Liên kết tài liệu

https://docs.python.org/3/library/unittest.mock.html

Hướng dẫn how does mocking work in python? - làm thế nào để chế nhạo hoạt động trong python?

Làm thế nào để giả hoạt động?

Một đối tượng mà bạn muốn kiểm tra có thể có sự phụ thuộc vào các đối tượng phức tạp khác. Để cô lập hành vi của đối tượng bạn muốn kiểm tra, bạn thay thế các đối tượng khác bằng các chế giễu mô phỏng hành vi của các đối tượng thực. Vì vậy, trong các từ đơn giản, chế giễu là tạo ra các đối tượng mô phỏng hành vi của các đối tượng thực.creating objects that simulate the behavior of real objects.

Làm thế nào để chế giễu pytest hoạt động?

Trong pytest, chế giễu có thể thay thế giá trị trả về của một hàm trong một hàm.Điều này rất hữu ích để kiểm tra hàm mong muốn và thay thế giá trị trả về của hàm lồng nhau trong hàm mong muốn mà chúng tôi đang thử nghiệm.replace the return value of a function within a function. This is useful for testing the desired function and replacing the return value of a nested function within that desired function we are testing.

Tại sao chế giễu được sử dụng trong thử nghiệm đơn vị?

Việc chế giễu là một cách để thay thế một sự phụ thuộc trong một đơn vị được thử nghiệm với sự phụ thuộc đó cho sự phụ thuộc đó.Đứng trong cho phép đơn vị được kiểm tra được kiểm tra mà không cần gọi sự phụ thuộc thực sự.to replace a dependency in a unit under test with a stand-in for that dependency. The stand-in allows the unit under test to be tested without invoking the real dependency.

Sự khác biệt giữa Mock và Magicmock là gì?

Vậy sự khác biệt giữa họ là gì?MagicMock là một lớp con của Mock.Nó chứa tất cả các phương pháp ma thuật được tạo sẵn và sẵn sàng sử dụng (ví dụ: __str__, __len__, v.v.).Do đó, bạn nên sử dụng MagicMock khi bạn cần Phương pháp ma thuật và chế giễu nếu bạn không cần chúng.MagicMock is a subclass of Mock . It contains all magic methods pre-created and ready to use (e.g. __str__ , __len__ , etc.). Therefore, you should use MagicMock when you need magic methods, and Mock if you don't need them.