Kursus
Seiring proyek Python Anda berkembang dalam kompleksitas dan ukuran, pentingnya pengujian yang andal juga meningkat. Pengujian membantu memastikan kode Anda bekerja sebagaimana mestinya dan menjaga kualitas di seluruh bagian aplikasi.
Saat aplikasi Anda berinteraksi dengan berbagai jenis basis data atau API eksternal seperti OpenAI GPT atau Anthropic Claude, menguji dependensi ini bisa menjadi tantangan. Di sinilah mocking sangat berguna.
Mocking memungkinkan Anda mensimulasikan objek, fungsi, dan perilaku, sehingga pengujian menjadi lebih terisolasi dan dapat diprediksi. Ini juga memungkinkan Anda berfokus pada fungsi spesifik yang ingin diuji dengan mengisolasinya dari dependensi yang dapat menimbulkan kompleksitas atau perilaku tak terduga.
Dalam blog ini, Anda akan mempelajari cara menggunakan pytest-mock, plugin yang kuat untuk pytest, untuk menerapkan mock dalam pengujian Python Anda secara efisien. Pada akhir tutorial ini, Anda akan siap menambahkan teknik mocking ke kasus uji Anda, sehingga menjadi lebih kuat.
Apa itu pytest-mock?
pytest-mock adalah plugin untuk kerangka kerja pengujian Python populer pytest yang memberikan akses mudah ke kemampuan mocking. Ini dibangun di atas unittest bawaan Python, menyederhanakan proses mocking selama pengujian.

