Cara menggunakan python mock(side_effect=exception example)

Mengejek bisa sulit dimengerti. Saat saya menguji kode yang telah saya tulis, saya ingin melihat apakah kode melakukan apa yang seharusnya dilakukan dari ujung ke ujung. Saya biasanya mulai berpikir tentang tes fungsional dan terintegrasi, di mana saya memasukkan input yang realistis dan mendapatkan hasil yang realistis. Saya mengakses setiap sistem nyata yang digunakan kode saya untuk memastikan interaksi antara sistem tersebut berfungsi dengan baik, menggunakan objek nyata dan panggilan API nyata. Meskipun jenis pengujian ini penting untuk memverifikasi bahwa sistem yang kompleks saling bekerja dengan baik, pengujian tersebut bukanlah yang kita inginkan dari pengujian unit

 

Tes unit adalah tentang menguji lapisan terluar dari kode. Pengujian integrasi diperlukan, tetapi pengujian unit otomatis yang kami jalankan tidak boleh mencapai kedalaman interaksi sistem tersebut. Ini berarti bahwa panggilan API apa pun dalam fungsi yang kami uji dapat dan harus ditiru. Kita harus mengganti setiap panggilan API nontrivial atau pembuatan objek dengan panggilan atau objek tiruan. Hal ini memungkinkan kami untuk menghindari penggunaan sumber daya yang tidak perlu, menyederhanakan contoh pengujian kami, dan mengurangi waktu pengoperasiannya. Pikirkan untuk menguji fungsi yang mengakses API HTTP eksternal. Daripada memastikan bahwa server pengujian tersedia untuk mengirimkan tanggapan yang benar, kita dapat meniru pustaka HTTP dan mengganti semua panggilan HTTP dengan panggilan palsu. Hal ini mengurangi kompleksitas dan dependensi pengujian, dan memberi kami kontrol yang tepat atas apa yang dikembalikan pustaka HTTP, yang mungkin sulit dicapai sebaliknya

Cara menggunakan python mock(side_effect=exception example)

Apa yang kita maksud dengan mengejek?

 

Istilah mengejek banyak digunakan, tetapi dokumen ini menggunakan definisi berikut

 

"Penggantian satu atau lebih panggilan fungsi atau objek dengan panggilan atau objek palsu"

 

Panggilan fungsi tiruan mengembalikan nilai yang telah ditentukan dengan segera, tanpa melakukan pekerjaan apa pun. Atribut dan metode objek tiruan didefinisikan sama seluruhnya dalam pengujian, tanpa membuat objek nyata atau melakukan pekerjaan apa pun. Fakta bahwa penulis tes dapat menentukan nilai kembalian dari setiap pemanggilan fungsi memberinya kekuatan yang luar biasa saat menguji, tetapi itu juga berarti bahwa dia perlu melakukan beberapa pekerjaan dasar untuk mengatur semuanya dengan benar.

 

Dalam Python, mengejek dilakukan melalui modul

[in my_module.py] 
from module import ClassA
0. Modul ini berisi sejumlah kelas dan fungsi yang berguna, yang paling penting adalah fungsi
[in my_module.py] 
from module import ClassA
1 (sebagai dekorator dan pengelola konteks) dan kelas
[in my_module.py] 
from module import ClassA
2. Mengejek dengan Python sebagian besar dilakukan melalui penggunaan dua komponen yang kuat ini

 

Apa yang TIDAK kita maksud dengan mengejek?

 

Pengembang menggunakan banyak objek atau modul "tiruan", yang merupakan pengganti lokal yang berfungsi penuh untuk layanan jaringan dan API. Misalnya, pustaka

[in my_module.py] 
from module import ClassA
3 adalah pustaka tiruan
[in my_module.py] 
from module import ClassA
4 yang menangkap semua panggilan API
[in my_module.py] 
from module import ClassA
4 dan memprosesnya secara lokal. Meskipun tiruan ini memungkinkan pengembang untuk menguji API eksternal secara lokal, mereka tetap membutuhkan pembuatan objek nyata. Ini bukan jenis ejekan yang tercakup dalam dokumen ini. Dokumen ini khusus tentang penggunaan objek
[in my_module.py] 
from module import ClassA
2 untuk mengelola sepenuhnya aliran kontrol dari fungsi yang diuji, yang memungkinkan pengujian kegagalan dan penanganan pengecualian dengan mudah

 

