Pytest parametrizasyonu
Pytest'teki parametri özelliği başlangıçta karmaşık görünebilir, ancak sorunu anladıktan sonra amacı basittir. Temel olarak, parametri aynı test işlevini farklı girişlerle verimli bir şekilde çalıştırmanıza olanak tanır ve daha az kodla ayrıntılı ve çeşitli onayları çalıştırmayı kolaylaştırır.
parametrisiniçağırırken, ilk bağımsız değişken bir veya daha fazla bağımsız değişken adı içeren bir dizedir, örneğin, "test\_input_". İkinci bağımsız değişken, örneğin ["27", "6+9", "0", "O"]bağımsız değişken değerlerinin listesini içerir. Son dört bağımsız değişken varsayılan değerlere sahiptir ve isteğe bağlıdır.
parametrize için İngilizce pytest API başvurusunu burada bulabilirsiniz: pytest. Metafunc.parametrize.
Parametrize ne zaman kullanılır?
parametriyi kullanmak isteyebileceğiniz iki yaygın senaryo şunlardır:
- döngüleri söz konusu olduğunda test edilirken
- Birden çok test aynı davranışı onayladığında
Önce parametre olmadan her örneği gözden geçirelim ve ardından testlerimizi onunla nasıl iyileştirebileceğimizi göstermek içinkullanarak inceleyelim.
For döngüleri
aşağıda for döngüsüne sahip bir test işlevi örneği verilmişti:
def test_string_is_digit():
items = ["1", "10", "33"]
for item in items:
assert item.isdigit()
Bu test sorunludur çünkü başarısız olursa aşağıdakiler gibi çeşitli sorunlara yol açabilir:
- Belirsiz test raporları: Test raporu yalnızca bir öğenin başarısız olup olmadığını veya birden çok hata olup olmadığını netleştirmez.
- Tek test görünümü: Tüm öğeler, tek tek öğe performansını gizleyen tek bir test olarak görülür.
- Belirsiz düzeltmeler: Bir hata düzeltilirse tüm sorunların testin tamamı yeniden çalıştırılmadan çözülmüş olup olmadığını öğrenmenin bir yolu yoktur.
Testi özellikle başarısız olması beklenen iki öğe içerecek şekilde değiştirelim:
def test_string_is_digit():
items = ["No", "1", "10", "33", "Yes"]
for item in items:
assert item.isdigit()
Test çalıştırıldığında, bu listede iki geçersiz öğe olmasına rağmen yalnızca bir hata gösterilir:
$ 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 ===============================
Bu, 'ı'e parametrize etmek için harika bir kullanım örneğidir. Testin nasıl güncelleştirildiğini göremeden önce, for döngüleri içermeyen başka bir yaygın durumu inceleyelim.
Aynı davranışı onaylayan testler
Aynı sonuca ulaşan bir grup test de parametrizeiçin iyi bir adaydır. Önceki test her öğe için bir testle yeniden yazıldıysa, daha iyi hata raporlamasına olanak sağlar, ancak tekrarlanabilir:
def test_is_digit_1():
assert "1".isdigit()
def test_is_digit_10():
assert "10".isdigit()
def test_is_digit_33():
assert "33".isdigit()
Bu testler, bir hatanın tek bir girişle kolayca ilişkilendirilebileceği için daha iyidir. Benzer birkaç teste sahip olmak alışılmışın dışında görünse de, ayrıntılı olmaya çalışan üretim test paketlerinde görmek yaygın bir durum.
Tam olarak nelerin başarısız olduğunu (veya geçtiğini) bildirebildiği için testler daha iyi olsa da, aşağıdaki sorunlarla birlikte gelir:
- Kod tekrarlanır ve bu da bakım yükü oluşturur
- Testleri güncelleştirirken yazım hataları ve hatalar olabilir
- Tekrarlayıcı oldukları için, mühendisler tüm kullanım örneklerini ve girişleri içermeyebilir.
Parametrize nasıl kullanılır
artık parametrizekullanım örneklerinden bazılarından haberdar olduğunuza göre, başarısız öğeleri içeren bir for döngüsü kullanan testi güncelleyelim.
'ıüzerinde parametrelendirmek için, pytest'yi bir kütüphane olarak içe aktarmanız ve ardından işlevde dekoratör olarak kullanmanız gerekir. Güncelleştirilmiş test şu şekildedir:
import pytest
@pytest.mark.parametrize("item", ["No", "1", "10", "33", "Yes"])
def test_string_is_digit(item):
assert item.isdigit()
Testleri çalıştırmadan önce değişikliklerin üzerinden geçelim.
pytest.mark.parametrize() dekoratörü iki parametre tanımlar. İlk bağımsız değişken, "item"adlı bir dizedir. Bu dize, test işlevi tanımının sonraki satırında gördüğünüz test işlevi için adlı bağımsız değişken olarak kullanılır. İkinci bağımsız değişken, test değerlerinin listesidir.
Zengin hata raporlama
Pytest, arka planda bu listedeki her öğeyi ayrı bir testolarak değerlendirir. Bu, başarılı ve başarısız olan testlerin ayrı olarak bildirildiği anlamına gelir. Şimdi testi pytestile çalıştırırken ne olacağını görelim:
$ 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 ==========================
Test raporlamasında birkaç önemli öğe vardır. İlk olarak, bir pytest testinden toplamda beş test bildirdiğini görüyoruz: üçü başarılı ve ikisi başarısız. Hatalar, başarısız girişin ne olduğu da dahil olmak üzere ayrı olarak bildirilir.
$ 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
Hatanın bildirildiği birçok yerde hataya neden olan değeri kaçırmak zordur.
Ayrıntılı çıkış bayrağını kullan
Testler komut satırında çalıştırıldığında, testler geçtiğinde, test raporlaması minimaldir. Hataları düzeltmek için bir güncelleştirmeden sonra test şu şekilde görünür:
@pytest.mark.parametrize("item", ["0", "1", "10", "33", "9"])
def test_string_is_digit(item):
assert item.isdigit()
Testleri çalıştırmak da minimum çıkış üretir:
$ 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 ===============================
Ayrıntı düzeyini artırmak, parametrizekullanıldığında pytest'in her test için çalıştırdığı değerleri gösterir.
$ 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 ===============================
Birden çok bağımsız değişken adı kullanma
Şimdiye kadar gördüğümüz örneklerde, ilk argümanda sadece bir bağımsız değişken adı vardır.
"item" kullanıyoruz, ancak dizeye virgülle ayrılmış ilk bağımsız değişkeni belirten birden çok bağımsız değişken adı ekleyebilirsiniz.
Birden çok bağımsız değişken adı kullanmanın bir diğer örneği, giriş değerinizi sınamak için beklenen değerlerin bir setini iletmek istemenizdir. İkinci bağımsız değişkeninizde, kümenizdeki her öğenin giriş adı sayısına eşit değer miktarına sahip olması gerekir. Örneğin, giriş adlarınız "test\_input, expected\_value"ise ikinci bağımsız değişkeniniz şöyle görünebilir: [("3+5", 8), ("3*4", 12)]
Bu test, Python hasattr() işlevini kullanarak bir nesnenin özniteliği olup olmadığını doğrular. Nesnenin ilişkili özniteliğe sahip olup olmadığına bağlı olarak bir boole döndürür.
>>> hasattr(dict(), "keys")
True
>>> hasattr("string", "append")
False
hasattr() iki bağımsız değişken gerektirdiğinden, parametresiyle'yi aşağıdaki şekilde kullanabiliriz:
@pytest.mark.parametrize("item, attribute", [("", "format"), (list(), "append")])
def test_attributes(item, attribute):
assert hasattr(item, attribute)
parametrize dekoratör hala ilk bağımsız değişken için tek bir dize kullanır, ancak iki bağımsız değişken adı virgülle ayrılmıştır ve bu da test işlevinin bağımsız değişkenleri haline gelir. Bu durumda, item ve attribute.
Sonraki, iki öğe çiftinin listesidir. Bu çiftlerin her biri, test edilecek bir item ve bir attribute'i temsil eder.
Pytest geçirilen nesnelerin dize gösterimini oluşturamayınca bir tane oluşturur. Testi çalıştırırken bunu görebilirsiniz:
$ 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 ===============================