Let's talk about the Python automated test framework

Posted by freshrod on Wed, 29 Dec 2021 21:20:36 +0100


In the python language department, there are many available automated testing frameworks, such as unittest+HTMLTestRunner, Nose, etc. in the early days, the Robot Framework is commonly used in recent years. The Robot Framework is the next very general testing framework in Python. Using the mechanism of extension plug-ins can help us realize almost any type of automated testing, Such as interface automation test, App automation test, Web UI automation test, etc.

Today, this article focuses on another general testing framework Pytest in Python language. Although it seems unreasonable to introduce Pytest as the author of Robot Framework, the framework technology is a family, and the framework that can quickly solve practical problems is a good framework. At the beginning of the year, I also published some suggestions on the selection of Robot Framework and Pytest framework: talk about the secret that Robot Framework has been misunderstood for many years. Interested readers can have a look.

One sentence summary: the core idea of Pytest is roughly the same as that of Robot Framework. It can meet the automatic test requirements in different scenarios in the form of plug-in extension.

  1. Introduction to Pytest
    Pytest is a very mature and full-featured Python testing framework. It is similar to Python's own unittest testing framework, but it is simpler and more powerful than the unittest framework.
    It provides complete online documents, and has a large number of third-party plug-ins and built-in help, which is suitable for many small or large projects. It is suitable for simple unit testing to complex function testing. You can also execute nose, unittest, and doctest style test cases. Support good integration practices, extended xUnit style setup, and non Python testing. Support the generation of test coverage report and PEP8 compatible coding style.

2. Installation and basic use of pytest
Pytest installation is very simple. You can install it online directly through pip command:

pip install -U pytest

Official Pytest documentation: https://docs.pytest.org/en/latest/

After installation, call the pytest test script mode:

1,py.test:

Pytest provides a command-line tool called directly, namely py Test, latest versions pytest and py Test both command-line tools are available

2,python -m pytest:

Effects and py Like test, this calling method is useful when testing multiple Python versions, such as testing Python 3:

python3 -m pytest [...]

Basic syntax:

usage: py.test [options] [file_or_dir] [file_or_dir] [...]

Introduction to some parameters:

py.test --version               View version
py.test --fixtures, --funcargs  View available fixtures
pytest --markers                View available markers
py.test -h, --help              Command line and profile help

# Stop after failure
py.test -x           Stop execution after first failure
py.test --maxfail=2  Stop execution after two failures

# Debug output
py.test -l, --showlocals  stay traceback Show local variables in
py.test -q, --quiet       Silent mode output
py.test -v, --verbose     Output more detailed information
py.test -s                Capture output, For example, display print Output of function
py.test -r char           Displays additional summary information for the specified test type
py.test --tb=style        Error message output format
    - long    default traceback Information format
    - native  Standard library format
    - short   Shorter format
    - line    One line per error

# Run the test for the specified marker
pytest -m MARKEXPR

# Run matching tests
py.test -k stringexpr

# Collect and display only available test cases, but do not run test cases
py.test --collect-only

# Call PDB on failure
py.test --pdb

3.Pytest case execution
3.1 use case search rules

If you run pytest without parameters, first find the test case in the path specified by the configuration item testpaths from the configuration file (pytest.ini, tox.ini, setup.cfg). If not, start from the current directory. Otherwise, the command line parameters are used to find directories and files. The search rules are as follows:

Finds the directory starting with test in the specified directory
Recursively traverses a directory unless the directory specifies a different recursion
Find the file name to test_ Start file
Find a class starting with Test (this class cannot have init method)
Find to test_ And test the functions and methods at the beginning
If you want to ignore the lookup path from the default lookup rule, you can add the -- ingore parameter, for example:

pytest --ignore=test_case/xxx.py

3.2 execution selection case

1. Execute all use cases in a single module:

py.test test_demo.py

2. Execute all use cases under the specified path:

py.test somepath

3. Execute use cases in string expressions:

py.test -k stringexpr

4. Run a use case in the specified module, such as test_ demo. Test in py module_ Func test function:

pytest test_demo.py::test_func

5. Run a use case under a class, such as test under TestClass_ Method test method:

