अध्याय 07

Fixture — टेस्ट की तैयारी एक जगह

एक ही setup बार-बार न लिखें: @pytest.fixture से एक बार लिखें, नाम से माँगें, हर टेस्ट में नई कॉपी पाएँ, और yield से सफ़ाई करें — टेस्ट fail होने पर भी।

45 मिनटPython 3.12
  1. 1समस्या
  2. 2समझें
  3. 3उदाहरण
  4. 4अनुमान
  5. 5स्वयं करें
  6. 6चुनौती

वह समस्या जिसे हम हल कर रहे हैं

यह रहा एक छोटा-सा शॉपिंग कार्ट, cart.py:

python
class Cart:
    def __init__(self):
        self.lines = []

    def add(self, name, price, quantity=1):
        if quantity < 1:
            raise ValueError(f"quantity must be at least 1: {quantity}")
        self.lines.append((name, price, quantity))

    def total(self):
        return sum(price * quantity for _, price, quantity in self.lines)

    def count(self):
        return sum(quantity for _, _, quantity in self.lines)

और इसके लिए तीन test, test_cart.py:

python
from cart import Cart


def test_total():
    cart = Cart()
    cart.add("pen", 15.0, 3)
    cart.add("bag", 850.0)
    assert cart.total() == 895.0


def test_count():
    cart = Cart()
    cart.add("pen", 15.0, 3)
    cart.add("bag", 850.0)
    assert cart.count() == 4


def test_add_one_more():
    cart = Cart()
    cart.add("pen", 15.0, 3)
    cart.add("bag", 850.0)
    cart.add("notebook", 60.0, 2)
    assert cart.total() == 1015.0
text
pytest -q
...                                                                      [100%]
3 passed in 0.01s

ये पास हो जाते हैं, लेकिन इन्हें पढ़ना थकाऊ है। तीन test, setup की नौ लाइनें, और सिर्फ़ तीन लाइनें जो वास्तव में कुछ जाँचती हैं। setup हर test में एक जैसा है, इसलिए वह एक लाइन जो अलग है — वही लाइन जिसके लिए test मौजूद है — उसके नीचे दब जाती है।

इसकी क़ीमत आगे चलकर भी चुकानी पड़ती है। जब Cart() को किसी argument की ज़रूरत पड़ने लगेगी, तो आपको उसे तीन जगह बदलना होगा, या तीस जगह। पिछले अध्याय में parametrize ने test के inputs में दोहराव को हटाया था। यह अध्याय उसके setup में दोहराव को हटाता है।

इस अध्याय के अंत में आप कर पाएंगे

  • @pytest.fixture से एक fixture लिखना और उसे test के parameter के रूप में नाम देकर माँगना
  • यह समझाना कि हर test को अपनी नई कॉपी क्यों मिलती है, और एक लिस्ट से इसे दिखाना
  • एक fixture को दूसरे fixture से बनाना
  • एक yield fixture लिखना जो अपने बाद सफ़ाई करे, और setup व teardown के क्रम का अनुमान लगाना
  • --setup-show से fixtures को बनते हुए देखना
  • fixture 'x' not found और "called directly" एरर को पढ़ना
  • fixture और एक साधारण helper function के बीच चुनाव करना

ज़रूरी शर्तें: `parametrize` से एक test को कई cases पर चलाना।


टेस्ट लिखने से पहले

fixtures ऐसी चीज़ नहीं हैं जिन्हें आप बाद में tests में जोड़ते हैं। वे एक योजना से अपने-आप निकलते हैं, और योजना पहले आती है।

क्या वादा किया गया है। Cart तीन चीज़ों का वादा करता है: add() एक line दर्ज करता है और 1 से कम quantity को ValueError के साथ ठुकरा देता है; total() price × quantity का योग है; count() quantities का योग है। कोई फ़ाइल नहीं, कोई network नहीं, कोई global state नहीं — अभी तक ऐसा कुछ नहीं जिसे पलटने की ज़रूरत हो।

