Python magic mock meningkatkan pengecualian

Lebih sering daripada tidak, perangkat lunak yang kami tulis berinteraksi langsung dengan apa yang kami beri label sebagai layanan "kotor". Dalam istilah awam. layanan yang sangat penting untuk aplikasi Python kita, tetapi interaksinya memiliki efek samping yang diinginkan tetapi tidak diinginkan—yaitu, tidak diinginkan dalam konteks uji coba mandiri

Membagikan

Membagikan

Python magic mock meningkatkan pengecualian

Lebih sering daripada tidak, perangkat lunak yang kami tulis berinteraksi langsung dengan apa yang kami beri label sebagai layanan "kotor". Dalam istilah awam. layanan yang sangat penting untuk aplikasi Python kita, tetapi interaksinya memiliki efek samping yang diinginkan tetapi tidak diinginkan—yaitu, tidak diinginkan dalam konteks uji coba mandiri

Python magic mock meningkatkan pengecualian

Oleh Naftuli Kay

Pakar Terverifikasi  di bidang Teknik

Dari membangun server TCP khusus hingga aplikasi keuangan berskala besar, pengalaman Naftuli yang luas membuatnya menjadi dev dan sysadmin kelas atas

Saya memiliki fungsi (foo_) yang memanggil fungsi lain (bar). Jika memohon bar() memunculkan HttpError, saya ingin menanganinya secara khusus jika kode statusnya 404, jika tidak, naikkan kembali

Saya mencoba untuk menulis beberapa tes unit di sekitar fungsi foo ini, mengejek panggilan ke bar(). Sayangnya, saya tidak bisa mendapatkan panggilan tiruan ke bar() untuk mengajukan Pengecualian yang ditangkap oleh blok except saya

Ini kode saya yang menggambarkan masalah saya

import unittest
import mock
from apiclient.errors import HttpError


class FooTests(unittest.TestCase):
    @mock.patch('my_tests.bar')
    def test_foo_shouldReturnResultOfBar_whenBarSucceeds(self, barMock):
        barMock.return_value = True
        result = foo()
        self.assertTrue(result)  # passes

    @mock.patch('my_tests.bar')
    def test_foo_shouldReturnNone_whenBarRaiseHttpError404(self, barMock):
        barMock.side_effect = HttpError(mock.Mock(return_value={'status': 404}), 'not found')
        result = foo()
        self.assertIsNone(result)  # fails, test raises HttpError

    @mock.patch('my_tests.bar')
    def test_foo_shouldRaiseHttpError_whenBarRaiseHttpErrorNot404(self, barMock):
        barMock.side_effect = HttpError(mock.Mock(return_value={'status': 500}), 'error')
        with self.assertRaises(HttpError):  # passes
            foo()

def foo():
    try:
        result = bar()
        return result
    except HttpError as error:
        if error.resp.status == 404:
            print '404 - %s' % error.message
            return None
        raise

def bar():
    raise NotImplementedError()

Saya mengikuti yang mengatakan bahwa Anda harus menyetel side_effect dari foo0 instance ke kelas foo1 agar fungsi tiruan meningkatkan kesalahan

Saya juga melihat beberapa Tanya Jawab StackOverflow terkait lainnya, dan sepertinya saya melakukan hal yang sama yang mereka lakukan untuk menyebabkan dan Pengecualian dimunculkan oleh tiruan mereka

  • https. //stackoverflow. com/a/10310532/346561
  • Cara menggunakan Python Mock untuk memunculkan pengecualian – tetapi dengan Errno disetel ke nilai yang diberikan

Mengapa pengaturan side_effect dari foo3 tidak menyebabkan foo1 yang diharapkan dinaikkan?

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

Python magic mock meningkatkan pengecualian

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 mencegat 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 bukan 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 dekorator

[in my_module.py] 
from module import ClassA
_1 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

 

Python magic mock meningkatkan pengecualian

 

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, kami 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 kami 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 asalnya 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 bahwa 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, tes selanjutnya 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

Bagaimana Anda memunculkan pengecualian di Python mock?

Meningkatkan pengecualian dengan tiruan . Jika Anda menyetel ini ke kelas atau instance pengecualian, maka pengecualian akan dimunculkan saat mock dipanggil. >>> mock = Mock(side_effect=Exception('Boom.

Apa yang dilakukan @patch dengan Python?

unittest. mock menyediakan mekanisme yang kuat untuk mengejek objek, yang disebut patch() , yang mencari objek dalam modul tertentu dan mengganti objek itu dengan Mock . Biasanya, Anda menggunakan patch() sebagai dekorator atau pengelola konteks untuk menyediakan ruang lingkup di mana Anda akan mengejek objek target.

Apa perbedaan antara MagicMock dan tiruan Python?

Jadi apa perbedaan antara keduanya? . Ini berisi semua metode ajaib yang dibuat sebelumnya dan siap digunakan (mis. g. __str__ , __len__ , dll. ). Oleh karena itu, Anda harus menggunakan MagicMock saat Anda membutuhkan metode sulap, dan Mock jika Anda tidak membutuhkannya. 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.

Bagaimana Anda menyatakan pengecualian dengan Python?

Pernyataan adalah konsep pemrograman yang digunakan saat menulis kode di mana pengguna menyatakan kondisi benar menggunakan pernyataan tegas sebelum menjalankan modul . Jika kondisinya True, kontrol hanya berpindah ke baris kode berikutnya.