pytest test_demo.py::TestClass::test_method

4. Characteristics of pytest fxiture

fixture is a unique function of pytest. It uses pytest fixture ID, defined in front of the function. When writing a test function, you can use the function name as the incoming parameter. Pytest will use the return value of the function as the incoming parameter of the test function in the way of dependency injection.

pytest.fixture(scope='function', params=None, autouse=False, ids=None)

Here, I would like to recommend my learning and exchange place 644956177, the technical Daniel in the group. If you are learning, Xiaobian welcomes you to join. Everyone is a test and Development Party and shares dry goods from time to time, including a copy of the latest 2021 data sorted out by myself.

4.1 as a parameter

fixture can be used as a parameter of other test functions, provided that it must return a value:

@pytest.fixture()
def hello():
    return "hello"

def test_string(hello):
    assert hello == "hello", "fixture should return hello"

4.2 as setup

fixture can also not return a value, which can be used to run a piece of code before the test method runs:

@pytest.fixture()  # Default parameters are called before each test method.
def before():
   print('before each test')

def test_1(before):
   print('test_1()')

@pytest.mark.usefixtures("before")
def test_2():
   print('test_2()')

This method is similar to setup_method,setup_ Modules are used in the same way. In fact, they are also special fixture s.

In the above example, one test uses pytest mark. Use the fixtures decorator to mark which fixture to use. This usage indicates that the fixture function is applied before starting the test, but its return value is not required.

4.3 scope of fixture

Fixrule can control its scope by setting the scope parameter (it also controls the frequency of calls). If scope = 'module', then the fixture is module level, and this fixture function will only be executed every time the same module is loaded. In this way, some objects that need time to be created can be reused. Fixture provides four scopes to specify the rules for fixture initialization:

Function: execute once before each test function. The default value is
Class: execute once before each class,
Module: each module is executed once before loading
Session: execute once before each session, that is, once for each test
4.4 reverse request

The fixture function can inversely obtain the test function, class or module context in the request by accepting the request object. For example:

@pytest.fixture(scope="module")
def smtp(request):
    import smtplib
    server = getattr(request.module, "smtpserver", "smtp.qq.com")
    smtp = smtplib.SMTP(server, 587, timeout=5)
    yield smtp
    smtp.close()

Sometimes it is necessary to comprehensively test whether the function of an object under a variety of different conditions meets the expectations. You can set the params parameter of the fixture, and then obtain the set value through request:

class Foo(object):
    def __init__(self, a, b, c):
        self.a = a
        self.b = b
        self.c = c

    def echo(self):
        print(self.a, self.b, self.c)
        return True

@pytest.fixture(params=[["1", "2", "3"], ["x", "y", "z"]])
def foo(request):
    return Foo(*request.param)

def test_foo(foo):
    assert foo.echo()
set up params After parameters, run test Different tests will be generated id,Can pass ids custom id: 

@pytest.fixture(params=[1, 2, 4, 8], ids=["a", "b", "c", "d"])
def param_a(request):
    return request.param

def test_param_a(param_a):
    print param_a

4.5 setup/teardown

setup/teardown refers to performing some actions when modules, functions and classes start and end running. For example, to test a database application in a function, you need to connect to the database before the function starts and disconnect from the database after the function runs. setup/teardown is a special fixture, which can be implemented in the following ways:

# Module level
def setup_module(module):
    pass

def teardown_module(module):
    pass


# Class level
@classmethod
def setup_class(cls):
    pass

@classmethod
def teardown_class(cls):
    pass


# Method level
def setup_method(self, method):
    pass

def teardown_method(self, method):
    pass


# Function level
def setup_function(function):
    pass

def teardown_function(function):
    pass

Sometimes, you want to have a global setup or teardown so that you can do some preparatory work at the beginning of the test, or do some cleaning work after the test. This can be done with hook:

def pytest_sessionstart(session):
    # setup_stuff

def pytest_sessionfinish(session, exitstatus):
    # teardown_stuff

It can also be implemented in the form of fixture:

@fixture(scope='session', autouse=True)
def my_fixture():
    # setup_stuff
    yield
    # teardown_stuff

4.6 automatic execution