पहले से क्या मौजूद होना चाहिए। वही जो हर अध्याय में: एक virtual environment जिसमें pytest इंस्टॉल हो, और cart.py उस फ़ोल्डर से import हो सके जिसमें आप pytest चलाते हैं (test फ़ाइल को उसी के बगल में रखें)।

कौन-से cases। उन्हें लिख लें, और एक ऐसा कॉलम जोड़ें जिसे लोग अक्सर छोड़ देते हैं — वह स्थिति जिससे test शुरू होता है:

| Case | शुरुआती स्थिति | क्रिया | अपेक्षित | |---|---|---|---| | सामान्य total | pen × 3, bag × 1 | total() | 895.0 | | आइटम की गिनती | pen × 3, bag × 1 | count() | 4 | | एक और line | pen × 3, bag × 1 | add("notebook", 60.0, 2) | total() का मान 1015.0 | | किनारे का case: कार्ट ख़ाली | ख़ाली कार्ट | total() | 0 | | अमान्य quantity | ख़ाली कार्ट | add("pen", 15.0, 0) | ValueError |

दूसरे कॉलम को ऊपर से नीचे पढ़ें। तीन rows एक ही भरे हुए कार्ट से शुरू होती हैं, दो एक ख़ाली कार्ट से। उस कॉलम में दोहराई गई हर वैल्यू एक fixture है जो लिखे जाने का इंतज़ार कर रहा है — यहाँ, empty_cart और उसके ऊपर बना एक cart। और जो row ऐसी स्थिति से शुरू होती है जिसे कोई और साझा नहीं करता, उसे fixture की ज़रूरत ही नहीं; वह अपनी स्थिति खुद बनाती है।

हर शुरुआती स्थिति से एक और सवाल पूछें: क्या इसे बनाने से test के बाहर कुछ बदलता है? एक नया Cart कुछ नहीं बदलता। किसी global डिक्शनरी में रजिस्टर किया गया discount code, खोला गया connection, लिखी गई फ़ाइल — ये बदलते हैं, और यही आपको बताता है कि fixture को सफ़ाई की ज़रूरत है (yield, इस अध्याय में आगे)।

क्या test नहीं करना है। ऐसे tests न लिखें जो साबित करें कि pytest हर test के लिए नया fixture बनाता है; यह pytest का वादा है, और यह अध्याय इसे एक बार दिखाता है ताकि आप उस पर भरोसा कर सकें। लेकिन अपनी सफ़ाई को ज़रूर test करें जब वह global state को वापस ठीक करती है — वह कोड आपका है, और वह ग़लत हो सकता है।

बाकी अध्याय इस योजना को कोड में बदलता है।

पहला fixture

setup को एक function में ले जाएँ, और उसके ऊपर @pytest.fixture लगाएँ:

python
import pytest

from cart import Cart


@pytest.fixture
def cart():
    c = Cart()
    c.add("pen", 15.0, 3)
    c.add("bag", 850.0)
    return c


def test_total(cart):
    assert cart.total() == 895.0


def test_count(cart):
    assert cart.count() == 4


def test_add_one_more(cart):
    cart.add("notebook", 60.0, 2)
    assert cart.total() == 1015.0
text
pytest -q
...                                                                      [100%]
3 passed in 0.01s

tests में कहीं भी cart() को call नहीं किया गया है। हर test में बस cart नाम का एक parameter है, और पूरी माँग बस इतनी ही है।

किसी test को चलाने से पहले pytest उसके parameters के नाम देखता है। हर नाम के लिए वह उसी नाम का fixture ढूँढता है, उसे चलाता है, और जो कुछ वह लौटाता है उसे argument के रूप में पास कर देता है। test बताता है कि उसे क्या चाहिए; वह उसे खुद नहीं बनाता। इस व्यवस्था का एक नाम है — dependency injection — लेकिन इसका तंत्र इससे ज़्यादा कुछ नहीं कि "parameter के नाम को fixture के नाम से मिलाओ"।

