Parametrize Pytest

Selesai

Fitur parametris di pytest awalnya mungkin tampak kompleks, tetapi tujuannya mudah setelah Anda memahami masalah yang ditanganinya. Pada dasarnya, parametrize memungkinkan Anda menjalankan fungsi pengujian yang sama dengan input yang berbeda secara efisien, sehingga lebih mudah untuk menjalankan pernyataan terperinci dan bervariasi dengan lebih sedikit kode.

Saat memanggil parametrize, argumen pertama adalah string yang berisi satu atau beberapa nama argumen, misalnya, "test\_input_". Argumen kedua berisi daftar nilai argumen, misalnya, ["27", "6+9", "0", "O"]. Empat argumen terakhir memiliki nilai default dan bersifat opsional.

Anda dapat menemukan referensi API pytest bahasa Inggris untuk parametrize di sini: pytest. Metafunc.parametrize.

Kapan menggunakan parametrisasi

Dua skenario umum di mana Anda mungkin ingin menggunakan parametris meliputi:

  • Saat menguji untuk perulangan
  • Ketika beberapa pengujian menegaskan perilaku yang sama

Mari kita tinjau setiap contoh terlebih dahulu tanpa menggunakan parametrize, dan kemudian dengan itu untuk menunjukkan bagaimana hal itu dapat meningkatkan pengujian kita.

Untuk perulangan

Berikut adalah contoh fungsi pengujian dengan perulangan for:

def test_string_is_digit():
    items = ["1", "10", "33"]
    for item in items:
        assert item.isdigit()

Pengujian ini bermasalah karena jika gagal, tes ini dapat menyebabkan beberapa masalah, termasuk:

  • Laporan pengujian ambigu: Laporan pengujian tidak mengklarifikasi apakah hanya satu item yang gagal atau jika ada beberapa kegagalan.
  • Tampilan pengujian tunggal: Semua item dilihat sebagai satu pengujian, yang mengaburkan performa item individual.
  • Perbaikan yang tidak pasti: Jika kegagalan diperbaiki, tidak ada cara untuk mengetahui apakah semua masalah diselesaikan tanpa menjalankan ulang seluruh pengujian.

Mari kita ubah pengujian untuk secara khusus menyertakan dua item yang diharapkan gagal:

def test_string_is_digit():
    items = ["No", "1", "10", "33", "Yes"]
    for item in items:
        assert item.isdigit()

Menjalankan pengujian hanya menunjukkan satu kegagalan meskipun ada dua item yang tidak valid dalam daftar tersebut:

$ pytest test_items.py
=================================== FAILURES ===================================
_____________________________ test_string_is_digit _____________________________
test_items.py:4: in test_string_is_digit
    assert item.isdigit()
E   AssertionError: assert False
E    +  where False = <built-in method isdigit of str object at 0x103fa1df0>()
E    +    where <built-in method isdigit of str object at 0x103fa1df0> = 'No'.isdigit
=========================== short test summary info ============================
FAILED test_items.py::test_string_is_digit - AssertionError: assert False
============================== 1 failed in 0.01s ===============================

Ini adalah kasus penggunaan yang bagus untuk parametrize. Sebelum kita dapat mempelajari cara memperbarui tes, mari kita jelajahi situasi umum lain yang tidak melibatkan perulangan for.

Pengujian yang menegaskan perilaku yang sama

Sekelompok tes yang membuat pernyataan yang sama juga merupakan kandidat yang baik untuk mem-parametrisasi. Jika pengujian sebelumnya ditulis ulang dengan satu pengujian untuk setiap item, itu akan memungkinkan pelaporan kegagalan yang lebih baik, tetapi akan berulang:

def test_is_digit_1():
    assert "1".isdigit()

def test_is_digit_10():
    assert "10".isdigit()

def test_is_digit_33():
    assert "33".isdigit()

Pengujian ini lebih baik dalam arti bahwa kegagalan dapat dengan mudah dikaitkan dengan satu input. Dan meskipun mungkin tampak tidak biasa untuk memiliki beberapa uji serupa, umum untuk melihat dalam rangkaian pengujian produksi yang berusaha menjadi terperinci.

Meskipun tes akan lebih baik karena mereka dapat melaporkan dengan tepat apa yang gagal (atau lulus) mereka juga datang dengan masalah berikut:

  • Kode berulang, yang menciptakan beban pemeliharaan
  • Ada potensi kesalahan ketik dan kesalahan lainnya saat memperbarui pengujian.
  • Karena berulang, teknisi mungkin tidak menyertakan semua kasus penggunaan dan input

Cara menggunakan parametrize

Sekarang setelah Anda mengetahui beberapa kasus penggunaan parametrize, mari kita perbarui pengujian yang menggunakan perulangan for yang termasuk item-item yang gagal.

Untuk menggunakan parametrize, Anda harus mengimpor pytest sebagai pustaka, lalu menggunakannya sebagai dekorator dalam fungsi. Berikut adalah pengujian yang diperbarui:

import pytest

@pytest.mark.parametrize("item", ["No", "1", "10", "33", "Yes"])
def test_string_is_digit(item):
    assert item.isdigit()

Sebelum menjalankan pengujian, mari kita membahas perubahan.

Dekorator pytest.mark.parametrize() mendefinisikan dua argumen. Argumen pertama adalah string yang disebut "item". String tersebut digunakan sebagai argumen bernama untuk fungsi pengujian yang Anda lihat di baris berikutnya dalam definisi fungsi pengujian. Argumen kedua adalah daftar nilai pengujian.

Pelaporan kesalahan yang rinci