Bagaimana kita mengejek dengan Python?

 

Mengejek dengan Python dilakukan dengan menggunakan

[in my_module.py] 
from module import ClassA
1 untuk membajak fungsi API atau panggilan pembuatan objek. Ketika
[in my_module.py] 
from module import ClassA
1 memotong panggilan, ia mengembalikan objek
[in my_module.py] 
from module import ClassA
2 secara default. Dengan menyetel properti pada objek
[in my_module.py] 
from module import ClassA
_2, Anda dapat meniru panggilan API untuk mengembalikan nilai apa pun yang Anda inginkan atau menaikkan
@patch('module.ClassB')
@patch('module.functionA')

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

 

Prosedur keseluruhan adalah sebagai berikut

 

  1. Tulis pengujian seolah-olah Anda menggunakan API eksternal nyata
  2. Dalam fungsi yang diuji, tentukan panggilan API mana yang perlu diejek;
  3. Dalam fungsi pengujian, tambal panggilan API
  4. Siapkan respons objek
    [in my_module.py] 
    from module import ClassA
    2
  5. Jalankan pengujian Anda

 

Jika tes Anda lulus, Anda sudah selesai. Jika tidak, Anda mungkin mengalami kesalahan pada fungsi yang sedang diuji, atau Anda mungkin salah menyiapkan respons

[in my_module.py] 
from module import ClassA
2 Anda. Selanjutnya, kita akan membahas lebih detail tentang alat yang Anda gunakan untuk membuat dan mengonfigurasi tiruan

tambalan

import unittest 
from unittest.mock import patch

 

[in my_module.py] 
from module import ClassA
1 dapat digunakan sebagai dekorator untuk fungsi pengujian, mengambil string yang menamai fungsi yang akan ditambal sebagai argumen. Agar
[in my_module.py] 
from module import ClassA
_1 menemukan fungsi yang akan ditambal, itu harus ditentukan menggunakan nama yang sepenuhnya memenuhi syarat, yang mungkin tidak seperti yang Anda harapkan. Jika kelas diimpor menggunakan pernyataan
@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 menjadi bagian dari namespace modul yang diimpor

 

Misalnya, jika kelas diimpor dalam modul

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

def test_some_func(self, mock_A, mock_B): 
...
8 sebagai berikut

 

[in my_module.py] 
from module import ClassA

 

Itu harus ditambal sebagai

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