अब test एक लाइन में अपना इरादा बताता है। और setup एक ही जगह रहता है, इसलिए Cart() में बदलाव का मतलब सिर्फ़ एक function में बदलाव है।

हर test को एक नया fixture मिलता है

test_add_one_more कार्ट में एक notebook जोड़ता है। क्या उसके बाद चलने वाले tests को ऐसा कार्ट दिखा जिसमें notebook था? नहीं — और इसे साफ़ तौर पर देखना ज़रूरी है, क्योंकि यही वह गुण है जो fixtures को सुरक्षित बनाता है।

पहले, बिना fixture वाला संस्करण। module के ऊपर एक लिस्ट, जिसे दो tests साझा करते हैं:

python
ITEMS = ["pen", "bag"]


def test_add_item():
    ITEMS.append("notebook")
    assert len(ITEMS) == 3


def test_starts_with_two():
    assert len(ITEMS) == 2
text
pytest -q
.F                                                                       [100%]
=================================== FAILURES ===================================
_____________________________ test_starts_with_two _____________________________

    def test_starts_with_two():
>       assert len(ITEMS) == 2
E       AssertionError: assert 3 == 2
E        +  where 3 = len(['pen', 'bag', 'notebook'])

test_shared.py:10: AssertionError
=========================== short test summary info ============================
FAILED test_shared.py::test_starts_with_two - AssertionError: assert 3 == 2
1 failed, 1 passed in 0.01s

दूसरा test सही है; वह इसलिए फ़ेल हुआ क्योंकि पहले test ने कुछ किया था। उसे अकेले चलाएँ तो वह पास हो जाता है:

text
pytest -q test_shared.py::test_starts_with_two
.                                                                        [100%]
1 passed in 0.01s

ऐसा test जिसका परिणाम इस पर निर्भर करे कि उससे पहले कौन-से tests चले, किसी test suite के सबसे महँगे bugs में से एक है, क्योंकि वह सिर्फ़ कभी-कभी दिखाई देता है।

अब वही दो tests, लेकिन लिस्ट एक fixture से आ रही है। fixture के अंदर एक print दिखाता है कि वह कब चलता है; -s pytest को बताता है कि प्रिंट किया गया आउटपुट capture न करे, ताकि हम उसे देख सकें:

python
import pytest


@pytest.fixture
def items():
    print("building a new list")
    return ["pen", "bag"]


def test_add_item(items):
    items.append("notebook")
    assert len(items) == 3


def test_starts_with_two(items):
    assert len(items) == 2
text
pytest -q -s
building a new list
.building a new list
.
2 passed in 0.01s

"building a new list" दो बार दिखाई देता है। fixture function हर उस test के लिए एक बार चला जिसने उसे माँगा, और हर test को ऐसी लिस्ट मिली जिसे किसी और ने छुआ तक नहीं था। यही डिफ़ॉल्ट है, और इसका एक नाम है, function scope: fixture की वैल्यू उतनी ही देर ज़िंदा रहती है जितनी देर एक test function। आठवाँ अध्याय दिखाता है कि इसे कैसे बदला जाए, और आमतौर पर आपको ऐसा क्यों नहीं करना चाहिए।

fixtures भी fixtures का उपयोग कर सकते हैं

एक fixture किसी दूसरे fixture को ठीक उसी तरह माँग सकता है जैसे कोई test — उसे parameter के रूप में नाम देकर:

python
import pytest

from cart import Cart


@pytest.fixture
def empty_cart():
    return Cart()


@pytest.fixture
def cart(empty_cart):
    empty_cart.add("pen", 15.0, 3)
    empty_cart.add("bag", 850.0)
    return empty_cart


def test_new_cart_is_empty(empty_cart):
    assert empty_cart.total() == 0


def test_total(cart):
    assert cart.total() == 895.0
text
pytest -q
..                                                                       [100%]
2 passed in 0.01s

