Read “Python Testing with pytest”

Read Python Testing with pytest: Simple, Rapid, Effective, and Scalable to learn Python and testing framework for it.

I am new for Python, but I need to maintain some python libraries in order to keep it up to date. Thus, I started learning Python and its world.

Thus, I took the Python testing book. Below is a note about it for me.

 Mark feature

In pytest, we can mark each test as @pytest.mark.smoke, for example. We can run only marked tests as:

$ pytest -m smoke runable/test/path.py

The mark is settable multiply. We can specify multiple marks like below.

@pytest.mark.one
@pytest.mark.two
def test_one_and_two_tests():
    ....

setup and teardown

Inunittest, we can use setUp/tearDown to set test data.
In pytest, we can define them like below. yield is an important part to call setup/teardown

@pytest.fixture(autouse=True)
def initialized_tasks_db ​(tmpdir):
    # Setup : start db
    tasks.start_tasks_db(str(tmpdir), ​ 'tiny' ​)

    yield # this is where the testing happens

    # Teardown : stop db
    tasks.stop_tasks_db()

Skip

We can skip test cases with reasons by skipif.

@pytest.mark.skipif(tasks.__version__ < ​ '0.2.0' ​, reason=​ 'not supported until version 0.2.0' ​)
def test_unique_id_1 ​():
    id_1 = tasks.unique_id()
    id_2 = tasks.unique_id()
    assert id_1 != id_2

We can also use skip and xfail in addition.

Raise

We can test raising errors like below.

def test_start_tasks_db_raises ​():
    with pytest.raises(ValueError) as excinfo:
        tasks.start_tasks_db(​ 'some/great/path' ​, ​ 'mysql' ​)

    exception_msg = excinfo.value.args[0]
    assert ​ exception_msg == ​ "db_type must be a 'tiny' or 'mongo'"

with syntax is very helpful in Python, ah…

ParamrizedTest

number_pairs = (
    ('one', 'one'),
    ('two', 'two'),
    ('three','there')
)

@pytest.mark.parametrize('numbers', number_pairs)
def test_numbers(self, numbers):
    (former, later) = numbers
    assert former == later

Easy to read. We can understand where we failed. nice.

_____________________________________________________________________________ TestWebDriverWebDriver.test_numbers[numbers2] ______________________________________________________________________________

self = <webdriver_test.TestWebDriverWebDriver object at 0x102c3a588>, numbers = ('three', 'there')

@pytest.mark.parametrize('numbers', number_pairs)
def test_numbers(self, numbers):
(former, later) = numbers
>       assert former == later
E       AssertionError: assert 'three' == 'there'
E         - three
E         ?     -
E         + there
E         ?   +

test/unit/webdriver/webdriver_test.py:96: AssertionError
======================================================================================== short test summary info =========================================================================================
FAIL test/unit/webdriver/webdriver_test.py::TestWebDriverWebDriver::()::test_numbers[numbers2]
1 failed, 9 passed in 0.61 seconds

About tests/conftest.py

Although conftest.py is a Python module, it should not be imported by test files. Don’t import conftest from anywhere. The conftest.py file gets read by pytest, and is considered a local plugin , which will make sense once we start talking about plugins in Chapter 5, ​ Plugins ​. For now, think of `tests/conftest.py` as a place where we can put fixtures used by all tests under the tests directory.

Fixture

A name of methods which has a fixture annotation can be used an argument for another test_ method. If the name matches each other, the fixture becomes one argument for the test method. We can use multiple fixtures like the same way.

@pytest.fixture()
def tuple_fixture():
    return (1, 'neko', None, { 'inu': 23 })

def test_tuple_fixture(tuple_fixture):
    assert tuple_fixture[3]['inu'] != 32
    assert tuple_fixture[2] is None

A fixture can be an argument as another fixture. The same fixture can be a test method like nested relation. Fixture feature has a scope. It has function, class, module and session. The default value is function.
We can specify them using usefixtures.

@pytest.fixture(scope='class')
def class_coped_fixture1():
    return 'one example'

# ...

@pytest.mark.usefixtures('class_coped_fixture1', 'class_coped_fixture2')
class TestSomething(object):
    def test_1(self, class_coped_fixture1, class_coped_fixture2):
        """Can use 'class_coped_fixture1' and 'class_coped_fixture2'"""

    def test_2(self, class_coped_fixture1, class_coped_fixture2):
        """Can use 'class_coped_fixture1' and 'class_coped_fixture2' again"""

Of course, we can the fixture in Parametrized test.

Interesting build-in fixtures are available like below.

available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_property, record_xml_attribute, record_xml_property, recwarn, tmpdir, tmpdir_factor


Plugin and toolchains, configurations

The book also addressed plugin feature for pytest. A tool, named tox, was also explained.

We can define configurations for pytest using pytest.ini file for example.

Is this style common in Python world? ah…

Wrap up

Through the book, I learned Python world and pytest. Personally, learning test framework in the language is important since if it is necessary to implement something in the language. I already had a little Python knowledge such as syntax, so I was able to learn smoothly.

1 Comment

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.