Di balik layar, pytest mempertimbangkan setiap item dalam daftar tersebut sebagai pengujian terpisah. Itu berarti bahwa tes lulus dan gagal dilaporkan secara terpisah. Mari kita lihat apa yang terjadi saat menjalankan pengujian dengan pytest:

$ pytest test_items.py
============================= test session starts ==============================
Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0
rootdir: /private
collected 5 items

test_items.py F...F                                                      [100%]

=================================== FAILURES ===================================
___________________________ test_string_is_digit[No] ___________________________
test_items.py:5: in test_string_is_digit
    assert item.isdigit()
E   AssertionError: assert False
E    +  where False = <built-in method isdigit of str object at 0x102d45e30>()
E    +    where <built-in method isdigit of str object at 0x102d45e30> = 'No'.isdigit
__________________________ test_string_is_digit[Yes] ___________________________
test_items.py:5: in test_string_is_digit
    assert item.isdigit()
E   AssertionError: assert False
E    +  where False = <built-in method isdigit of str object at 0x102d45df0>()
E    +    where <built-in method isdigit of str object at 0x102d45df0> = 'Yes'.isdigit
=========================== short test summary info ============================
FAILED test_items.py::test_string_is_digit[No] - AssertionError: assert False
FAILED test_items.py::test_string_is_digit[Yes] - AssertionError: assert False
========================= 2 failed, 3 passed in 0.07s ==========================

Ada beberapa item penting dalam pelaporan pengujian. Pertama, kita melihat bahwa dari satu pengujian pytest melaporkan lima tes secara total: tiga lulus dan dua gagal. Kegagalan dilaporkan secara terpisah, termasuk apa input yang gagal.

$ pytest test_items.py
___________________________ test_string_is_digit[No] ___________________________
[...]
E    +    where <built-in method isdigit of str object at 0x102d45e30> = 'No'.isdigit
[...]
FAILED test_items.py::test_string_is_digit[No] - AssertionError: assert False

Sulit untuk mengabaikan nilai yang menyebabkan kegagalan dengan begitu banyak tempat di mana hal itu dilaporkan.

Gunakan opsi output verbose

Ketika pengujian dijalankan di baris perintah, laporan pengujian yang muncul ketika pengujian lulus bersifat minimal. Berikut adalah tampilan pengujian setelah pembaruan untuk memperbaiki kegagalan:

@pytest.mark.parametrize("item", ["0", "1", "10", "33", "9"])
def test_string_is_digit(item):
    assert item.isdigit()

Dan menjalankan pengujian menghasilkan output minimal:

$ pytest test_items.py 
============================= test session starts ==============================
Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0
rootdir: /private
collected 5 items

test_items.py .....                                                      [100%]

============================== 5 passed in 0.01s ===============================

Meningkatkan verbositas menunjukkan nilai yang dijalankan pytest untuk setiap pengujian saat menggunakan parametrize:

$ pytest -v test_items.py
============================= test session starts ==============================
Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 
rootdir: /private
collected 5 items

test_items.py::test_string_is_digit[0] PASSED                            [ 20%]
test_items.py::test_string_is_digit[1] PASSED                            [ 40%]
test_items.py::test_string_is_digit[10] PASSED                           [ 60%]
test_items.py::test_string_is_digit[33] PASSED                           [ 80%]
test_items.py::test_string_is_digit[9] PASSED                            [100%]

============================== 5 passed in 0.01s ===============================

Cara menggunakan beberapa nama argumen

Contoh yang telah kita lihat sejauh ini hanya memiliki satu nama argumen dalam argumen pertama. Kami telah menggunakan "item" tetapi Anda dapat menyertakan beberapa nama argumen dalam string yang menentukan argumen pertama yang dipisahkan oleh koma.

Salah satu kasus penggunaan beberapa nama argumen adalah jika Anda ingin meneruskan serangkaian nilai yang diharapkan untuk diuji terhadap nilai input Anda. Dalam argumen kedua Anda, setiap item dalam set Anda harus memiliki kuantitas nilai yang sama dengan jumlah nama input. Misalnya, jika nama input Anda "test\_input, expected\_value", argumen kedua Anda mungkin terlihat seperti ini: [("3+5", 8), ("3*4", 12)]

Pengujian ini memverifikasi apakah objek memiliki atribut menggunakan fungsi hasattr() Python. Ini mengembalikan boolean tergantung pada apakah objek memiliki atribut terkait.

>>> hasattr(dict(), "keys")
True
>>> hasattr("string", "append")
False

Karena hasattr() memerlukan dua argumen, kita dapat menggunakan parametrize dengan cara berikut:

@pytest.mark.parametrize("item, attribute", [("", "format"), (list(), "append")])
def test_attributes(item, attribute):
    assert hasattr(item, attribute)

Dekorator parametrisasi masih menggunakan string tunggal untuk argumen pertama tetapi dengan dua nama argumen yang dipisahkan oleh koma, yang menjadi argumen untuk fungsi pengujian. Dalam hal ini, item dan attribute.

Selanjutnya adalah daftar dua pasang item. Setiap pasangan ini mewakili item dan attribute yang akan diuji.

Saat pytest tidak dapat membuat representasi string dari objek yang dikirimkan, pytest akan membuatnya sendiri. Anda dapat melihat ini saat menjalankan pengujian:

$ pytest -v test_items.py
============================= test session starts ==============================
Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 
rootdir: /private
collected 2 items

test_items.py::test_attributes[-format] PASSED                           [ 50%]
test_items.py::test_attributes[item1-append] PASSED                      [100%]

============================== 2 passed in 0.01s ===============================