cart को empty_cart के ऊपर बनाया गया है, इसलिए कोई test तैयारी का वह स्तर माँग सकता है जिसकी उसे ज़रूरत है। कोई भी test कुछ call नहीं करता; pytest नामों के पीछे-पीछे चलता है, test_total से cart तक और cart से empty_cart तक, और उन्हें उसी क्रम में बनाता है जिसकी उस श्रृंखला को ज़रूरत है।

इसे होते हुए देखना — --setup-show

यह जानने के लिए कि कौन-से fixtures चले, आपको print calls जोड़ने की ज़रूरत नहीं है। --setup-show हर setup और teardown को प्रिंट करता है:

text
pytest -q --setup-show

        SETUP    F empty_cart
        test_cart.py::test_new_cart_is_empty (fixtures used: empty_cart) .
        TEARDOWN F empty_cart
        SETUP    F empty_cart
        SETUP    F cart (fixtures used: empty_cart)
        test_cart.py::test_total (fixtures used: cart, empty_cart) .
        TEARDOWN F cart
        TEARDOWN F empty_cart
2 passed in 0.01s

इसे ऊपर से पढ़ें। F का मतलब है function scope — एक test के लिए बनाया गया और उसके बाद फेंक दिया गया। empty_cart का setup दो बार होता है, हर test के लिए एक बार, और यही पिछले सेक्शन वाली ताज़गी है। जहाँ cart का उपयोग होता है, वहाँ empty_cart का setup पहले होता है, क्योंकि cart उस पर निर्भर है। और teardowns setups के उल्टे क्रम में आते हैं: जो सबसे बाद में बना, वह सबसे पहले हटाया गया।

(अगर आपने plugins इंस्टॉल किए हैं, तो उनके अपने fixtures के लिए अतिरिक्त लाइनें दिख सकती हैं। वे उनकी हैं, आपकी नहीं।)

अब तक teardown ने कुछ भी दिखाई देने वाला काम नहीं किया, क्योंकि पलटने के लिए कुछ था ही नहीं। अगले सेक्शन में यह बदलता है।

yield — पहले setup, फिर सफ़ाई

कुछ setup अपने पीछे ऐसा कुछ छोड़ जाते हैं जिसे पलटना ज़रूरी है: बंद करने के लिए कोई connection, हटाने के लिए कोई फ़ाइल, वापस रखने के लिए कोई global setting। इसके लिए fixture return की जगह yield का उपयोग करता है:

python
import pytest


@pytest.fixture
def connection():
    print("\nconnection: open")
    conn = {"orders": []}
    yield conn
    print("connection: close")


def test_starts_empty(connection):
    print("test: running")
    assert connection["orders"] == []
text
pytest -q -s

connection: open
test: running
.connection: close

1 passed in 0.01s

yield से पहले का सब कुछ setup है। yield के बाद वाली वैल्यू वह है जो test को मिलती है। फिर fixture रुक जाता है — test चलता है — और जब test पूरा हो जाता है, तो pytest fixture को yield के बाद से फिर शुरू करता है, और वह बचा हुआ हिस्सा teardown है। (. "connection: close" के आगे इसलिए बैठा है क्योंकि test का परिणाम उसी पल प्रिंट हो जाता है जब test ख़त्म होता है, teardown शुरू होने से ठीक पहले।)

सफ़ाई का कोड setup वाले function में ही है, उससे कुछ लाइनें नीचे। आप एक को जोड़कर दूसरे को भूल नहीं सकते, बिना यह साफ़ दिखे।

दो yield fixtures के साथ, जिनमें से एक दूसरे पर निर्भर हो, क्रम वही होता है जिसकी ओर --setup-show ने इशारा किया था:

python
import pytest


@pytest.fixture
def connection():
    print("\n1. connection: open")
    conn = {"orders": []}
    yield conn
    print("5. connection: close")


@pytest.fixture
def saved_order(connection):
    print("2. saved_order: insert")
    connection["orders"].append("A-100")
    yield "A-100"
    connection["orders"].remove("A-100")
    print("4. saved_order: delete")