def test_some_func(self, mock_A, mock_B): 
...
9, bukan
[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, karena semantik pernyataan
[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, yang mengimpor kelas dan fungsi ke namespace saat ini

 

Biasanya

[in my_module.py] 
from module import ClassA
1 digunakan untuk menambal panggilan API eksternal atau panggilan fungsi intensif waktu atau sumber daya lainnya atau pembuatan objek. Anda seharusnya hanya menambal beberapa panggilan per pengujian. Jika Anda mendapati diri Anda mencoba
[in my_module.py] 
from module import ClassA
_1 lebih dari beberapa kali, pertimbangkan untuk memfaktorkan ulang pengujian Anda atau fungsi yang Anda uji

 

Menggunakan

[in my_module.py] 
from module import ClassA
_1 dekorator akan secara otomatis mengirim argumen posisi ke fungsi yang Anda dekorasi (i. e. , fungsi pengujian Anda). Saat menambal beberapa fungsi, dekorator yang paling dekat dengan fungsi yang sedang didekorasi dipanggil terlebih dahulu, sehingga akan membuat argumen posisi pertama

 

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

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

 

Secara default, argumen ini adalah instance dari

[in my_module.py] 
from module import ClassA
2, yang merupakan objek tiruan default
[in my_module.py] 
from module import ClassA
0. Anda dapat menentukan perilaku fungsi yang ditambal dengan menyetel atribut pada instance
[in my_module.py] 
from module import ClassA
2 yang dikembalikan

 

Cara menggunakan python mock(side_effect=exception example)

 

MagicMock

 

[in my_module.py] 
from module import ClassA
2 objek menyediakan antarmuka tiruan sederhana yang memungkinkan Anda menyetel nilai kembalian atau perilaku lain dari panggilan fungsi atau pembuatan objek yang Anda tambal. Ini memungkinkan Anda untuk sepenuhnya menentukan perilaku panggilan dan menghindari pembuatan objek nyata, yang bisa memberatkan. Misalnya, jika kita menambal panggilan ke
[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, panggilan pustaka HTTP, kita dapat menentukan respons terhadap panggilan tersebut yang akan dikembalikan saat panggilan API dibuat dalam fungsi yang sedang diuji, daripada memastikan bahwa server pengujian

 

Dua atribut terpenting dari instance

[in my_module.py] 
from module import ClassA
2 adalah
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 dan
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, keduanya memungkinkan kita untuk menentukan perilaku pengembalian panggilan yang ditambal

 

return_value

 

Atribut

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 pada instance
[in my_module.py] 
from module import ClassA
2 yang diteruskan ke fungsi pengujian Anda memungkinkan Anda untuk memilih apa yang dikembalikan oleh callable yang ditambal. Dalam kebanyakan kasus, Anda ingin mengembalikan versi tiruan dari apa yang biasanya dikembalikan oleh callable. Ini bisa berupa JSON, iterable, nilai, turunan dari objek respons nyata,
[in my_module.py] 
from module import ClassA
2 berpura-pura menjadi objek respons, atau apa saja. Saat menambal objek, panggilan yang ditambal adalah panggilan pembuatan objek, jadi
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 dari
[in my_module.py] 
from module import ClassA
2 harus berupa objek tiruan, yang bisa jadi
[in my_module.py] 
from module import ClassA
2 lainnya

Jika kode yang Anda uji adalah Pythonic dan melakukan pengetikan bebek daripada pengetikan eksplisit, menggunakan

[in my_module.py] 
from module import ClassA
2 sebagai objek respons bisa nyaman. Daripada bersusah payah membuat instance kelas yang sebenarnya, Anda dapat menentukan pasangan kunci-nilai atribut arbitrer di konstruktor
[in my_module.py] 
from module import ClassA
2 dan mereka akan diterapkan secara otomatis ke instance

 

[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'}))

 

Perhatikan bahwa argumen diteruskan ke

[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, i. e. ,
[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, adalah
[in my_module.py] 
from module import ClassA
2 dan kami menetapkan
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 ke
[in my_module.py] 
from module import ClassA
2 lainnya. Saat mengejek, semuanya adalah
[in my_module.py] 
from module import ClassA
2

 

Menentukan MagicMock

 

Sementara fleksibilitas

[in my_module.py] 
from module import ClassA
_2 nyaman untuk mengejek kelas dengan cepat dengan persyaratan yang rumit, itu juga bisa menjadi kerugian. Secara default,
[in my_module.py] 
from module import ClassA
2 bertindak seolah-olah mereka memiliki atribut apa pun, bahkan atribut yang tidak Anda inginkan untuk mereka miliki. Dalam contoh di atas, kita mengembalikan objek
[in my_module.py] 
from module import ClassA
2 alih-alih objek
[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. Namun, katakanlah kita telah membuat kesalahan dalam panggilan
[in my_module.py] 
from module import ClassA
1 dan menambal fungsi yang seharusnya mengembalikan objek
[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 alih-alih objek
[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 yang kita kembalikan akan tetap bertindak seolah-olah memiliki semua atribut dari objek
[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, meskipun kita bermaksud memodelkan objek
[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. Hal ini dapat menyebabkan kesalahan pengujian yang membingungkan dan perilaku pengujian yang salah

 

Solusi untuk ini adalah dengan

[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 saat membuatnya, menggunakan argumen kata kunci
[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. Ini menciptakan
[in my_module.py] 
from module import ClassA
2 yang hanya akan mengizinkan akses ke atribut dan metode yang ada di kelas tempat
[in my_module.py] 
from module import ClassA
2 ditentukan. Mencoba untuk mengakses atribut yang tidak ada di objek aslinya akan memunculkan
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, seperti halnya objek sebenarnya. Contoh sederhananya adalah

 

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

 

efek samping

 

Terkadang Anda ingin menguji apakah fungsi Anda menangani pengecualian dengan benar, atau apakah beberapa panggilan fungsi yang Anda tambal ditangani dengan benar. Anda dapat melakukannya menggunakan

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. Menyetel
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 ke pengecualian akan segera memunculkan pengecualian itu saat fungsi yang ditambal dipanggil

 

Menyetel

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 ke iterable akan mengembalikan item berikutnya dari iterable setiap kali fungsi yang ditambal dipanggil. Menyetel
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 ke nilai lain akan mengembalikan nilai tersebut

 

[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

 

menegaskan_dipanggil_dengan

 

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 menegaskan bahwa fungsi yang ditambal dipanggil dengan argumen yang ditentukan sebagai argumen untuk
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')

 

Contoh lengkap

 

Dalam contoh ini, saya menguji fungsi

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 pada
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. Ini berarti panggilan API di
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 akan dilakukan dua kali, yang merupakan waktu yang tepat untuk menggunakan
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

 

Kode lengkap dari contoh ada di sini

 

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):

 

Saya menambal dua panggilan dalam fungsi yang sedang diuji (

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), satu ke
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 dan satu ke
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. Karena saya menambal dua panggilan, saya mendapatkan dua argumen untuk fungsi pengujian saya, yang saya panggil
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 dan
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. Keduanya adalah objek
[in my_module.py] 
from module import ClassA
2. Dalam keadaan default, mereka tidak berbuat banyak. Kita perlu menetapkan beberapa perilaku respons kepada mereka

 

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}))]

 

Tes ini untuk memastikan fasilitas coba lagi berfungsi pada akhirnya, jadi saya akan menelepon pembaruan beberapa kali, dan melakukan beberapa panggilan ke

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 dan
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

 

Di sini saya menyiapkan

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 yang saya inginkan. Saya ingin semua panggilan ke
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 berfungsi (mengembalikan
response = self.vars_client.update('test', '0')self.assertEqual(response, response)
4 kosong tidak masalah untuk pengujian ini), panggilan pertama ke
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 gagal dengan pengecualian, dan panggilan kedua ke
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 berfungsi. Kontrol halus atas perilaku semacam ini hanya mungkin dilakukan melalui ejekan

 

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

 

Setelah saya menyiapkan

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
_2s, sisa pengujiannya mudah. Perilaku adalah. panggilan pertama ke
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 gagal, sehingga fasilitas coba lagi membungkus
response = self.vars_client.update('test', '0')self.assertEqual(response, response)
9 harus menangkap kesalahan, dan semuanya harus berfungsi untuk kedua kalinya. Perilaku ini dapat diverifikasi lebih lanjut dengan memeriksa riwayat panggilan
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 dan
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

Kesimpulan

 

Menggunakan objek tiruan dengan benar bertentangan dengan intuisi kita untuk membuat pengujian senyata dan selengkap mungkin, tetapi melakukannya memberi kita kemampuan untuk menulis pengujian mandiri yang berjalan cepat, tanpa ketergantungan. Ini memberi kita kekuatan untuk menguji penanganan pengecualian dan kasus tepi yang tidak mungkin untuk diuji. Yang terpenting, ini memberi kami kebebasan untuk memfokuskan upaya pengujian kami pada fungsionalitas kode kami, daripada kemampuan kami untuk menyiapkan lingkungan pengujian. Dengan berkonsentrasi pada pengujian apa yang penting, kami dapat meningkatkan cakupan pengujian dan meningkatkan keandalan kode kami, itulah sebabnya kami menguji di tempat pertama