Sometimes some fixtures need to be automatically executed globally, such as the initialization of some global variables, or some global cleaning or initialization functions. At this time, you can set the autouse parameter of the fixture to make the fixture execute automatically. Set to autouse=True to make the function execute by default. The following example will clean up the possible residual files before starting the test, and then set the program directory to this directory:

work_dir = "/c/temp"
@pytest.fixture(scope="session", autouse=True)
def clean_workdir():
    shutil.rmtree(work_dir)

5. Pytest Mark features
The function of marker in pytest is to mark tests to facilitate selective execution of test cases. Pytest provides some built-in markers:

# Skip test
@pytest.mark.skip(reason=None)

# Skip the test when a condition is met
@pytest.mark.skipif(condition)

# The test is expected to fail
@pytest.mark.xfail(condition, reason=None, run=True, raises=None, strict=False)

# Parameterized test function. Add parameters to the test case for the runtime to populate the test
# If the parameter name of parameterize conflicts with the fixture name, the fixture will be overwritten
@pytest.mark.parametrize(argnames, argvalues)

# Execute the given fixtures on the given test
# This usage has the same effect as using fixture directly
# It's just that you don't need to put the fixture name as a parameter in the method declaration
@pytest.mark.usefixtures(fixturename1, fixturename2, ...)

# Let the test be executed as soon as possible
@pytest.mark.tryfirst

# Let the test run as late as possible
@pytest.mark.trylast

Where pytest skip and pytest Xfail can realize the function of skipping tests. skip means skipping tests directly, while xfail means there is an expected failure.

In addition to the built-in markers, pytest also supports undefined markers, such as:

@pytest.mark.old_test
def test_one():
    assert False

@pytest.mark.new_test
def test_two():
    assert False

@pytest.mark.windows_only
def test_three():
    assert False

By using the - m parameter, pytest can selectively execute some tests:

$ pytest test.py -m 'not windows_only'

For a more detailed description of the marker, please refer to the official document: https://docs.pytest.org/en/latest/example/markers.html

  1. conftest.py file
    In a broad sense, conf test Py is a local per directory plug-in where directory specific hooks and fixtures can be defined. py. The test framework looks for conf test in the project it tests Py file, and then look for test options for the entire directory in this file, such as whether to detect and run doctest and which mode should be used to detect test files and functions.

To sum up, conf test Py files have the following functions:

Fixtures: used to provide static test data for test cases, which can be accessed by all tests unless a scope is specified.
Loading plug-ins: used to import external plug-ins or modules: pytest_plugins =“myapp.testsupport.myplugin”
Define hook: used to configure hooks, such as pytest_runtest_setup,pytest_runtest_teardown,pytest_config, etc.
Test root path: if conf test If the PY file is placed in the project root path, pytest will search for sub modules under the project root directory and add them to sys Path, so that all modules in the project can be tested without setting PYTHONPATH to specify the location of project modules.
There can be more than one confitest Py files exist at the same time, and their scope is directory. For example, when the test is very complex, you can create a subdirectory for a specific set of tests and create conf test. In this directory Py file and define a future or hooks. Like the following structure:

test_case
├── conftest.py
├── module1
│   └── conftest.py
├── module2
│   └── conftest.py
└── module3
    └── conftest.py

7. Pytest plug-in mechanism
Pytest is called a full function testing framework because it can extend the required functions in the form of external plug-ins or custom plug-ins. Here are several common third-party plug-ins:

Pytest xdist: distributed test
Pytest cov: generate test coverage report
pytest-pep8: check whether the code conforms to the PEP8 specification
Pytest flakes: detect code style
Pytest html: generate html report
Pytest randomly: the test sequence is random
Pytest rerunfailures: failed retry
Pytest timeout: timeout test

If you have worked but often feel that there are many difficulties, you feel that you are not good enough to learn in the test, and you want to continue learning. You may be concerned about me if you want to change careers. You can focus on me. [software test dao], you can receive the latest software test, interview with big factories, Python automation, interface, and framework to build learning materials in official account.

If my blog is helpful to you and you like my blog content, please click "like", "comment" and "collect" for three times!

Topics: Python Programming unit testing software testing