logo kerangka kerja pytest
pytest-mock meningkatkan keterbacaan dan memudahkan penerapan mock dalam pengujian dengan pendekatan yang lebih native terhadap pytest. Baik Anda perlu me-mock fungsi individual, metode kelas, atau seluruh objek, pytest-mock menyediakan fleksibilitas yang diperlukan untuk pengujian efektif tanpa kompleksitas tambahan.
Salah satu alasan utama keterbacaan pytest-mock adalah gaya pengujian deklaratifnya, yang memungkinkan Anda menentukan panggilan dan perilaku yang diharapkan langsung dalam tes. Ini membuat kode lebih mudah ditulis, dibaca, dan dipelihara sekaligus tetap tangguh terhadap perubahan implementasi yang tidak mengubah perilaku eksternal kode.
Menyiapkan pytest-mock
Membuat lingkungan virtual untuk proyek Anda adalah praktik terbaik untuk memastikan dependensi terisolasi. Jika Anda belum menyiapkan lingkungan baru, Anda dapat membuatnya dengan conda menggunakan perintah berikut:
# create a conda environment
conda create --name yourenvname python=3.11
# activate conda environment
conda activate yourenvname
Menginstal pytest dan pytest-mock
Instal pytest dan pytest-mock langsung di lingkungan Anda menggunakan pip atau conda:
pip install pytest
pip install pytest-mock
Mengonfirmasi instalasi
Setelah menginstal pytest dan pytest-mock, verifikasi bahwa instalasi berhasil dengan menjalankan perintah berikut:
pytest --version
Dasar-dasar Mocking dengan pytest-mock
Mocking dapat dipahami sebagai membuat versi "dummy" dari sebuah komponen yang meniru perilaku nyata.
Ini sangat membantu ketika Anda perlu menguji bagaimana fungsi atau kelas berinteraksi dengan komponen eksternal seperti basis data atau API eksternal.
Pengantar mocking
Sebuah objek mock mensimulasikan perilaku objek nyata. Ini sering digunakan dalam unit test untuk mengisolasi komponen dan menguji perilakunya tanpa mengeksekusi kode dependen.
Sebagai contoh, Anda mungkin menggunakan objek mock untuk mensimulasikan panggilan basis data tanpa benar-benar terhubung ke basis data.
Mocking sangat berguna ketika objek asli:
- Sulit dibuat atau dikonfigurasi.
- Memakan waktu untuk digunakan (misalnya, mengakses basis data jarak jauh).
- Memiliki efek samping yang harus dihindari selama pengujian (misalnya, mengirim email, menimbulkan biaya).
Menggunakan fixture mocker
pytest-mock menyediakan fixture mocker, yang memudahkan pembuatan dan pengendalian objek mock. Fixture mocker sangat kuat dan terintegrasi ke dalam pengujian pytest Anda.
💡Apa itu fixture?Dalam Python, fixture adalah komponen yang dapat digunakan kembali untuk menyiapkan dan membersihkan sumber daya yang dibutuhkan pengujian, seperti basis data atau berkas. Fixture memastikan konsistensi, mengurangi duplikasi, dan menyederhanakan penyiapan pengujian. |
Mari kita lihat contohnya:
def fetch_weather_data(api_client):
response = api_client.get("https://api.weather.com/data")
if response.status_code == 200:
return response.json()
else:
return None
Fungsi fetch_weather_data bergantung pada API cuaca eksternal untuk mengambil data, namun memanggil API saat pengujian bisa menimbulkan biaya yang tidak perlu.
Untuk menghindari hal ini, Anda dapat menggunakan mocking untuk mensimulasikan perilaku API dalam pengujian, memastikan aplikasi diuji tanpa melakukan panggilan eksternal. Berikut cara mocking membantu:
def test_fetch_weather_data(mocker):
# Create a mock API client
mock_api_client = mocker.Mock()
# Mock the response of the API client
mock_response = mocker.Mock()
mock_response.status_code = 200
mock_response.json.return_value = {"temperature": "20C", "condition": "Sunny"}
# Set the mock API client to return the mocked response
mock_api_client.get.return_value = mock_response
# Call the function with the mock API client
result = fetch_weather_data(mock_api_client)
# Assert that the correct data is returned
assert result == {"temperature": "20C", "condition": "Sunny"}
mock_api_client.get.assert_called_once_with("https://api.weather.com/data")
Dalam test_fetch_weather_data(), kita menggunakan fixture mocker untuk membuat klien API mock mock_api_client dan mock_response. Respons mock dikonfigurasi untuk meniru panggilan API nyata dengan mengembalikan status kode 200 dan data JSON yang berisi informasi cuaca.
Metode get dari klien API mock diatur untuk mengembalikan respons mock ini. Saat fetch_weather_data() diuji, fungsi berinteraksi dengan klien API mock alih-alih melakukan panggilan API sebenarnya.
Pendekatan ini memastikan fungsi berperilaku sesuai harapan sekaligus menjaga pengujian tetap cepat, hemat biaya, dan independen dari sistem eksternal.
Pernyataan assert di akhir memverifikasi bahwa data yang dikembalikan sesuai dengan keluaran yang diharapkan.
Baris terakhir, mock_api_client.get.assert_called_once_with("https://api.weather.com/data"), secara khusus memeriksa bahwa metode get dari klien API mock dipanggil tepat satu kali dan dengan argumen yang benar. Ini membantu memastikan bahwa fungsi berinteraksi dengan API dengan cara yang diharapkan, menambahkan lapisan validasi tambahan pada pengujian.
Kasus Penggunaan Umum untuk pytest-mock
Mocking berguna dalam berbagai skenario, termasuk unit test di mana Anda perlu mengisolasi fungsi atau komponen tertentu. Mari lihat beberapa kasus penggunaan umum:
1. Mocking fungsi atau metode kelas
Anda mungkin perlu menguji sebuah fungsi yang bergantung pada fungsi atau metode lain, tetapi Anda ingin mengendalikan perilakunya selama pengujian. Mocking memungkinkan Anda mengganti dependensi tersebut dengan perilaku yang telah ditentukan:
# production code
def calculate_discount(price, discount_provider):
discount = discount_provider.get_discount()
return price - (price * discount / 100)
# test code
def test_calculate_discount(mocker):
# Mock the get_discount method
mock_discount_provider = mocker.Mock()
mock_discount_provider.get_discount.return_value = 10 # Mocked discount value
# Call the function with the mocked dependency
result = calculate_discount(100, mock_discount_provider)
# Assert the calculated discount is correct
assert result == 90
mock_discount_provider.get_discount.assert_called_once()
Dalam pengujian ini, metode get_discount() dari discount_provider di-mock untuk mengembalikan nilai yang telah ditentukan (10%). Ini mengisolasi fungsi calculate_discount() dari implementasi sebenarnya dari discount_provider.
2. Mocking kode yang bergantung pada waktu
Saat menguji fungsi yang melibatkan waktu atau penundaan, me-mock metode terkait waktu membantu menghindari penantian atau manipulasi waktu nyata:
# production code
import time
def long_running_task():
time.sleep(5) # Simulate a long delay
return "Task Complete"
# test code
def test_long_running_task(mocker):
# Mock the sleep function in the time module
mocker.patch("time.sleep", return_value=None)
# Call the function
result = long_running_task()
# Assert the result is correct
assert result == "Task Complete"
Di sini, time.sleep dipatch untuk melewati penundaan sebenarnya selama pengujian. Ini memastikan pengujian berjalan cepat tanpa menunggu 5 detik.
3. Mocking atribut objek
Terkadang, Anda mungkin ingin me-mock perilaku atribut atau properti sebuah objek untuk mensimulasikan kondisi yang berbeda selama pengujian:
# production code
class User:
def __init__(self, name, age):
self.name = name
self.age = age
@property
def is_adult(self):
return self.age >= 18
# test code
def test_user_is_adult(mocker):
# Create a User object
user = User(name="Alice", age=17)
# Mock the is_adult property
mocker.patch.object(User, "is_adult", new_callable=mocker.PropertyMock, return_value=True)
# Assert the mocked property value
assert user.is_adult is True
Dalam contoh ini, properti is_adult() dari kelas User di-mock untuk mengembalikan True, terlepas dari usia sebenarnya. Ini berguna untuk menguji skenario yang bergantung pada status objek.
Teknik Mocking Lanjutan dengan pytest-mock
Setelah Anda nyaman dengan mocking dasar, pytest-mock menawarkan kemampuan lanjutan untuk menangani skenario yang lebih kompleks.
1. Mock side effect
Mock tidak hanya dapat mensimulasikan nilai kembalian—mock juga dapat meniru perilaku seperti melempar pengecualian atau mengubah nilai kembalian secara dinamis. Ini dicapai dengan menambahkan “side effect” pada mock:
import pytest
# production code
def process_payment(payment_gateway, amount):
response = payment_gateway.charge(amount)
if response == "Success":
return "Payment processed successfully"
else:
raise ValueError("Payment failed")
# test code
def test_process_payment_with_side_effects(mocker):
# Mock the charge method of the payment gateway
mock_payment_gateway = mocker.Mock()
# Add side effects: Success on first call, raise exception on second call
mock_payment_gateway.charge.side_effect = ["Success", ValueError("Insufficient funds")]
# Test successful payment
assert process_payment(mock_payment_gateway, 100) == "Payment processed successfully"
# Test payment failure
with pytest.raises(ValueError, match="Insufficient funds"):
process_payment(mock_payment_gateway, 200)
# Verify the mock's behavior
assert mock_payment_gateway.charge.call_count == 2
Properti side_effect memungkinkan mock mengembalikan nilai berbeda atau melempar pengecualian pada pemanggilan berikutnya. Dalam contoh ini, panggilan pertama ke charge mengembalikan "Success", sementara panggilan kedua melempar ValueError. Ini berguna untuk menguji berbagai skenario dalam satu pengujian.
2. Melakukan spying pada fungsi
Spying memungkinkan Anda melacak bagaimana fungsi nyata dipanggil, termasuk jumlah panggilan dan argumen yang diteruskan. Ini sangat berguna saat Anda ingin memastikan sebuah fungsi dipanggil seperti yang diharapkan sambil tetap mengeksekusi logika aslinya:
# production code
def log_message(logger, message):
logger.info(message)
return f"Logged: {message}"
# test code
def test_log_message_with_spy(mocker):
# Spy on the info method of the logger
mock_logger = mocker.Mock()
spy_info = mocker.spy(mock_logger, "info")
# Call the function
result = log_message(mock_logger, "Test message")
# Assert the returned value
assert result == "Logged: Test message"
# Verify the spy behavior
spy_info.assert_called_once_with("Test message")
assert spy_info.call_count == 1
Metode spy() melacak bagaimana sebuah metode nyata dipanggil tanpa mengubah perilakunya. Di sini, kita melakukan spying pada metode info dari logger untuk memastikan metode tersebut dipanggil dengan pesan yang benar sambil tetap membiarkan metode dijalankan secara normal.
Praktik Terbaik Menggunakan pytest-mock
Mocking sangat kuat, tetapi penggunaan yang ceroboh dapat menimbulkan masalah dalam pengujian Anda. Berikut beberapa praktik terbaik yang saya sarankan untuk diikuti:
1. Hindari over-mocking
Menggunakan terlalu banyak mock dapat menghasilkan pengujian yang rapuh yang rusak saat kode internal berubah, bahkan jika perilaku eksternal tidak berubah. Usahakan hanya me-mock yang diperlukan dan andalkan implementasi nyata kapan pun memungkinkan.
Over-mocking dapat membuat pengujian Anda sangat bergantung pada detail implementasi kode, yang berarti bahkan refactoring kecil pun dapat menyebabkan pengujian gagal tanpa alasan. Sebaliknya, fokuslah me-mock hanya bagian sistem yang bersifat eksternal atau memiliki efek samping signifikan.
2. Gunakan konvensi penamaan yang jelas
Saat me-mock objek, gunakan nama yang deskriptif untuk mock Anda. Ini membuat pengujian lebih mudah dibaca dan dipelihara. Misalnya, alih-alih menggunakan nama generik seperti mock_function(), gunakan sesuatu yang lebih deskriptif, seperti mock_api_response().
Konvensi penamaan yang jelas membantu orang lain memahami tujuan tiap mock, mengurangi kebingungan dan memudahkan pemeliharaan rangkaian pengujian dari waktu ke waktu.
3. Jaga pengujian tetap sederhana dan terfokus
Setiap pengujian harus fokus pada satu perilaku atau skenario. Ini menyederhanakan debug dan membuat pengujian Anda mudah dipahami. Pengujian yang terfokus dengan baik lebih mungkin memberikan umpan balik yang jelas saat ada masalah, sehingga memudahkan identifikasi dan perbaikan.
Kesimpulan
Dalam blog ini, kita membahas dasar-dasar mocking dengan pytest-mock dan mempelajari cara menggunakannya untuk meningkatkan pengujian Python. Kita mencakup segala hal mulai dari mocking dasar atas fungsi dan metode hingga teknik lebih lanjut seperti menambahkan side effect dan melakukan spying pada fungsi.
Mocking adalah alat penting untuk membuat pengujian yang andal dan mudah dipelihara, terutama saat bekerja dengan sistem kompleks atau dependensi eksternal. Dengan memasukkan pytest-mock ke dalam proyek Anda, Anda dapat menulis pengujian yang lebih terisolasi, dapat diprediksi, dan lebih mudah dipelihara.
Untuk terus mengembangkan keterampilan pengujian Anda, saya sarankan mengecek kursus gratis Introduction to Testing in Python di Datacamp.
FAQs
Apa perbedaan mocking dengan menguji komponen nyata?
Mocking menggantikan komponen nyata dengan yang disimulasikan, memungkinkan pengujian yang terisolasi dan hemat biaya tanpa bergantung pada sistem eksternal atau data nyata.
Bisakah saya menggunakan pytest-mock dengan kerangka kerja pengujian lain?
pytest-mock dirancang khusus untuk pytest dan terintegrasi mulus dengannya, namun dibangun di atas unittest.mock Python, yang dapat digunakan secara mandiri.
Apakah fixture wajib untuk mocking di pytest-mock?
Meskipun fixture seperti mocker meningkatkan efisiensi mocking, Anda tetap dapat menggunakan unittest.mock langsung di pytest tanpa fixture, meski dengan integrasi yang lebih rendah.

