
What is the difference between the Pytest and Unittest test test frameworks?
How to distinguish the two is very simple. As an official testing framework, unittest is more basic in testing, and can be re developed on the basis of it. At the same time, the format will be more complex in usage; As a third-party framework, pytest framework is more flexible to use, and can have good compatibility with the original unittest style test cases. At the same time, it is richer in expansion. You can add scenarios through extended plug-ins, such as some concurrent tests;
Pytest installation
pip installation:
pip install pytest Copy code
Test installation succeeded:
pytest --help py.test --help Copy code
Check installation version:
pytest --version Copy code
Pytest example
Rules for writing Pytest:
- Test file to test_ Start (end with _test)
- The Test class starts with Test;
- The test method is test_ start
- Assertions use the basic assert
test_example.py
def count_num(a: list) -> int: return len(a) def test_count(): assert count_num([1, 2, 3]) != 3 Copy code
Perform tests:
pytest test_example.py Copy code
Execution results:
C:\Users\libuliduobuqiuqiu\Desktop\GitProjects\PythonDemo\pytest>pytest test_example.py -v ================================================================= test session starts ================================================================= platform win32 -- Python 3.6.8, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- d:\coding\python3.6\python.exe cachedir: .pytest_cache rootdir: C:\Users\libuliduobuqiuqiu\Desktop\GitProjects\PythonDemo\pytest plugins: Faker-8.11.0 collected 1 item test_example.py::test_count FAILED [100%] ====================================================================== FAILURES ======================================================================= _____________________________________________________________________ test_count ______________________________________________________________________ def test_count(): > assert count_num([1, 2, 3]) != 3 E assert 3 != 3 E + where 3 = count_num([1, 2, 3]) test_example.py:11: AssertionError =============================================================== short test summary info =============================================================== FAILED test_example.py::test_count - assert 3 != 3 ================================================================== 1 failed in 0.16s ================================================================== Copy code
remarks:
- . Represents test pass, F represents test failure;
- -v displays detailed test information, - h displays detailed help information of pytest command;
sign
By default, pytest looks for test in the current directory_ Is a test file that starts with _test (ends with _test), and executes all functions and methods in the file that start with test _ (ends with _test);
- Specifies to run the test case, which can be displayed by:: tag (file name:: Class Name:: method name) (file name:: function name)
pytest test_example3.py::test_odd Copy code
- Specify some test cases and test runs. You can use - k fuzzy matching
pytest -k example Copy code
- Pass pytest mark. Skip () or pytest makr. Skipif() conditional expression, skipping the specified test case
import pytest test_flag = False @pytest.mark.skip() def test_odd(): num = random.randint(0, 100) assert num % 2 == 1 @pytest.mark.skipif(test_flag is False, reason="test_flag is False") def test_even(): num = random.randint(0, 1000) assert num % 2 == 0 Copy code
- Pass pytest Raise () captures the exceptions that the test case might throw
def test_zero(): num = 0 with pytest.raises(ZeroDivisionError) as e: num = 1/0 exc_msg = e.value.args[0] print(exc_msg) assert num == 0 Copy code
- You know in advance that the test example will fail, but you don't want to skip it. You need to display a prompt and use pytest mark. xfail()
@pytest.mark.xfail() def test_sum(): random_list = [random.randint(0, 100) for x in range(10)] num = sum(random_list) assert num < 20 Copy code
- Multiple groups of data are tested for test cases, and each group of parameters can be executed independently once (it can avoid stopping the test after a single group of data test fails in the test case)
@pytest.mark.parametrize('num,num2', [(1,2),(3,4)]) def test_many_odd(num: int, num2: int): assert num % 2 == 1 assert num2 % 2 == 0 Copy code
Firmware
Firmware is a preprocessing function. pytest will load and run these firmware before (or after) executing the test function. Common application scenarios include database connection and shutdown (device connection and shutdown)
Simple use
import pytest @pytest.fixture() def postcode(): return "hello" def test_count(postcode): assert postcode == "hello" Copy code
According to the official explanation, when running the test function, it will first detect the parameters of the running function and search for fixture s with the same name as the parameters. Once pytest finds them, it will run these firmware, obtain the return values (if any) of these firmware, and pass these return values to the test function as parameters;
Pretreatment and post-treatment
Next, further verify the official statement:
import pytest @pytest.fixture() def connect_db(): print("Connect Database in .......") yield print("Close Database out .......") def read_database(key: str): p_info = { "name": "zhangsan", "address": "China Guangzhou", "age": 99 } return p_info[key] def test_count(connect_db): assert read_database("name") == "zhangsan" Copy code
Result of executing test function:
============================= test session starts ============================= platform win32 -- Python 3.6.8, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- D:\Coding\Python3.6\python.exe cachedir: .pytest_cache rootdir: C:\Users\libuliduobuqiuqiu\Desktop\GitProjects\PythonDemo\pytest plugins: Faker-8.11.0 collecting ... collected 1 item test_example.py::test_count Connect Database in ....... PASSED [100%]Close Database out ....... ============================== 1 passed in 0.07s ============================== Copy code
remarks:
- Firstly, the results verify the official explanation. pytest will find the firmware with the same name before executing the test function;
- connect_ There is yield in DB firmware. By default, pytest will judge that the code before the yield keyword belongs to preprocessing and will be executed before the test. The code after yield belongs to post-processing and will be executed after the test;
Scope
We have learned about the function of firmware from the above, and extracted some repetitive work to facilitate reuse. At the same time, in order to control firmware more finely in pytest framework, we will use scope to specify the scope of use of firmware, The first mock exam is the first mock exam in the module. The more specific example is the connection of the database. The operation of this connection may be time-consuming. I only need to run the test function of this module once, without running every time.
In defining firmware, the function is generally declared through the scop parameter. Commonly used are:
- Function: function level, each test function will execute firmware once;
- Class: class level, each test class is executed once, and all methods can be used;
- Module: module level, each module is executed once, and the functions and methods in the module can be used;
- Session: session level. A test is executed only once. All found functions and methods are available.
import pytest @pytest.fixture(scope="function") def func_scope(): print("func_scope") @pytest.fixture(scope="module") def mod_scope(): print("mod_scope") @pytest.fixture(scope="session") def sess_scope(): print("session_scope") def test_scope(sess_scope, mod_scope, func_scope): pass def test_scope2(sess_scope, mod_scope, func_scope): pass Copy code
Execution results:
============================= test session starts ============================= platform win32 -- Python 3.6.8, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- D:\Coding\Python3.6\python.exe cachedir: .pytest_cache rootdir: C:\Users\libuliduobuqiuqiu\Desktop\GitProjects\PythonDemo\pytest plugins: Faker-8.11.0 collecting ... collected 2 items test_example2.py::test_scope session_scope mod_scope func_scope PASSED [ 50%] test_example2.py::test_scope2 func_scope PASSED [100%] ============================== 2 passed in 0.07s ============================== Copy code
It can be seen from here that the firmware of module and session scope is only executed once, which can verify the official introduction
Automatic execution
Some people may say that it's so troublesome. Setting up directly in the unittest framework can automatically perform preprocessing, and the same pytest framework has similar automatic execution; The firmware in pytest framework generally runs automatically through the parameter autouse.
import pytest @pytest.fixture(scope='session', autouse=True) def connect_db(): print("Connect Database in .......") yield print("Close Database out .......") def test1(): print("test1") def test2(): print("test") Copy code
Execution results:
============================= test session starts ============================= platform win32 -- Python 3.6.8, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- D:\Coding\Python3.6\python.exe cachedir: .pytest_cache rootdir: C:\Users\libuliduobuqiuqiu\Desktop\GitProjects\PythonDemo\pytest plugins: Faker-8.11.0 collecting ... collected 2 items test_example.py::test1 Connect Database in ....... PASSED [ 50%]test1 test_example.py::test2 PASSED [100%]test Close Database out ....... ============================== 2 passed in 0.07s ============================== Copy code
The results show that the test function automatically executes connect before and after running_ DB firmware;
Parameterization
I mentioned @ pytest mark. Parameterize passes the parameterization test. When the firmware passes in parameters, it needs to pass the firmware request built in the pytest framework and pass the request Param get parameters
import pytest @pytest.fixture(params=[ ('redis', '6379'), ('elasticsearch', '9200') ]) def param(request): return request.param @pytest.fixture(autouse=True) def db(param): print('\nSucceed to connect %s:%s' % param) yield print('\nSucceed to close %s:%s' % param) def test_api(): assert 1 == 1 Copy code
Execution results:
============================= test session starts ============================= platform win32 -- Python 3.6.8, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- D:\Coding\Python3.6\python.exe cachedir: .pytest_cache rootdir: C:\Users\libuliduobuqiuqiu\Desktop\GitProjects\PythonDemo\pytest plugins: Faker-8.11.0 collecting ... collected 2 items test_example.py::test_api[param0] Succeed to connect redis:6379 PASSED [ 50%] Succeed to close redis:6379 test_example.py::test_api[param1] Succeed to connect elasticsearch:9200 PASSED [100%] Succeed to close elasticsearch:9200 ============================== 2 passed in 0.07s ============================== Copy code
Here, simulate the connection between redis and elasticsearch, load the firmware, automatically execute the connection, and then execute the test function to disconnect.
summary
For development, why should we also learn automated testing? A very important point is to save some repeated work time through automated testing. At the same time, it is of great significance for optimizing code structure, improving code coverage and subsequent project reconstruction, At the same time, understanding the difference between pytest and unittest on the basis of is helpful to choose the test tools suitable for you in different business scenarios. This article only briefly introduces the basic use of pytest. If you are interested, you can look at the official documents. The official documents also mention the use of built-in firmware, common test scenarios and so on.
reference resources:
docs.pytest.org/en/6.2.x/co... learning-pytest.readthedocs.io/zh/latest/d...