Skip to content

ይህ ትምህርት ገና ወደ አማርኛ አልተተረጎመም፤ ስለዚህ በእንግሊዝኛ ቀርቧል። የእንግሊዝኛውን ገጽ ክፈቱ

pytest basics

4 min read

Plain assert stops at the first failure and doesn't say what went wrong. Python programmers use a tool that fixes both: it runs every test, then explains each failure with the values involved. By the end of this lesson you'll write tests in their own file, run them, and read the report.

Tests in their own file

is that tool. It isn't part of Python: on a computer you install it, like the packages in lesson 8.8. Here it loads the first time you press Run tests.

grades.py is lesson 5.8's grade_for. Next to it, test_grades.py holds three . Each imports what it tests (lesson 8.5) and makes one assertion. Press Run tests:

def grade_for(score):
    if score >= 85:
        return "A"
    elif score >= 70:
        return "B"
    elif score >= 50:
        return "C"
    else:
        return "F"

In the editor, press Escape then Tab to move on.

The command line shows what ran: pytest -q, "quietly". pytest looked in the folder for files named test_*.py or *_test.py, and in them for functions whose names start with test. Name yours test_ and the case, like test_boundary. It ran each one and printed a dot for each test, then 3 passed and how long it took (that time changes every run). All your test functions together are your .

pytest only runs what fits its naming rules. The file below has one function with a true assertion, named check_top. Answer, then run it:

ጥያቄ

What will pytest report for a file whose only function is check_top?

from grades import grade_for


def check_top():
    assert grade_for(90) == "A"

In the editor, press Escape then Tab to move on.

Reading a failure

Here's a bigger suite of six tests, and lesson 5.8's bug: > 85 instead of >= 85. Plain assert stopped at the first false assertion. Answer, then run it:

ጥያቄ

The bug changes only grade_for(85). What does the last line of pytest's report start with?

def grade_for(score):
    if score > 85:
        return "A"
    elif score >= 70:
        return "B"
    elif score >= 50:
        return "C"
    else:
        return "F"

In the editor, press Escape then Tab to move on.

Read the report from the top. .F.... is one character per test, in order: the second one, test_boundary, is the one. Under FAILURES, pytest shows that test's code, with > at the line that failed. Then come the E lines, the explanation:

E       AssertionError: assert 'B' == 'A'
E         
E         - A
E         + B

This is the same assertion that gave a bare AssertionError in lesson 9.1, now with both values: grade_for(85) returned 'B', and the test said 'A'. The - line is the right side of == and the + line is the left side. Write the call on the left and the answer you expect on the right, as here, and - is always your answer, + the code's. Below them, test_grades.py:9 is the file and line. The last two lines sum it up: FAILED test_grades.py::test_boundary, then the count.

Each test starts fresh

A test file isn't a script that runs top to bottom. pytest calls each test function on its own, as the last two lines here do by hand. Predict:

ውጤቱን ገምቱ

Decide before you look. Guessing wrong is how this sticks.

def test_top():
    grade = "A"
    assert grade == "A"


def test_same_grade():
    assert grade == "A"


test_top()
test_same_grade()

Python prints

Traceback (most recent call last):
  File "<your code>", line 11, in <module>
    test_same_grade()
    ~~~~~~~~~~~~~~~^^
  File "<your code>", line 7, in test_same_grade
    assert grade == "A"
           ^^^^^
NameError: name 'grade' is not defined

grade is a local name of test_top (lesson 5.3): it's gone when that call ends. test_same_grade has no name grade of its own, so Python raises NameError. If you expected the second test to see the first test's grade, you read the file as one script.

pytest does the same, and reports it as a failing test:

from grades import grade_for


def test_top():
    grade = grade_for(90)
    assert grade == "A"


def test_same_grade():
    assert grade_for(95) == grade

In the editor, press Escape then Tab to move on.

The report says E NameError: name 'grade' is not defined. Give each test everything it needs: here, assert grade_for(95) == "A".

Comparing floats

Lesson 2.1 showed that 0.1 + 0.2 == 0.3 is False: most floats are a tiny bit off from the decimal you wrote. pytest.approx compares with a small tolerance instead. It needs import pytest:

import pytest


def test_plain():
    assert 0.1 + 0.2 == 0.3


def test_approx():
    assert 0.1 + 0.2 == pytest.approx(0.3)

In the editor, press Escape then Tab to move on.

Use approx whenever the answer is a float that comes from a calculation.

መልመጃ

Test vat

Here's vat from lesson 5.1, in money.py. Run the starter first: vat(100) should be 115, and pytest shows what it really returned. Fix that test with pytest.approx, then add a test for a price of 0.

Your code

from money import vat


def test_vat_250():
    assert vat(250) == 287.5


def test_vat_100():
    assert vat(100) == 115

In the editor, press Escape then Tab to move on.

መፍትሄውን አሳይ

This is one way to solve it, not the only one. If yours prints the same thing, it works.

import pytest
from money import vat


def test_vat_250():
    assert vat(250) == pytest.approx(287.5)


def test_vat_100():
    assert vat(100) == pytest.approx(115)


def test_vat_zero():
    assert vat(0) == 0

ጥያቄ

A folder has grades.py, testing_grades.py, and test_grades.py, each with functions named test_something. Which file does pytest run tests from?

ዋና ዋና ነጥቦች

  • pytest runs every function whose name starts with test in every file named test_*.py (or *_test.py), each on its own, and reports how many passed and failed.
  • A function or file with another name is never run, and a test that never ran isn't a pass: check the count.
  • Read a failure's E lines: assert 'B' == 'A' shows both sides; - is the right side of ==, + the left. Put the call on the left, the answer on the right.
  • Each test starts fresh. Use pytest.approx for floats.