In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-02-23 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/03 Report--
This article mainly explains "how to use the Pytest testing framework". The content of the article is simple and clear, and it is easy to learn and understand. Please follow the editor's ideas to study and learn "how to use the Pytest testing framework".
Why do unit tests?
It is believed that many Python users will have the experience that in order to test whether a module or function outputs its expected results, it is often used to print the output part of the result to the console using the print () function.
Def myfunc (* args, * * kwargs): do_something () data =. Print (data)
In the process of improvement, we often have to use the print () function to ensure the accuracy of the results, but at the same time, because there are more modules or functions to test, the code will gradually leave behind a variety of print () calls that have not been removed or commented, making the whole code less concise and appropriate.
In programming, there is often a concept of "unit testing", which refers to the inspection and verification of the smallest testable unit in the software. This minimum testable unit can be any one or combination of our expressions, functions, classes, modules, packages, so we can uniformly put the steps of testing with print () into the unit test.
In Python, the official has already built the module unittest for unit testing for us. But for beginners, unittest is a little difficult on the learning curve, because it needs to be encapsulated by inheriting test case classes (TestCase), so you need to know enough about object-oriented knowledge; and binding to classes means that if you want to achieve customization or module decoupling, you may need to spend more time on design partition.
So, in order to make testing simple and extensible, a testing framework called pytest was born in the Python community. With pytest, we don't have to think about how to implement our tests based on TestCase, we just need to keep the logic of our original code unchanged, plus an assert keyword to assert the results, and the rest of the pytest will help us deal with it.
# main.py import pytest raw_data = read_data (...) Def test_myfunc (* args, * * kwargs): do_something () data =. Assert data = = raw_data if _ _ name__ = ='_ _ main__': pytest.main ()
Then we just need to run the main.py file that contains the above code, and we can see the results of the pytest test for us on the terminal console. If the result passes, there will not be too much information, and if the test fails, an error message will be thrown telling the runtime what is in the data.
Although pytest is simple enough, it also provides many practical features (such as dependency injection), which themselves have some conceptual knowledge; but this does not mean to dissuade people who want to use pytest to test their own code, but to give us more choices, so only with a better understanding of these functions and concepts of pytest can we give full play to the power of pytest.
Quickly implement your first Pytest test
After installing pytest through pip install pytest, we can quickly implement our first test.
First of all, we can create a new Python file at will. Here I will name it directly after test_main.py, and then leave the following content in it:
From typing import Union import pytest def add (x: Union [int, float], y: Union [int, float],)-> Union [int, float]: return x + y @ pytest.mark.parametrize (argnames= "x penny yjinghe result", argvalues= [(1MJ 1, 2), (2, 4, 6), (3. 3, 3, 3, 6),]) def test_add (x: Union [int) Float], y: Union [int, float], result: Union [int, float],): assert add (x, y) = result
Then switch the terminal to the path where the file is located, and then run pytest-v. You will see that pytest has passed the parameters to be tested into the test function and achieved the corresponding results:
You can see that we don't have to loop parameters through for repeatedly, and we can also visually see from the results what the specific values of the parameters passed in each test are. Here we can do it only through the mark.parametrize decorator provided by pytest. It also shows that it is easy to get started with pytest, but we need to understand some concepts in this framework a little bit.
The concept and usage of Pytest
Naming
If you need pytest to test your code, first we need to test the functions, classes, methods, modules and even code files that start with test_* or end with * _ test by default, in order to comply with the standard testing conventions. If we remove the test_ from the file name of the example we quickly started, we will find that pytest has not collected the corresponding test cases.
Of course, we can also modify different prefixes or suffixes in the pytest configuration file, as in the official example:
# content of pytest.ini # Example 1: have pytest look for "check" instead of "test" [pytest] python_files = check_*.py python_classes = Check python_functions = * _ check
But usually we can just use the default test prefix. If we only want to select specific test cases or only test modules under specific modules, we can specify them in the form of double colons on the command line, like this:
Pytest test.py::test_demo pytest test.py::TestDemo::test_demo tag (mark)
In pytest, mark tag is a very useful function, through the tag decorator to decorate our object to be tested, so that pytest will operate on our function according to the function of mark when testing.
The official itself provides some preset mark functions, and we only pick the ones that are commonly used.
Parameter test: pytest.parametrize
As in the previous example and its naming meaning, mark.parametrize is mainly used in scenarios where we want to pass different parameters or different combinations of parameters to an object to be tested.
As with our previous test_add () example, we tested:
The case of whether the result is result=2 or not when x is 1 and y is 1
Whether the result is result=6 or not when x is 2 and y is 4.
Whether the result is result=6.3 or not when xx is 3.3 and y is 3.
……
We can also stack and combine the parameters, but the effect is similar:
Import pytest @ pytest.mark.parametrize ("x", [0,1]) @ pytest.mark.parametrize ("y", [2,3]) @ pytest.mark.parametrize ("result", [2,4]) def test_add (XMague y, result): assert add (XMague y) = = result
Of course, if we have enough parameters, pytest can still test everything for us as long as we write it into parametrize. So we don't have to write extra code anymore.
However, it is important to note that there are some differences between parametrize and an important concept, fixture, which we will talk about later: the former mainly simulates how the object to be tested will output under different parameters, while the latter will test what the result will be when the parameters or data are fixed.
Skip the test
In some cases, our code contains parts that are specific to different situations, versions, or compatibility, so the code is usually applicable only if certain conditions are met, otherwise there will be problems with execution. but the cause of this problem is not due to code logic, but caused by the system or version information, then it is obviously unreasonable as a use case test or test failure at this time. For example, I wrote a compatibility function, add (), for Python 3.3, but there will be problems when the version is greater than Python 3.3.
Therefore, in order to adapt to this situation, pytest provides two tags, mark.skip and mark.skipif, and of course the latter uses more.
Import pytest import sys @ pytest.mark.skipif (sys.version_info > = (3Magne3)) def test_add (xrecoy, result): assert add (xmemy) = = result
So when we add this flag, we use the sys module before each test case to determine whether the version of the Python interpreter is greater than 3.3.3.If it is greater, it will be skipped automatically.
Expected exception
As long as the code is written by people, there is bound to be an inevitable BUG. Of course, there are some BUG that we can expect as code writers. This kind of special BUG is often called Exception. For example, we have a division function:
Def div (x, y): return x / y
But according to our algorithm, we know that the divisor cannot be 0; therefore, if we pass y _ zero, we will inevitably throw a ZeroDivisionError exception. So the usual practice is to use try...exception to catch the exception and throw the corresponding error message (we can also use the if statement to determine the condition, and finally throw the error):
Def div (x, y): try: return x except ZeroDivisionError y except ZeroDivisionError: raise ValueError ("y cannot be 0")
Therefore, at this point during testing, if we want to test whether the exception assertion can be thrown correctly, we can use the raises () method provided by pytest:
Import pytest @ pytest.mark.parametrize ("x", [1]) @ pytest.mark.parametrize ("y", [0]) def test_div (x, y): with pytest.raises (ValueError): div (x, y)
Note here that we need to assert that we are capturing the ValueError that we specified to throw after the ZeroDivisionError was thrown, not the former. Of course, we can use another tagged method (pytest.mark.xfail) to combine with pytest.mark.parametrize:
@ pytest.mark.parametrize, [pytest.param (1 None 0, None, marks=pytest.mark.xfail (raises= (ValueError),]) def test_div_with_xfail (x recorder y result): assert div (x field y) = = result
In this way, the failed part is directly marked during the test.
Fixture
Of the many features of pytest, the most amazing is fixture. Most people literally translate fixture into the word "fixture", but if you have ever understood the Java Spring framework, it will be easier for you to understand it as something similar to an IoC container in practice, but I think it may be more appropriate to call it a "vehicle".
Because usually the role of fixture is to provide our test cases with a fixed, freely detachable general object, which itself carries something in it like a container; when we use it for our unit testing, pytest will automatically inject the corresponding object into the vehicle.
Here I simulate a little bit of what happens when we use the database. Usually we create a database object through a database class, then connect to connect () before using it, then operate, and finally disconnect close () to release resources.
# test_fixture.py import pytest class Database (object): def _ init__ (self, database): self.database = database def connect (self): print (f "\ n {self.database} database has been connected\ n") def close (self): print (f "\ n {self.database} database has been closed\ n") def add (self Data): print (f "`{data}` has been add to database.") Return True @ pytest.fixture def myclient (): db = Database ("mysql") db.connect () yield db db.close () def test_foo (myclient): assert myclient.add (1) = = True
In this code, the key to the implementation of the vehicle is the @ pytest.fixture line of decorator code, through which we can directly use a function with resources as our vehicle, and pass the function's signature (that is, naming) as a parameter to our test case, and pytest will automatically help us inject it when running the test.
During the injection process, pytest will help us execute the connect () method of the db object in myclient () to call the method that simulates the database connection, and after the test is complete, it will help us call the close () method to release resources.
The fixture mechanism of pytest is a key to enable us to realize complex testing. Imagine that we only need to write a fixture with test data, and we can use it many times in different modules, functions or methods.
Of course, pytest provides us with the situation of adjustable vehicle scope (scope), from small to large:
Function: function scope (default)
Class: class scope
Module: module scope
Package: package scope
Session: session scope
The vehicle will be born and destroyed with the life cycle of the scope. So if we want to increase the scope of the vehicle we create, we can add one more scope parameter to @ pytest.fixture () to increase the scope of the vehicle.
Although pytest officially provides us with some built-in general-purpose vehicles, we usually have more custom vehicles. So we can all put it into a file called conftest.py for unified management:
# conftest.py import pytest class Database: def _ _ init__ (self, database): self.database:str = database def connect (self): print (f "\ n {self.database} database has been connected\ n") def close (self): print (f "\ n {self.database} database has been closed\ n") def add (self, data): print (f "\ n` {data} `has been add to database.") Return True @ pytest.fixture (scope= package) def myclient (): db = Database ("mysql") db.connect () yield db db.close ()
Because we declare that the scope is the same package, we modify the previous test_add () test part slightly under the same package to inject and use it directly without explicitly importing the myclient vehicle:
From typing import Union import pytest def add (x: Union [int, float], y: Union [int, float],)-> Union [int, float]: return x + y @ pytest.mark.parametrize (argnames= "x penny YJ result", argvalues= [(1) Union [int, float], y: Union [int, float] Result: Union [int, float], myclient): assert myclient.add (x) = = True assert add (x, y) = = result
Then run pytest-vs to see the result of the output:
Pytest extension
For everyone who uses the framework, the ecology of the framework will indirectly affect the development of the framework (such as Django and Flask). While pytest leaves enough room for expansion, coupled with many easy-to-use features, it is possible to use many plug-ins or third-party extensions to use pytest.
According to the official plug-in list, there are about 850 plug-ins or third-party extensions in pytest. We can find the Plugin List page in pytest's official Reference. Here I only pick two plug-ins related to our next chapter practice:
We can install the relevant plug-ins as needed and then install them through the pip command. Finally, we only need to simply refer to the use documentation of the plug-in to write the corresponding part, and finally start the pytest test.
Pytest-xdist
Pytest-xdist is a pytest plug-in maintained by the pytest team and allows us to conduct parallel testing to improve our testing efficiency, because if our project is of a certain scale, then there must be a lot of testing. Because pytest collects test cases in a synchronous way, it can not make full use of multicore.
So through pytest-xdist we can greatly accelerate the speed of each round of testing. Of course, we only need to add the-n parameter when starting the pytest test, where the number of CPU can be directly replaced by auto, which will automatically help you adjust the number of CPU cores used in the pytest test:
Pytest-asyncio
Pytest-asycnio is an extension that allows pytest to test asynchronous functions or methods, and is also officially maintained by pytest. Since most of the current asynchronous frameworks or libraries are often implemented based on Python's official asyncio, pytest-asyncio can further integrate asynchronous tests and asynchronous vehicles into test cases.
We directly decorate the asynchronous function or method with the @ pytest.mark.asyncio tag in the tested function or method, and then test it:
Import asyncio import pytest async def foo (): await asyncio.sleep (1) return 1 @ pytest.mark.asyncio async def test_foo (): r = await foo () assert r = 1 Thank you for your reading. This is the content of "how to use the Pytest testing Framework". After studying this article, I believe you have a deeper understanding of how to use the Pytest testing Framework. The specific use situation still needs to be verified by practice. Here is, the editor will push for you more related knowledge points of the article, welcome to follow!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.