def test_order_is_saved(connection, saved_order):
    print("3. test: running")
    assert saved_order in connection["orders"]
text
pytest -q -s

1. connection: open
2. saved_order: insert
3. test: running
.4. saved_order: delete
5. connection: close

1 passed in 0.01s

setup निर्भरता की श्रृंखला में नीचे से ऊपर की ओर जाता है; teardown वापस नीचे की ओर आता है। यही एकमात्र क्रम है जो काम करता है: saved_order अपनी row को connection के ज़रिए हटाता है, इसलिए जब वह ऐसा करे तब connection का खुला रहना ज़रूरी है।

test फ़ेल होने पर भी teardown चलता है

जब test अपने अंत में खुद ही सफ़ाई कर सकता है, तो सफ़ाई के लिए fixture की मेहनत क्यों करें? आज़माकर देखें। OPEN हर उस चीज़ का प्रतीक है जिसे कोई test पीछे छोड़ सकता है — एक खुला connection, एक अस्थायी फ़ाइल, एक बदली हुई setting:

python
OPEN = []


def test_broken():
    OPEN.append("connection")
    assert OPEN == ["A-100"]
    OPEN.remove("connection")


def test_nothing_left_open():
    assert OPEN == []
text
pytest -q --tb=no
FF                                                                       [100%]
=========================== short test summary info ============================
FAILED test_cleanup_in_body.py::test_broken - AssertionError: assert ['connec...
FAILED test_cleanup_in_body.py::test_nothing_left_open - AssertionError: asse...
2 failed in 0.01s

(--tb=no tracebacks को छिपा देता है और सिर्फ़ सारांश रखता है।) एक ग़लती, दो विफलताएँ। फ़ेल होने वाला assert एक exception उठाता है, और exception उठते ही test वहीं ख़त्म हो जाता है, इसलिए उसके नीचे की सफ़ाई वाली लाइन कभी चली ही नहीं। connection OPEN में ही रह गया, और अगला test — जो पूरी तरह सही था — विरासत में मिली गड़बड़ी के कारण फ़ेल हो गया। किसी बड़े suite में, वही दूसरी विफलता है जिस पर आप पूरी दोपहर लगा देते।

अब वही काम, लेकिन सफ़ाई yield के बाद:

python
import pytest

OPEN = []


@pytest.fixture
def connection():
    OPEN.append("connection")
    yield "connection"
    OPEN.remove("connection")


def test_broken(connection):
    assert OPEN == ["A-100"]


def test_nothing_left_open():
    assert OPEN == []
text
pytest -q --tb=no
F.                                                                       [100%]
=========================== short test summary info ============================
FAILED test_cleanup_in_fixture.py::test_broken - AssertionError: assert ['con...
1 failed, 1 passed in 0.01s

एक ग़लती, एक विफलता। टूटा हुआ test अब भी रिपोर्ट होता है — fixture कुछ नहीं छिपाता — लेकिन उसकी गड़बड़ी अगले test तक नहीं पहुँची।

यही कारण है, और इससे निकलने वाला नियम यह है: सफ़ाई का कोड कभी test की body के अंत में नहीं जाता। वह fixture के yield के बाद जाता है।

fixture या साधारण function?

setup साझा करने का एकमात्र तरीका fixture नहीं है। एक साधारण function — मान लीजिए cart_with(*lines), जो दी गई lines से एक कार्ट बनाता है — भी काम करता है, और नीचे दिया गया पूर्ण उदाहरण ऐसे ही एक function का उपयोग करता है।

फ़ैसला करने वाला अंतर यह है: test किसी helper को call करता है, और उसे arguments दे सकता है। test किसी fixture को call नहीं कर सकता; वह सिर्फ़ उसका नाम ले सकता है। fixture उसे माँगने वाले हर test के लिए हमेशा एक ही चीज़ बनाता है।

इसलिए:

  • fixture का उपयोग करें जब कई tests को एक जैसी शुरुआती स्थिति चाहिए, और ख़ासकर तब जब उस स्थिति को बाद में साफ़ करना ज़रूरी हो। गारंटी वाला teardown सिर्फ़ fixture को मिलता है।
  • helper function का उपयोग करें जब हर test को थोड़ा अलग ऑब्जेक्ट चाहिए — इन lines वाला कार्ट, उस नाम वाला user। arguments के लिए ही तो functions होते हैं।

ये दोनों अच्छी तरह साथ काम करते हैं: एक fixture किसी helper को call कर सकता है, जैसा अगला उदाहरण करता है। (एक ऐसा fixture बनाने का तरीका भी है जो एक function लौटाता है — बारहवें अध्याय का "factory" pattern। अभी आपको इसकी ज़रूरत नहीं है।)


पूर्ण उदाहरण

एक दुकान जहाँ discount codes एक module-level डिक्शनरी में रहते हैं। किसी code को रजिस्टर करने से global state बदलती है, इसलिए जो test किसी code को रजिस्टर करे, उसे उसे दोबारा हटाना भी होगा।

shop.py:

python
DISCOUNTS = {}


def register_discount(code, percent):
    DISCOUNTS[code] = percent


def remove_discount(code):
    del DISCOUNTS[code]


class Cart:
    def __init__(self):
        self.lines = []

    def add(self, name, price, quantity=1):
        if quantity < 1:
            raise ValueError(f"quantity must be at least 1: {quantity}")
        self.lines.append((name, price, quantity))

    def total(self, code=None):
        amount = sum(price * quantity for _, price, quantity in self.lines)
        if code is not None:
            amount = amount * (100 - DISCOUNTS[code]) / 100
        return round(amount, 2)

test_shop.py:

python
import pytest

from shop import Cart, register_discount, remove_discount


def cart_with(*lines):
    # A plain helper: it takes arguments, so each test can ask for its own cart.
    cart = Cart()
    for name, price, quantity in lines:
        cart.add(name, price, quantity)
    return cart


@pytest.fixture
def cart():
    # The cart most tests share. Built fresh for every test that asks.
    return cart_with(("pen", 15.0, 3), ("bag", 850.0, 1))


@pytest.fixture
def summer_code():
    # Changes global state, so it must undo that change afterwards.
    register_discount("SUMMER", 10)
    yield "SUMMER"
    remove_discount("SUMMER")


def test_total(cart):
    assert cart.total() == 895.0


def test_summer_discount(cart, summer_code):
    assert cart.total(summer_code) == 805.5


def test_code_is_gone_afterwards(cart):
    with pytest.raises(KeyError):
        cart.total("SUMMER")


def test_small_cart(summer_code):
    cart = cart_with(("pen", 15.0, 2))
    assert cart.total(summer_code) == 27.0


def test_zero_quantity_is_refused():
    with pytest.raises(ValueError):
        Cart().add("pen", 15.0, 0)
text
pytest -q --setup-show

        SETUP    F cart
        test_shop.py::test_total (fixtures used: cart) .
        TEARDOWN F cart
        SETUP    F cart
        SETUP    F summer_code
        test_shop.py::test_summer_discount (fixtures used: cart, summer_code) .
        TEARDOWN F summer_code
        TEARDOWN F cart
        SETUP    F cart
        test_shop.py::test_code_is_gone_afterwards (fixtures used: cart) .
        TEARDOWN F cart
        SETUP    F summer_code
        test_shop.py::test_small_cart (fixtures used: summer_code) .
        TEARDOWN F summer_code
        test_shop.py::test_zero_quantity_is_refused .
5 passed in 0.01s

यहाँ तीन बातें ध्यान देने योग्य हैं।

पहला, हर test सिर्फ़ वही माँगता है जिसका वह उपयोग करता है। test_small_cart को एक अलग कार्ट चाहिए था, इसलिए उसने cart माँगा ही नहीं; उसने अपनी lines के साथ helper को call किया — और --setup-show पुष्टि करता है कि उसके लिए cart कभी बना ही नहीं। test_zero_quantity_is_refused को सिर्फ़ एक ख़ाली कार्ट और एक call चाहिए, इसलिए वह कोई fixture उपयोग नहीं करता; यह योजना वाली "ऐसी शुरुआती स्थिति जिसे कोई और साझा नहीं करता" वाली row है।

दूसरा, test_code_is_gone_afterwards असल में fixture का test है। यह test_summer_discount के बाद चलता है और जाँचता है कि SUMMER अब मौजूद नहीं है। यह क्यों मायने रखता है, यह देखने के लिए summer_code में yield और सफ़ाई वाली लाइन की जगह return "SUMMER" लिख दें:

text
pytest -q --tb=no
..F..                                                                    [100%]
=========================== short test summary info ============================
FAILED test_shop.py::test_code_is_gone_afterwards - Failed: DID NOT RAISE Key...
1 failed, 4 passed in 0.01s

code उस test से बाहर निकलकर, जिसने उसे रजिस्टर किया था, अगले test में रिस गया। उस जाँच के बिना यह रिसाव चुपचाप हो जाता।

तीसरा, helper और fixture साथ मिलकर काम करते हैं। cart_with जानता है कि कार्ट कैसे बनाना है; cart fixture तय करता है कि ज़्यादातर tests कौन-सा कार्ट साझा करें।


जब यह काम न करे

fixture 'crat' not found

text
_________________________ ERROR at setup of test_empty _________________________
file .../test_typo.py, line 11
  def test_empty(crat):
E       fixture 'crat' not found
>       available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, capteesys, cart, doctest_namespace, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, subtests, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory
>       use 'pytest --fixtures [testpath]' for help on them.

किसी parameter का नाम किसी fixture से मेल नहीं खाया। या तो उसकी स्पेलिंग ग़लत है — यहाँ cart की जगह crat, जो लिस्ट में ठीक वहीं मौजूद है — या function में @pytest.fixture वाली लाइन नहीं है, और उस स्थिति में वह लिस्ट में होगा ही नहीं। ध्यान दें कि यहाँ E और ERROR है, F और FAILED नहीं: test कभी शुरू ही नहीं हुआ।

Fixture "cart" called directly

text
Fixture "cart" called directly. Fixtures are not meant to be called directly,
but are created automatically when test functions request them as parameters.

test ने अपनी body में cart() लिखा। fixture को parameter के रूप में नाम देकर माँगा जाता है, उसे कभी call नहीं किया जाता। अगर आप सच में उसे अपने arguments के साथ call करना चाहते हैं, तो आपको असल में एक helper function चाहिए।

फ़ेल होने की जगह ERROR at setup of test_total exception test चलने से पहले fixture के अंदर उठा — उदाहरण के लिए setup में किसी ग़लत add() से आया ValueError: quantity must be at least 1: 0। E का मतलब है "तैयारी टूट गई"; F का मतलब है "दावा ग़लत निकला"। पहले fixture में देखें।

fixture function has more than one 'yield' एक fixture ठीक एक बार yield करता है। setup yield के ऊपर जाता है, सफ़ाई उसके नीचे; दो वैल्यूज़ के लिए एक tuple yield करें या दो fixtures लिखें।

एक test अकेले पास होता है और बाकियों के साथ फ़ेल होता है tests के बीच कुछ साझा हो रहा है — कोई module-level लिस्ट या डिक्शनरी, या कोई global जिसे किसी fixture ने बदला और वापस नहीं रखा। उसे एक fixture में बनाएँ, और अगर वह global state है, तो yield के बाद बदलाव को पलट दें।

सफ़ाई का कोड नहीं चला सफ़ाई का कोड test की body में किसी फ़ेल होने वाले assert के बाद है, या fixture return का उपयोग करता है। उसे किसी yield fixture में, yield के बाद ले जाएँ।