Marker, skip আর xfail — কোন টেস্ট কোথায় চলবে
skip, skipif, importorskip আর xfail দিয়ে প্রতিটি টেস্টের প্রয়োজন ও প্রত্যাশা জানানো; strict=True, raises= আর XPASS-এর অর্থ; নিজের marker রেজিস্টার করে -m দিয়ে বাছাই; সাথে parametrize করা fixture আর factory fixture।
- 1সমস্যা
- 2বোঝা
- 3উদাহরণ
- 4অনুমান
- 5নিজে করা
- 6কঠিন করা
যে সমস্যাটা আমরা সমাধান করছি
ছোট একটি প্রজেক্ট, shop, যাতে একটি মডিউল আর দুটি টেস্ট ফাইল। money.py:
def parse_price(text: str) -> float:
return float(text.replace(",", ""))
def split_bill(total: float, people: int) -> float:
return round(total / people, 2)test_money.py সাধারণ মান দিয়ে এটি পরীক্ষা করে। test_export.py দাম পড়ে YAML থেকে, আর তার জন্য লাগে বাইরের প্যাকেজ yaml:
import yaml
from money import parse_price
def test_prices_from_yaml():
data = yaml.safe_load("pen: '15.00'")
assert parse_price(data["pen"]) == 15.0যে মেশিনে yaml ইনস্টল করা নেই, সেখানে pytest -q বলে:
==================================== ERRORS ====================================
_______________________ ERROR collecting test_export.py ________________________
ImportError while importing test module '/home/you/shop/test_export.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/usr/lib/python3.12/importlib/__init__.py:90: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
test_export.py:1: in <module>
import yaml
E ModuleNotFoundError: No module named 'yaml'
=========================== short test summary info ============================
ERROR test_export.py
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
1 error in 0.13sশেষ দুটি লাইন পড়ুন। Interrupted। test_money.py-এর ভালো টেস্ট দুটি চালানোই হয়নি। এই মেশিনে চলতে না পারা একটিমাত্র ফাইল গোটা রানটিকে সাথে নিয়ে ডুবেছে।
যথেষ্ট দিন টিকে থাকা যেকোনো প্রজেক্টের এটাই বাস্তব অবস্থা। কিছু টেস্টের লাগে এমন একটি লাইব্রেরি যা ঐচ্ছিক। কিছু কেবল Linux-এ অর্থবহ। কিছুর লাগে এমন একটি API key যা কেবল CI-এর কাছে আছে। কিছু টেস্ট এমন একটি বাগের কথা বলে যা সবাই জানে, কিন্তু কেউ এখনো ঠিক করেনি। আর কিছু টেস্ট এক মিনিট সময় নেয়, আর প্রতিবার ফাইল সেভ করার পর এক মিনিট অপেক্ষা করতে আপনি চান না।
এই টেস্টগুলো মুছে ফেলা ভুল, কমেন্ট করে রাখাও ভুল। আপনি যা চান তা হলো pytest-কে জানানো — প্রতিটি টেস্টের কী লাগবে আর তার কাছে আপনি কী আশা করেন — তারপর প্রতিটি রানে কী করতে হবে তা pytest-ই ঠিক করুক। এর হাতিয়ারটির নাম marker।
এই অধ্যায় শেষে আপনি পারবেন
- একটি টেস্ট সবসময়, কোনো শর্তে, বা টেস্টের ভেতর থেকে skip করতে, আর কারণটি দৃশ্যমান রাখতে
- ঐচ্ছিক প্যাকেজ না থাকলে
pytest.importorskipদিয়ে গোটা ফাইল skip করতে - জানা বাগকে
xfailদিয়ে চিহ্নিত করতে, আরstrict=Trueওraises=দিয়ে তাকে নিখুঁত করতে - রিপোর্টের অক্ষর
s,xআরXপড়তে, আর-rs,-rx,-rAদিয়ে বিস্তারিত চাইতে - নিজের marker বানাতে, রেজিস্টার করতে, আর
--strict-markersদিয়ে বানান-ভুল ধরতে -m "not slow"আর-m "slow and db"দিয়ে টেস্ট বেছে নিতে- এমন fixture লিখতে যা প্রতিটি টেস্ট কয়েকবার চালায়, আর এমন fixture যা একটি ফাংশন ফেরত দেয়
আগে যা জানা লাগবে: mocking।
টেস্ট লেখার আগে
Marker টেস্ট কী পরীক্ষা করে তা নিয়ে নয় — সেটা আগের অধ্যায়গুলোতে হয়ে গেছে। Marker হলো দুটি প্রশ্নের উত্তর, যা টেস্ট লেখার আগেই দিতে হয়, অথচ বেশিরভাগ মানুষ উত্তর দেয় ভুল কারণে একটি রান লাল হয়ে যাওয়ার পর।
১. এই টেস্টটি আদৌ চলতে হলে কী লাগবে? এমন একটি প্যাকেজ যা বাধ্যতামূলক dependency নয়, একটি নির্দিষ্ট অপারেটিং সিস্টেম, পাইথনের একটি ভার্সন, গোপন কিছু রাখা একটি environment variable, একটি ডেটাবেস, নেটওয়ার্ক, অনেকটা সময়। এর প্রতিটিই একটি পূর্বশর্ত। সেটি না থাকলে টেস্টটির বলার কিছু নেই: সে পাসও করেনি, ফেলও করেনি।
২. আজ এর কাছে আমি কী আশা করি? সাধারণত, পাস করবে। কিন্তু কোডে যদি একটি জানা বাগ থাকে, আর আপনি সঠিক আচরণটি বর্ণনা করা টেস্টটি লিখছেন, তাহলে আশা করেন এটি ফেল করবে — আর ফেল করবে একটি নির্দিষ্ট উপায়ে।
উত্তরগুলোই marker ঠিক করে দেয়, তাই আগে সেগুলো লিখে ফেলুন। shop প্রজেক্টের পরিকল্পনা দেখতে এরকম:
| কেস | ইনপুট / পরিস্থিতি | যা লাগবে | প্রত্যাশিত ফল | Marker | |---|---|---|---|---| | সাধারণ দাম | "1,250.50" | কিছু না | 1250.5 | নেই | | YAML export | "pen: '15.00'" | yaml প্যাকেজ | যেখানে ইনস্টল আছে সেখানে 15.0 | pytest.importorskip("yaml") | | POSIX পাথ | "data", "prices.csv" | Windows নয় | "data/prices.csv" | skipif(sys.platform == "win32") | | লাইভ রেট | আসল API | RATES_API_KEY সেট করা | একটি রেট, কেবল যেখানে key আছে | টেস্টের ভেতরে pytest.skip() | | মুদ্রার চিহ্ন | "$1,250" | কিছু না | 1250.0 — আজ ValueError (বাগ #42) | xfail(raises=ValueError, strict=True) | | অনেক দাম | 1000টি স্ট্রিং | এক সেকেন্ড বা বেশি | সঠিক যোগফল | slow (নিজের) |
টেবিলটি কলাম ধরে পড়ুন। যা লাগবে কলাম থেকে আসে skip। প্রত্যাশিত ফল কলাম থেকে আসে xfail। আর সময় কতটা লাগে, তা থেকে আসে একটি নিজের marker, যা দিয়ে আপনি বেছে নেন কী চালাবেন।
এর কোনোটি কাজ করার আগে যা প্রস্তুত থাকা চাই:
pytestইনস্টল করা একটি virtual environment, আর টেস্ট থেকেmoney.pyimport করা যায় এমন অবস্থা (আগের অধ্যায়গুলোর মতো প্রজেক্টের রুট থেকে চালান);- প্রজেক্টের রুটে একটি
pyproject.toml, কারণ নিজের marker সেখানেই রেজিস্টার করা হয়; - বাগটি কোথাও একটি নম্বরসহ লিখে রাখা, যাতে
reason=সেটির দিকে ইঙ্গিত করতে পারে।
আর যা marker দিয়ে চিহ্নিত করবেন না:
- কোনো টেস্ট ফেল করছে আর কেন তা জানেন না বলে সেটিকে skip করবেন না। Skip পরের প্রতিটি রান থেকে ব্যর্থতাটি লুকিয়ে ফেলে। হয় ঠিক করুন, নয়তো এতটা বুঝুন যাতে কারণসহ
xfail(raises=...)লিখতে পারেন। - অস্থির (flaky) টেস্টকে
xfailকরবেন না। যে টেস্ট মাঝে মাঝে পাস করে, সে কোনোদিনXআর কোনোদিনxদেখাবে, আরstrict=Trueথাকলে এলোমেলোভাবে ফেল করবে। অস্থিরতা টেস্টের নিজের বাগ; সেটাই ঠিক করুন। - সবকিছুকে
slowলেবেল দেবেন না। প্রতিটি রানে সুইটের বেশিরভাগই বাদ পড়লে রোজকার রানটি কিছুই পরীক্ষা করে না। কেবল সেই কয়েকটি টেস্টকে লেবেল দিন যেগুলো সত্যিই কয়েক সেকেন্ড খায়। - pytest ঠিকমতো skip করে কি না, তার টেস্ট লিখবেন না। আপনি
money.pyপরীক্ষা করছেন, pytest নয়। Marker হলো কনফিগারেশন; রানের রিপোর্টই তার কাজ করার যথেষ্ট প্রমাণ।
অধ্যায়ের বাকিটা এই টেবিলটিকেই, সারি ধরে ধরে, বাস্তবায়ন করে।
skip — এই টেস্টটি নয়, আর কারণ এই
প্রথমে সেই পদ্ধতি, যা marker সম্পর্কে জানার আগে সাধারণত করা হয় — আগেভাগে return:
import os
def test_live_rates():
if "RATES_API_KEY" not in os.environ:
return
assert False, "would call the real API here". [100%]
1 passed in 0.07sPassed। টেস্টটি কিছুই পরীক্ষা করেনি, অথচ রিপোর্ট বলছে সফল। key ছাড়া একটি ল্যাপটপে এটি প্রতিটি রানে বলা একটি মিথ্যা, আর এই সবুজ বিন্দুটিকে আসল একটি থেকে আলাদা করার উপায় নেই। সমাধান শর্তটি এড়িয়ে যাওয়া নয়, বরং সেটি সৎভাবে জানানো — skip হিসেবে।
সবচেয়ে সহজ marker হলো একটি decorator যা বলে "এটি চালিও না", সাথে একটি কারণ:
import os
import sys
import pytest
from money import parse_price, split_bill
@pytest.mark.skip(reason="rounding rules not agreed yet")
def test_split_rounding():
assert split_bill(100.0, 3) == 33.34
@pytest.mark.skipif(sys.platform == "win32", reason="uses a POSIX path")
def test_posix_path():
assert os.path.join("data", "prices.csv") == "data/prices.csv"
@pytest.mark.skipif(sys.version_info < (3, 13), reason="needs Python 3.13")
def test_new_feature():
assert parse_price("10") == 10.0
def test_live_rates():
if "RATES_API_KEY" not in os.environ:
pytest.skip("RATES_API_KEY is not set")
assert False, "would call the real API here"s.ss [100%]
1 passed, 3 skipped in 0.09sচারটি টেস্ট, সিদ্ধান্ত নেওয়ার চারটি আলাদা উপায়:
@pytest.mark.skip(reason=...)— সবসময় skip হয়। ভেতরের কোড কখনো চলে না।@pytest.mark.skipif(condition, reason=...)— শর্তটি সত্য হলে skip হয়। শর্তটি যাচাই হয় ফাইলটি collect করার সময়। এই রানটি ছিল Linux-এ, তাইtest_posix_pathচলেছে আর পাস করেছে; পাইথন ছিল 3.12, তাইtest_new_featureskip হয়েছে।- টেস্টের ভেতরে ডাকা
pytest.skip("...")— সিদ্ধান্তটি হয় টেস্ট চলার সময়। এটি ব্যবহার করুন যখন মাঝপথে গিয়ে জানতে পারেন, যেমন environment দেখার পর বা কোনো fixture কী ফেরত দিল তা দেখার পর।
প্রগ্রেস লাইনের প্রতিটি s একটি skip হওয়া টেস্ট। Skip ব্যর্থতা নয়: রানটি সবুজই থাকে।
কিন্তু কারণগুলো কোথাও দেখা যাচ্ছে না। ডিফল্টভাবে pytest সারাংশে কেবল ব্যর্থতা আর error দেখায়। Skip দেখতে চাইলে -rs দিন:
s.ss [100%]
=========================== short test summary info ============================
SKIPPED [1] test_skips.py:9: rounding rules not agreed yet
SKIPPED [1] test_skips.py:19: needs Python 3.13
SKIPPED [1] test_skips.py:26: RATES_API_KEY is not set
1 passed, 3 skipped in 0.10sএজন্যই reason গুরুত্বপূর্ণ। তিন মাস পর কেবল "skipped" শব্দটি কাউকে বলবে না টেস্টটি আবার চালু করা যায় কি না।
importorskip — যখন গোটা ফাইলের একটি প্যাকেজ লাগে
ফিরে যাই test_export.py-তে। import yaml-এর জায়গায় লিখুন:
import pytest
from money import parse_price
yaml = pytest.importorskip("yaml")
def test_prices_from_yaml():
data = yaml.safe_load("pen: '15.00'")
assert parse_price(data["pen"]) == 15.0.. [100%]
=========================== short test summary info ============================
SKIPPED [1] test_export.py:5: could not import 'yaml': No module named 'yaml'
2 passed, 1 skipped in 0.08spytest.importorskip("yaml") import করার চেষ্টা করে। সফল হলে মডিউলটি ফেরত দেয়, তাই সেটিকে yaml-এ রাখা হয়েছে। ব্যর্থ হলে গোটা ফাইলটি skip হয়, import-এর ত্রুটিটিই কারণ হিসেবে। বাকি দুটি টেস্ট এখন চলে। yaml ইনস্টল করুন, আর কোনো পরিবর্তন ছাড়াই একই ফাইল পুরোটা চলবে।
xfail — যে বাগের কথা আপনি জানেন
Skip বলে "এই টেস্ট এখানে চলতে পারে না"। দ্বিতীয় আরেকটি, ভিন্ন কথাও আছে: "এই টেস্ট চলে, আর এটি ফেল করবে বলেই প্রত্যাশিত, কারণ কোডে একটি জানা বাগ আছে"।
parse_price-এ একটি আছে। এটি মুদ্রার চিহ্ন সামলাতে পারে না:
import pytest
from money import parse_price
@pytest.mark.xfail(reason="bug #42: currency symbol is not stripped")
def test_parse_with_symbol():
assert parse_price("$1,250") == 1250.0
@pytest.mark.xfail(reason="spaces as thousands separators")
def test_parse_with_spaces():
assert parse_price("1 250") == 1250.0pytest -q -rx:
xx [100%]
=========================== short test summary info ============================
XFAIL test_xfail.py::test_parse_with_symbol - bug #42: currency symbol is not stripped
XFAIL test_xfail.py::test_parse_with_spaces - spaces as thousands separators
2 xfailed in 0.08sSkip হওয়া টেস্টের মতো নয়, xfail টেস্ট সত্যিই চলে। পূর্বাভাস অনুযায়ী এটি ফেল করেছে, তাই ছোট হাতের x দেখায় আর রানটি সবুজ থাকে। টেস্টটি এখন বাগটির একটি লিখিত দলিল, যা সুইটের ভেতরেই আছে — কারো মাথার ভেতরের একটি নোট নয়।
তাহলে শুধু skip করলেই হতো না কেন? কারণ skip হওয়া টেস্ট কখনো চলে না, তাই আপনাকে কিছুই জানাতে পারে না — বাগ ঠিক হলেও না, কোড বদলে গিয়ে নতুন উপায়ে ফেল করলেও না। xfail টেস্ট প্রতিবার চলে, আর তার ফল আপনার পূর্বাভাসের সাথে মেলানো হয়। Skip হলো "এখানে চলতে পারে না"-র জন্য; xfail হলো "চলে, আর আমি জানি সে কী বলবে"-র জন্য।
এবার কেউ বাগ #42 ঠিক করল, money.py বদলে $ ছেঁটে ফেলার ব্যবস্থা করল:
Xx [100%]
=================================== XPASSES ====================================
=========================== short test summary info ============================
XPASS test_xfail.py::test_parse_with_symbol - bug #42: currency symbol is not stripped
1 xfailed, 1 xpassed in 0.12sবড় হাতের X, XPASS: "ফেল করার কথা ছিল, কিন্তু পাস করেছে"। আর রানটি এখনো সবুজ — exit code 0। সবুজ রানের দিকে কেউ তাকায় না, তাই marker থেকে যায়, আর টেস্টটি এমন একটি বাগের দাবি করে যেতে থাকে যার আর অস্তিত্ব নেই।
strict=True — সুখবরকে জোরালো করুন
import pytest
from money import parse_price
@pytest.mark.xfail(reason="bug #42: currency symbol is not stripped", strict=True)
def test_parse_with_symbol():
assert parse_price("$1,250") == 1250.0F [100%]
=================================== FAILURES ===================================
____________________________ test_parse_with_symbol ____________________________
[XPASS(strict)] bug #42: currency symbol is not stripped
=========================== short test summary info ============================
FAILED test_xfail.py::test_parse_with_symbol - [XPASS(strict)] bug #42: curre...
1 failed in 0.10sstrict=True থাকলে অপ্রত্যাশিত পাস একটি ব্যর্থতা। শুনতে উল্টো লাগে, আর ঠিক এটাই আপনি চান: যে বাগটি ঠিক করেছে তাকে marker সরাতে বলা হয়, আর তখন থেকে টেস্টটি একটি সাধারণ টেস্ট হয়ে সংশোধনটিকে পাহারা দেয়। প্রজেক্টের সব xfail-এর জন্য এটি চাইলে pytest কনফিগারেশনে xfail_strict = true দিন।
raises= — সঠিক কারণে ফেল করুন
আর্গুমেন্ট ছাড়া xfail যেকোনো ব্যর্থতা মেনে নেয়। তার মধ্যে সেগুলোও আছে যা আপনি বোঝাতে চাননি:
import pytest
from money import parse_price
@pytest.mark.xfail(reason="bug #42")
def test_loose():
assert parse_prise("$1,250") == 1250.0
@pytest.mark.xfail(raises=ValueError, reason="bug #42")
def test_precise():
assert parse_prise("$1,250") == 1250.0xF [100%]
=================================== FAILURES ===================================
_________________________________ test_precise _________________________________
@pytest.mark.xfail(raises=ValueError, reason="bug #42")
def test_precise():
> assert parse_prise("$1,250") == 1250.0
^^^^^^^^^^^
E NameError: name 'parse_prise' is not defined
test_raises.py:13: NameError
=========================== short test summary info ============================
XFAIL test_raises.py::test_loose - bug #42
1 failed, 1 xfailed in 0.09sদুটি টেস্টেই একই বানান-ভুল, parse_prise। test_loose NameError-টি গিলে ফেলে শান্তভাবে একটি x দেখিয়েছে — এটি চিরকাল "ফেল করার কথা" হয়েই থাকত, কখনো parse_price পরীক্ষা না করেই। test_precise জানিয়ে দিয়েছে কোন ব্যর্থতা সে আশা করে — float("$1250") যে ValueError দেয় — তাই অন্য যেকোনো exception আসল ব্যর্থতা হিসেবে দেখানো হয়। বানানটি ঠিক করুন, দুটিই x হয়ে যাবে।
রিপোর্টের অক্ষরগুলো
প্রগ্রেস লাইন প্রতিটি টেস্টের জন্য একটি করে অক্ষর ব্যবহার করে, আর সারাংশে কী যাবে তা বাছতে -r সেই একই অক্ষরগুলো নেয়:
| অক্ষর | অর্থ | সারাংশে দেখাতে | |---|---|---| | . | পাস করেছে | -rp | | F | ফেল করেছে | -rf (ডিফল্টভাবে চালু) | | E | setup বা teardown-এ error | -rE (ডিফল্টভাবে চালু) | | s | skip হয়েছে | -rs | | x | xfailed — প্রত্যাশামতো ফেল করেছে | -rx | | X | xpassed — অপ্রত্যাশিতভাবে পাস করেছে | -rX |
অক্ষরগুলো একসাথে দেওয়া যায়: -rsx skip আর xfail একসাথে দেখায়। -rA দেখায় সবকিছু, পাসসহ:
..xs [100%]
==================================== PASSES ====================================
=========================== short test summary info ============================
PASSED test_params.py::test_parse_price[15-15.0]
PASSED test_params.py::test_parse_price[1,250.50-1250.5]
SKIPPED [1] test_params.py:6: space separators not decided
XFAIL test_params.py::test_parse_price[$1,250-1250.0] - bug #42
2 passed, 1 skipped, 1 xfailed in 0.08sনিজের marker
skip আর xfail বদলে দেয় pytest একটি টেস্টের সাথে কী করবে। কিন্তু একটি marker নিছক একটি লেবেল-ও হতে পারে, আপনার বানানো একটি নাম, যা টেস্ট বাছাইয়ে ব্যবহার না করা পর্যন্ত কিছুই বদলায় না। ধরা যাক কিছু টেস্ট ধীর, আর কিছুর ডেটাবেস লাগে:
import time
import pytest
from money import parse_price, split_bill
def test_parse_plain():
assert parse_price("1,250.50") == 1250.5
@pytest.mark.slow
def test_parse_many():
time.sleep(1)
assert sum(parse_price("1.5") for _ in range(1000)) == 1500.0
@pytest.mark.slow
@pytest.mark.db
def test_bill_from_database():
time.sleep(1)
assert split_bill(300.0, 3) == 100.0
@pytest.mark.db
def test_bill_lookup():
assert split_bill(90.0, 3) == 30.0.... [100%]
=============================== warnings summary ===============================
test_marks.py:12
/home/you/shop/test_marks.py:12: PytestUnknownMarkWarning: Unknown pytest.mark.slow - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/how-to/mark.html
@pytest.mark.slow
test_marks.py:18
/home/you/shop/test_marks.py:18: PytestUnknownMarkWarning: Unknown pytest.mark.slow - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/how-to/mark.html
@pytest.mark.slow
test_marks.py:19
/home/you/shop/test_marks.py:19: PytestUnknownMarkWarning: Unknown pytest.mark.db - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/how-to/mark.html
@pytest.mark.db
test_marks.py:25
/home/you/shop/test_marks.py:25: PytestUnknownMarkWarning: Unknown pytest.mark.db - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/how-to/mark.html
@pytest.mark.db
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
4 passed, 4 warnings in 2.09sচারটিই পাস করেছে, আর প্রতিটি ব্যবহারে pytest অভিযোগ করছে। pytest.mark.<যেকোনো কিছু> গ্রহণযোগ্য — attribute-টি তখনই তৈরি হয়ে যায় — তাই pytest বুঝতে পারে না slow আসল নাম, নাকি অন্য কিছুর ভুল বানান। সে চায় আপনি যে নামগুলো বোঝাতে চান সেগুলো ঘোষণা করুন।
Marker রেজিস্টার করা
তালিকাটি যায় pyproject.toml-এ, প্রতিটি marker-এর জন্য একটি স্ট্রিং, কোলনের আগে নাম আর পরে বর্ণনা:
[tool.pytest.ini_options]
markers = [
"slow: takes more than a second; deselect with -m 'not slow'",
"db: needs the database",
].... [100%]
4 passed in 2.11sসতর্কবার্তাগুলো চলে গেছে, আর pytest --markers এখন প্রজেক্টে যোগ দেওয়া পরবর্তী মানুষটির জন্য সেগুলো তালিকা করে দেখায়:
@pytest.mark.slow: takes more than a second; deselect with -m 'not slow'
@pytest.mark.db: needs the database-m দিয়ে বাছাই
এবার লেবেলগুলো কাজে আসে। -m marker-এর নাম দিয়ে একটি expression নেয়, and, or, not আর বন্ধনীসহ:
$ pytest -q -m "not slow"
.. [100%]
2 passed, 2 deselected in 0.07s
$ pytest -q -m "slow and db"
. [100%]
1 passed, 3 deselected in 1.08s
$ pytest -q -m "slow or db"
... [100%]
3 passed, 1 deselected in 2.07sDeselected মানে skipped নয়। Skip হওয়া টেস্টকে বাছা হয়েছিল, তারপর সে চলতে অস্বীকার করেছে; deselected টেস্টকে কখনো বাছাই করা হয়নি। -m "not slow" হলো প্রতিবার সেভ করার পরের রান; পুরো রান CI-এর জন্য আর push করার আগে।
--strict-markers — বানান-ভুল হয়ে যায় error
সতর্কবার্তা চোখ এড়িয়ে যাওয়া সহজ। এখানে একটি টেস্ট যা কেউ পরে যোগ করেছে, marker-এর বানান ভুল:
@pytest.mark.slwo
def test_split_large_group():
time.sleep(1)
assert split_bill(1000.0, 8) == 125.0... [100%]
=============================== warnings summary ===============================
test_marks.py:30
/home/you/shop/test_marks.py:30: PytestUnknownMarkWarning: Unknown pytest.mark.slwo - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/how-to/mark.html
@pytest.mark.slwo
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
3 passed, 2 deselected, 1 warning in 1.07sচাওয়া হয়েছিল -m "not slow", তবু রানে এক সেকেন্ড লেগেছে: ভুল বানানের টেস্টটি slow নয়, তাই সেটি চলেছে। --strict-markers থাকলে রেজিস্টার না করা marker collection-ই থামিয়ে দেয়:
==================================== ERRORS ====================================
________________________ ERROR collecting test_marks.py ________________________
'slwo' not found in `markers` configuration option
=========================== short test summary info ============================
ERROR test_marks.py - Failed: 'slwo' not found in `markers` configuration option
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
1 error in 0.13sপ্রতিবার এই ফ্ল্যাগ টাইপ করতে চাইবেন না, তাই এটি addopts-এ রাখুন:
[tool.pytest.ini_options]
addopts = "--strict-markers"
markers = [
"slow: takes more than a second; deselect with -m 'not slow'",
"db: needs the database",
]pytestmark — গোটা ফাইল চিহ্নিত করা
যখন একটি ফাইলের প্রতিটি টেস্টের ডেটাবেস লাগে, প্রতিটিতে আলাদা decorator বসানো পুনরাবৃত্তি। মডিউল স্তরে pytestmark নামের একটি ভেরিয়েবল ফাইলের প্রতিটি টেস্টে প্রযোজ্য হয়:
import pytest
from money import split_bill
pytestmark = pytest.mark.db
def test_bill_for_two():
assert split_bill(50.0, 2) == 25.0
def test_bill_for_four():
assert split_bill(80.0, 4) == 20.0$ pytest -q -m db
.. [100%]
2 passed, 4 deselected in 0.07sনামটি হুবহু pytestmark হতে হবে। এটি একটি তালিকাও হতে পারে, pytestmark = [pytest.mark.db, pytest.mark.slow]।
parametrize-এর একটিমাত্র কেসে marker
Parametrize করা টেস্টে marker দিলে তা প্রতিটি কেসে প্রযোজ্য হয়। একটি কেস চিহ্নিত করতে সেটিকে pytest.param(..., marks=...)-এ মুড়ে দিন:
import pytest
from money import parse_price
@pytest.mark.parametrize(
"text, expected",
[
("15", 15.0),
("1,250.50", 1250.5),
pytest.param(
"$1,250", 1250.0,
marks=pytest.mark.xfail(raises=ValueError, reason="bug #42"),
),
pytest.param(
"1 250", 1250.0,
marks=pytest.mark.skip(reason="space separators not decided"),
),
],
)
def test_parse_price(text, expected):
assert parse_price(text) == expectedtest_params.py::test_parse_price[15-15.0] PASSED [ 25%]
test_params.py::test_parse_price[1,250.50-1250.5] PASSED [ 50%]
test_params.py::test_parse_price[$1,250-1250.0] XFAIL (bug #42) [ 75%]
test_params.py::test_parse_price[1 250-1250.0] SKIPPED (space separa...) [100%]
=================== 2 passed, 1 skipped, 1 xfailed in 0.11s ====================জানা বাগটি কাজ করা কেসগুলোর পাশেই, একই টেবিলে থাকে, আর যেদিন ঠিক হবে সেদিন আপনি একটি pytest.param মোড়ক মুছে দেবেন।
আরও এগিয়ে: যে fixture বদলায়, আর যে fixture বানায়
দুটি fixture প্যাটার্ন fixture-এর অধ্যায়গুলোতে জায়গা পায়নি, আর marker-এর পরই সেগুলোর ভালো সময়, কারণ দুটিই সেই টেস্টগুলোকে বহুগুণ করে বা আকার দেয় যেগুলো আপনি এইমাত্র বাছাই করতে শিখলেন।
Parametrize করা fixture
parametrize একটি টেস্টের ইনপুট বদলায়। কখনো কখনো আপনি চান একটি fixture ব্যবহার করা প্রতিটি টেস্ট সেই fixture-এর প্রতিটি মানের জন্য একবার করে চলুক। @pytest.fixture-কে একটি params তালিকা দিন, আর বর্তমান মানটি পড়ুন request.param থেকে:
import pytest
from money import parse_price
@pytest.fixture(params=["1250", "1,250", "1,250.00"], ids=["plain", "comma", "decimals"])
def price_text(request):
return request.param
def test_parses_to_1250(price_text):
assert parse_price(price_text) == 1250.0
def test_is_positive(price_text):
assert parse_price(price_text) > 0test_fixtures.py::test_parses_to_1250[plain] PASSED [ 16%]
test_fixtures.py::test_parses_to_1250[comma] PASSED [ 33%]
test_fixtures.py::test_parses_to_1250[decimals] PASSED [ 50%]
test_fixtures.py::test_is_positive[plain] PASSED [ 66%]
test_fixtures.py::test_is_positive[comma] PASSED [ 83%]
test_fixtures.py::test_is_positive[decimals] PASSED [100%]
============================== 6 passed in 0.08s ===============================দুটি টেস্ট, তিনটি মান, ছয়টি রান। request একটি বিল্ট-ইন fixture, যা বর্ণনা করে কোন টেস্টের জন্য প্রস্তুতি চলছে; request.param থাকে কেবল যখন fixture-এর params আছে। ids রিপোর্টে প্রতিটি মানের নাম দেয় — না দিলে pytest মান থেকেই একটি id বানায়, যা লম্বা বা ছাপা যায় না এমন মানের ক্ষেত্রে পড়া কঠিন। তালিকায় marks=-সহ pytest.param(...)-ও রাখা যায়, ঠিক parametrize-এর মতো।
এটি কোথায় কাজে আসে: একই টেস্টগুলো কয়েকটি backend, কয়েকটি ফাইল ফরম্যাট, কয়েকটি মুদ্রার বিরুদ্ধে। params-এ একটি মান যোগ করুন, আর fixture-টি ব্যবহার করা প্রতিটি টেস্ট সেটিও পরীক্ষা করবে।
Factory fixture
একটি fixture টেস্ট থেকে আর্গুমেন্ট নিতে পারে না; টেস্ট কেবল তার নাম উল্লেখ করে। তাহলে টেস্টের যদি আলাদাভাবে বানানো দুটি জিনিস লাগে? একটি ফাংশন ফেরত দিন:
import pytest
from money import parse_price
@pytest.fixture
def make_receipt(tmp_path):
def _make(name, lines):
path = tmp_path / f"{name}.txt"
path.write_text("\n".join(lines))
return path
return _make
def receipt_total(path):
return sum(parse_price(line) for line in path.read_text().splitlines())
def test_two_receipts(make_receipt):
lunch = make_receipt("lunch", ["15", "1,250.50"])
taxi = make_receipt("taxi", ["300"])
assert receipt_total(lunch) + receipt_total(taxi) == 1565.5
def test_empty_receipt(make_receipt):
empty = make_receipt("empty", [])
assert receipt_total(empty) == 0.. [100%]
2 passed in 0.08sFixture-টি এখনো প্রতি টেস্টে একবার চলে, আর এখনো নিজের fixture পায় (এখানে tmp_path, যা ফাইলগুলো মুছে ফেলার দায়িত্বও নেয়)। সে যা হাতে তুলে দেয় তা হলো _make, আর টেস্ট সেটি যতবার খুশি, যে আর্গুমেন্ট দিয়ে খুশি ডাকে। make_... নামটি প্রচলিত রীতি, আর পাঠককে এক নজরে বলে দেয় এই fixture ডাকার মতো একটি ফাংশন, তৈরি একটি মান নয়।
একটা সম্পূর্ণ উদাহরণ
সবকিছু একসাথে, একটি প্রজেক্টে। pyproject.toml:
[tool.pytest.ini_options]
addopts = "--strict-markers"
markers = [
"slow: takes more than a second; deselect with -m 'not slow'",
"network: talks to an outside service",
]test_checkout.py, একই money.py দিয়ে — যাতে বাগ #42 এখনো আছে:
import os
import time
import pytest
from money import parse_price, split_bill
# Every test that asks for amount_text runs once per format.
@pytest.fixture(params=["300", "300.00", "1,250"], ids=["whole", "decimals", "comma"])
def amount_text(request):
return request.param
# A factory: the fixture hands back a function the test can call many times.
@pytest.fixture
def make_share():
def _make(text, people):
return split_bill(parse_price(text), people)
return _make
def test_share_is_positive(amount_text, make_share):
assert make_share(amount_text, 3) > 0
@pytest.mark.parametrize(
"text, people, share",
[
("300", 3, 100.0),
("90", 4, 22.5),
pytest.param(
"$300", 3, 100.0,
marks=pytest.mark.xfail(raises=ValueError, strict=True, reason="bug #42"),
),
],
)
def test_share(make_share, text, people, share):
assert make_share(text, people) == share
@pytest.mark.slow
def test_many_group_sizes(make_share):
time.sleep(1) # stands in for real, slow work
assert all(make_share("300", n) > 0 for n in range(1, 1000))
@pytest.mark.network
def test_live_exchange_rate():
if "RATES_API_KEY" not in os.environ:
pytest.skip("RATES_API_KEY is not set")
raise AssertionError("would call the real service here")রোজকার রান, কারণসহ:
$ pytest -q -rsx -m "not slow"
.....xs [100%]
=========================== short test summary info ============================
SKIPPED [1] test_checkout.py:52: RATES_API_KEY is not set
XFAIL test_checkout.py::test_share[$300-3-100.0] - bug #42
5 passed, 1 skipped, 1 deselected, 1 xfailed in 0.09sকেবল ধীর টেস্টটি, আর অফলাইন রান যা ধীর ও নেটওয়ার্ক দুই ধরনের টেস্টই বাদ দেয়:
$ pytest -q -m slow
. [100%]
1 passed, 7 deselected in 1.07s
$ pytest -q -m "not slow and not network"
.....x [100%]
5 passed, 2 deselected, 1 xfailed in 0.08sগুনে দেখুন: তিনটি মানের একটি fixture থেকে test_share_is_positive-এর তিনটি রান, test_share-এর তিনটি কেস, একটি ধীর টেস্ট আর একটি নেটওয়ার্ক টেস্ট — মোট আটটি, আর 1 passed, 7 deselected-এর যোগফলও তাই।
আর প্রতিটি ফল সত্যি কথা বলে। নেটওয়ার্ক টেস্ট key ছাড়া পাস করার ভান করে না; সে বলে যে skip হয়েছে আর কেন। বাগ #42 সুইটের ভেতরে আছে, আর যেহেতু এটি raises=ValueError-সহ strict, যেদিন ঠিক হবে সেদিন রানটি লাল হয়ে marker সরাতে বলবে।
কিছু ভাঙা অবস্থা ও তার সমাধান
PytestUnknownMarkWarning: Unknown pytest.mark.slow - is this a typo? Marker-টি রেজিস্টার করা নেই। pyproject.toml-এর markers-এ যোগ করুন। নামটি সত্যিই বানান-ভুল হলে ঠিক করুন — আর addopts-এ --strict-markers দিন, যাতে পরেরটি সতর্কবার্তা না হয়ে error হয়।
`'slwo' not found in markers configuration option` এটি --strict-markers-এর নিজের কাজ করা: ফাইলটি এমন একটি marker ব্যবহার করছে যা তালিকায় নেই। বানান ঠিক করুন, অথবা নতুন নামটি রেজিস্টার করুন।
8 deselected আর no tests ran, exit code 5 -m কিছুর সাথেই মেলেনি। -m expression-এ ভুল বানানের নাম error নয়, --strict-markers থাকলেও না — -m sloww কেবল কোনো টেস্টই বাছাই করে না। নামটি pytest --markers-এর সাথে মিলিয়ে দেখুন।
ERROR: Wrong expression passed to '-m': not slow and: at column 13: expected not OR left parenthesis OR identifier; got end of input -m expression অসম্পূর্ণ। পুরো expression উদ্ধৃতিচিহ্নে রাখুন, আর নিশ্চিত করুন প্রতিটি and/or-এর দুই পাশে কিছু আছে।
Error evaluating 'skipif': you need to specify reason=STRING when using booleans as conditions. reason= ছাড়া skipif(sys.platform == "linux")। একটি boolean শর্ত কারণ সম্পর্কে কিছু বলে না, তাই pytest একটি কারণ দাবি করে।
`Using pytest.skip outside of a test will skip the entire module. If that's your intention, pass allow_module_level=True.` pytest.skip("...") ফাইলের একেবারে উপরের স্তরে ডাকা হয়েছে। প্যাকেজ না থাকলে pytest.importorskip ব্যবহার করুন; নয়তো লিখুন pytest.skip("...", allow_module_level=True), অথবা pytestmark-এ একটি skipif দিন।
[XPASS(strict)] bug #42 সুখবর, ব্যর্থতা হিসেবে জানানো: বাগটি ঠিক হয়েছে। xfail marker সরান আর টেস্টটি রেখে দিন।
বাগ ঠিক হওয়ার পরও যে xfail টেস্ট x থেকে যায় এটি অন্য কোনো কারণে ফেল করছে — প্রায়ই টেস্টের নিজের ভেতরে একটি বানান-ভুল। যে exception আপনি সত্যিই আশা করেন সেটি দিয়ে raises= যোগ করুন, তাহলে অন্য যেকোনো কিছু আসল ব্যর্থতা হিসেবে দেখাবে।
AttributeError: 'SubRequest' object has no attribute 'param' Fixture-টি request.param পড়ছে, কিন্তু তার কোনো params= তালিকা নেই, অন্য কোনোভাবেও parametrize করা হয়নি। @pytest.fixture-এ params=[...] যোগ করুন।
fixture 'name' not found, একটি fixture-এর দিকে ইঙ্গিত করে একটি fixture লেখা হয়েছে def make_user(name): হিসেবে, এই আশায় যে টেস্ট name পাঠাবে। Fixture-এর আর্গুমেন্ট হলো অন্য fixture, তাই pytest name নামের একটি fixture খুঁজতে গেছে। এটিকে factory বানান: ভেতরের একটি ফাংশন যা name নেয়, আর fixture সেটি ফেরত দেয়।
ধাপ ৪ / ৬ — অনুমান
যাচাই করুন
pytest -q চালালে প্রগ্রেস লাইন আর শেষ লাইন কী হবে? (মনে রাখুন, round(2.675, 2) দেয় 2.67।)
import pytest
@pytest.mark.skip(reason="not ready")
def test_export():
assert False
def test_total():
assert 2 + 2 == 4
@pytest.mark.xfail(reason="bug #7")
def test_rounding():
assert round(2.675, 2) == 2.68- As.F 1 passed, 1 skipped, 1 failed
- B..x 2 passed, 1 xfailed
- Cs.x 1 passed, 1 skipped, 1 xfailed
- Ds.X 1 passed, 1 skipped, 1 xpassed
slow marker pyproject.toml-এ রেজিস্টার করা আছে, --strict-markers নেই। আপনি pytest -m "not slow" চালালেন, তবু রানে পাঁচ সেকেন্ড লাগল। কেন?
import time
import pytest
@pytest.mark.slwo
def test_big_report():
time.sleep(5)
assert True- A`-m` কেবল `skip`-এর মতো বিল্ট-ইন marker-এর সাথে কাজ করে
- BMarker-এর বানান ভুল, `slwo` — তাই টেস্টটি `slow` নয় আর বাছাই হয়েছে; pytest কেবল একটি সতর্কবার্তা দেয়
- C`time.sleep` থাকলে pytest marker উপেক্ষা করে
- D`-m "not slow"` কেবল ধীর টেস্টগুলোই চালায়
বাগ #12 আসলে ইতিমধ্যে ঠিক হয়ে গেছে, তাই assert-টি সত্য। pytest -q কী জানাবে?
import pytest
@pytest.mark.xfail(strict=True, reason="bug #12")
def test_upper():
assert "taka".upper() == "TAKA"- A`X` — 1 xpassed, exit code 0
- B`x` — 1 xfailed, exit code 0
- C`s` — marker থাকায় টেস্টটি skip হয়
- D`F` — `[XPASS(strict)]`-সহ FAILED, exit code 1
উত্তর দিতে অ্যাকাউন্ট লাগবে
উত্তর মিলিয়ে দেখতে সাইন ইন করুন
প্রশ্নগুলো উপরে আছে, আর মাথায় মাথায় উত্তর ভেবে নেওয়াই আসল কাজ। সঠিক উত্তর, ব্যাখ্যা আর তিন ধাপের ইঙ্গিত দেখতে সাইন ইন করুন।
নিজে করুন
আপনার কাছে আগে থেকে আছে এমন একটি টেস্ট সুইট নিন — আগের কোনো অধ্যায় থেকে, বা নিজের কাজ থেকে — আর তাকে একটি সৎ marker-এর সেট দিন:
pyproject.toml-এ নিজের দুটি marker রেজিস্টার করুন (যেমনslowআরnetwork), আরaddopts-এ--strict-markersচালু করুন।- প্রতিটি দিয়ে অন্তত একটি টেস্ট চিহ্নিত করুন। ধীর টেস্ট না থাকলে
time.sleep(1)দিয়ে একটি বানিয়ে নিন। - এমন একটি টেস্ট যোগ করুন যার একটি environment variable লাগে, আর variable না থাকলে ভেতর থেকে
pytest.skipদিয়ে skip করুন। - আপনার কোডে একটি বাগ খুঁজুন, বা বানিয়ে নিন। যে টেস্টটির পাস করা উচিত সেটি লিখুন, আর তাকে
strict=Trueও একটি নির্দিষ্টraises=-সহxfailদিন। paramsআরids-সহ একটি fixture যোগ করুন, যা অন্তত দুটি টেস্ট ব্যবহার করে।
তারপর চালান, আর প্রতিটির আউটপুট রেখে দিন:
pytest -q -rsx -m "not slow"pytest -q -m slowpytest -q -rA
শেষে দুটি পরীক্ষা। আপনার একটি marker-এর বানান ভুল করুন, আর একবার --strict-markers ছাড়া, একবার সহ চালান। তারপর ধাপ ৪-এর বাগটি ঠিক করে আবার চালান। প্রতিবার pytest কী বলে পড়ুন — এই দুটি বার্তাই marker দেওয়া একটি সুইটকে সৎ রাখে, আর একবার নিজে ঘটিয়ে দেখলে দ্বিতীয়বার চিনতে সহজ হয়।
সমাধান
একটি সমাধান, shop প্রজেক্টের money.py দিয়ে। ধাপ ৪-এর বাগটি নতুন, বাগ #51: স্প্রেডশিটের একটি খালি ঘর আসে "" হয়ে, আর সেটি 0.0 গণ্য হওয়া উচিত — কিন্তু float("") ValueError দেয়।
pyproject.toml:
[tool.pytest.ini_options]
addopts = "--strict-markers"
markers = [
"slow: takes more than a second; deselect with -m 'not slow'",
"network: needs a real outside service and its API key",
]test_solution.py:
import os
import time
import pytest
from money import parse_price, split_bill
# Step 5: one fixture, three formats, used by two tests.
@pytest.fixture(params=["90", "90.00", "1,090"], ids=["whole", "decimals", "comma"])
def amount_text(request):
return request.param
def test_parse_is_positive(amount_text):
assert parse_price(amount_text) > 0
def test_split_for_one_is_whole_amount(amount_text):
assert split_bill(parse_price(amount_text), 1) == parse_price(amount_text)
# Step 2: a slow test, labelled so it can be left out.
@pytest.mark.slow
def test_many_prices():
time.sleep(1) # stands in for real, slow work
assert sum(parse_price("2.5") for _ in range(1000)) == 2500.0
# Step 3: decided inside the test, because only the environment knows.
@pytest.mark.network
def test_live_rates():
if "RATES_API_KEY" not in os.environ:
pytest.skip("RATES_API_KEY is not set")
raise AssertionError("would call the real service here")
# Step 4: bug #51 - an empty cell should count as 0, today it raises ValueError.
@pytest.mark.xfail(raises=ValueError, strict=True, reason="bug #51: empty cell")
def test_empty_cell_is_zero():
assert parse_price("") == 0.0তিনটি রান:
$ pytest -q -rsx -m "not slow"
......sx [100%]
=========================== short test summary info ============================
SKIPPED [1] test_solution.py:34: RATES_API_KEY is not set
XFAIL test_solution.py::test_empty_cell_is_zero - bug #51: empty cell
6 passed, 1 skipped, 1 deselected, 1 xfailed in 0.09s
$ pytest -q -m slow
. [100%]
1 passed, 8 deselected in 1.07s
$ pytest -q -rA
.......sx [100%]
==================================== PASSES ====================================
=========================== short test summary info ============================
PASSED test_solution.py::test_parse_is_positive[whole]
PASSED test_solution.py::test_parse_is_positive[decimals]
PASSED test_solution.py::test_parse_is_positive[comma]
PASSED test_solution.py::test_split_for_one_is_whole_amount[whole]
PASSED test_solution.py::test_split_for_one_is_whole_amount[decimals]
PASSED test_solution.py::test_split_for_one_is_whole_amount[comma]
PASSED test_solution.py::test_many_prices
SKIPPED [1] test_solution.py:34: RATES_API_KEY is not set
XFAIL test_solution.py::test_empty_cell_is_zero - bug #51: empty cell
7 passed, 1 skipped, 1 xfailed in 1.08sপ্রথম পরীক্ষা: @pytest.mark.slow-এর বদলে ভুল বানানে @pytest.mark.slwo। --strict-markers ছাড়া (এখানে -o addopts="" দিয়ে বন্ধ করা), রানে এখনো এক সেকেন্ড লাগে, কারণ "ধীর" টেস্টটি বাদ পড়েনি:
$ pytest -q -m "not slow" -o addopts=""
.......sx [100%]
=============================== warnings summary ===============================
test_solution.py:24
/home/you/shop/test_solution.py:24: PytestUnknownMarkWarning: Unknown pytest.mark.slwo - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/how-to/mark.html
@pytest.mark.slwo
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
7 passed, 1 skipped, 1 xfailed, 1 warning in 1.09sসেটি থাকলে রান শুরুই হয় না:
$ pytest -q -m "not slow"
==================================== ERRORS ====================================
______________________ ERROR collecting test_solution.py _______________________
'slwo' not found in `markers` configuration option
=========================== short test summary info ============================
ERROR test_solution.py - Failed: 'slwo' not found in `markers` configuration ...
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
1 error in 0.13sদ্বিতীয় পরীক্ষা: money.py-তে বাগ #51 ঠিক করা হলো:
def parse_price(text: str) -> float:
if not text.strip():
return 0.0
return float(text.replace(",", ""))
def split_bill(total: float, people: int) -> float:
return round(total / people, 2)$ pytest -q -m "not slow"
......sF [100%]
=================================== FAILURES ===================================
___________________________ test_empty_cell_is_zero ____________________________
[XPASS(strict)] bug #51: empty cell
=========================== short test summary info ============================
FAILED test_solution.py::test_empty_cell_is_zero - [XPASS(strict)] bug #51: e...
1 failed, 6 passed, 1 skipped, 1 deselected in 0.08sxfail লাইনটি সরান, আর তখন থেকে টেস্টটি সাধারণ টেস্ট হিসেবে পাস করবে।
প্রতিটি সিদ্ধান্তের কারণ:
--strict-markersaddopts-এ, কমান্ড লাইনে নয়। যে নিরাপত্তা-জাল চালু করতে মনে রাখতে হয়, সেটি নিরাপত্তা-জাল নয়। কনফিগে থাকলে সবার জন্য প্রযোজ্য, CI-সহ।slowএকটি লেবেল, skip নয়। ধীর টেস্টটি একটি ভালো টেস্ট; এটি চলা উচিত, কেবল প্রতিবার সেভ করার পর নয়। লেবেল আপনাকে বেছে নিতে দেয়; skip এটিকে প্রতিটি রান থেকে সরিয়ে দিত।- API key-এর জন্য টেস্টের ভেতরে
pytest.skip(),skipifনয়। এখানে দুটিই কাজ করত, কিন্তু যাচাইটি সেই কোডের পাশেই থাকা উচিত যার key লাগে। আর টেস্ট যখন মাঝপথে গিয়ে জানে যে এগোতে পারবে না, তখন ভেতরের রূপটিই একমাত্র উপায়। raises=ValueErrorআরstrict=Trueএকসাথে।raises=নিশ্চিত করে টেস্টটি বাগ #51-এর কারণে ফেল করছে, বানান-ভুলের কারণে নয়;strict=Trueনিশ্চিত করে বাগ ঠিক হওয়ার দিনটি চোখে পড়বে। প্রত্যেকটি marker-এর মিথ্যা বলা শুরু করার একটি আলাদা পথ বন্ধ করে।- ফরম্যাটগুলোর জন্য parametrize করা fixture,
parametrizeনয়। দুটি টেস্ট একই তিনটি ইনপুট ভাগ করে। Fixture-এ তালিকাটি এক জায়গায় থাকে; চতুর্থ একটি ফরম্যাট যোগ করলে দুটি টেস্টেই একসাথে যোগ হয়। - Id
whole,decimals,comma। রিপোর্টে[comma]বলে দেয় কেসটি কী নিয়ে। এখানে[1,090]-ও চলত, কিন্তু মান লম্বা বা ছাপার অযোগ্য হলেও id কাজ করে যায়।
ধাপ ৬ / ৬
কঠিন করা — অধ্যায়ের কুইজ
সহজ থেকে কঠিন — দশটি প্রশ্ন, শেষেরগুলো ইচ্ছে করেই কঠিন।
সাইন ইন করে কুইজ দিন