Testing, Packages and a Real Project
Testing with pytest
pip install pytest
shop/pricing.py:
def apply_discount(price, percent):
if not 0 <= percent <= 100:
raise ValueError("percent must be 0-100")
return round(price * (100 - percent) / 100, 2)
tests/test_pricing.py:
import pytest
from shop.pricing import apply_discount
@pytest.mark.parametrize("price, pct, expected", [(100, 10, 90.0), (19.99, 0, 19.99), (50, 100, 0.0)])
def test_apply_discount(price, pct, expected):
assert apply_discount(price, pct) == expected
def test_rejects_bad_percent():
with pytest.raises(ValueError):
apply_discount(10, 150)
Run pytest -q. The Software Testing & QA track goes much deeper.
Using third-party packages
pip install requests rich
import requests
resp = requests.get("https://api.github.com/repos/python/cpython", timeout=10)
resp.raise_for_status()
print(resp.json()["stargazers_count"])
Before adding a dependency, check it is maintained and widely used. Pin versions in requirements.txt, and audit them: pip install pip-audit && pip-audit.
pyproject.toml
Modern projects describe themselves in pyproject.toml:
[project]
name = "expense-tracker"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = ["rich>=13"]
[project.scripts]
expenses = "expenses.cli:main"
pip install -e . installs your project in editable mode and creates the expenses command.
Capstone: an expense tracker CLI
Requirements:
expenses add 12.50 food "lunch"stores an expense in~/.expenses.json.expenses list --month 2026-10prints a table (userich).expenses summaryshows totals per category and the month's total.- Invalid input gives a friendly message, never a traceback.
- Data is written atomically: write to a temp file, then
Path.replace()it, so a crash never corrupts your data. - Tests cover parsing, totals and the storage layer (use
tmp_path).
Skeleton:
import argparse, json
from dataclasses import dataclass, asdict
from datetime import date
from pathlib import Path
DB = Path.home() / ".expenses.json"
@dataclass
class Expense:
amount: float
category: str
note: str
day: str
def load():
return [Expense(**e) for e in json.loads(DB.read_text())] if DB.exists() else []
def save(expenses):
tmp = DB.with_suffix(".tmp")
tmp.write_text(json.dumps([asdict(e) for e in expenses], indent=2))
tmp.replace(DB)
def main():
p = argparse.ArgumentParser(prog="expenses")
sub = p.add_subparsers(dest="cmd", required=True)
a = sub.add_parser("add"); a.add_argument("amount", type=float)
a.add_argument("category"); a.add_argument("note", nargs="?", default="")
sub.add_parser("summary")
args = p.parse_args()
expenses = load()
if args.cmd == "add":
expenses.append(Expense(args.amount, args.category, args.note, date.today().isoformat()))
save(expenses)
elif args.cmd == "summary":
totals = {}
for e in expenses:
totals[e.category] = totals.get(e.category, 0) + e.amount
for cat, amt in sorted(totals.items()):
print(f"{cat:<12}{amt:>10.2f}")
Where to go next
- Flask Full-Stack to build web apps.
- Automation & Scripting for scheduled jobs and APIs.
- Software Testing & QA for professional test suites.
Check yourself
- What does
pip install -e .do? - Why write to a temp file before replacing the real one?
- How would you test
save()without touching your real home folder?
Seu progresso fica salvo apenas neste navegador.
Enviar para revisão (10 pontos)