tep is a testing tool that integrates third-party packages based on the pytest testing framework, provides project scaffolding, and helps to quickly realize the implementation of automated projects by writing Python code.
In the tep project, the automated test cases are placed tests
in the directory, and each .py
file is independent of each other without dependencies. One file is one test case, separated from each other.
Although use cases can also refer to each other, unless it is absolutely necessary, it is generally not recommended to do so. It will lead to the whole body, and it will be difficult to maintain later.
The code writing of the use case is written from top to bottom, which is the same as the conventional writing method of pytest/unittest/script. There will be no learning costs, and generally there will be no problems. Problems with cost may be environment variables and fixtures, because tep is encapsulated and provides a sharing method of dependency injection, and fixture is a knowledge point that is difficult for pytest to understand, so it is necessary to talk about tep environment variables and fixtures through this article 、 The relationship between the three use cases helps to understand, so that the pytest automation project can be realized more flexibly and smoothly with the help of tep.
If you don't use environment variables and fixtures
If you don't use environment variables and fixtures, it's totally possible! For example, create a new script tests
under login_test.py
:
from tep.client import request
def test():
response = request("post",
url="https://qa.com/api/users/login",
headers={"Content-Type": "application/json"},
json={
"username": "admin",
"password": "123456",
}
)
assert response.status_code < 400
The request interface https://qa.com/api/users/login
asserts that the response status code is less than 400. Here comes the problem: url
fixed, what should I do if I need to switch between two environments qa
and ?release
to parameterize
Whether you are doing automated testing or performance testing, you will come into contact with the word parameterization. It refers to defining the fixed data (hard-coded) in the code as variables, so that the data is different each time it is run, and the fixed data becomes dynamic data. Sources of dynamic data are variables, databases , external files, etc. The type of dynamic data is generally a constant string, or a function, such as JMeter's function assistant, or dependency injection, such as pytest's fixture.
Dependency injected fixtures
"Dependency injection is a technical form of Inversion of Control (IoC, Inversion of Control)", this sentence comes from Wikipedia, I don't know what it means, draw a picture to express it briefly:
It means that if you give client
one injector
, client
you can use it without doing anything service
.
pytest's fixture implements dependency injection, allowing us to introduce fixtures to add some extra stuff without modifying the test code.
For url
example, the domain name needs to be parameterized, and the domain name is different in different environments, so tep makes it a fixture and introduces it through function parameters:
from tep.client import request
from tep.fixture import *
def test(url): # 引入fixture
response = request("post",
url=url("/api/users/login"),
headers={"Content-Type": "application/json"},
json={
"username": "admin",
"password": "123456",
}
)
assert response.status_code < 400
tep.fixture.url
It is defined as follows:
@pytest.fixture(scope="session")
def url(env_vars):
def domain_and_uri(uri):
if not uri.startswith("/"):
uri = "/" + uri
return env_vars.domain + uri
return domain_and_uri
If you understand it at a glance, congratulations, if you are confused at a glance, it doesn't matter. I will take the time to explain it clearly, it is very important!
Treat fixtures as variables
Although by definition, a fixture is def
a function defined with keywords, it can be understood as a variable. for example:
import pytest
@pytest.fixture
def name():
return "dongfanger"
The usage of the general function is to add parentheses to the function name and pass it name()
to get it "dongfanger"
. The fixture is different, the above definition can be understood as:
name = "dongfanger"
Assign "dongfanger"
the value to name
, fixture name = return value. name
Get it through variables "dongfanger"
.
Since it is a variable, you can assign any value, str
, function
, class
, object
any. For example, define a function inside the fixture:
import pytest
@pytest.fixture
def who():
def get_name():
return "dongfanger"
return get_name
It is understood as assigning the function name get_name
to the fixture name variable:
who = get_name
get_name
Is a function name, you need to add parentheses get_name()
to get it "dongfanger"
. who
You must also pass who()
to get it "dongfanger"
. Let's see tep.fixture.url
if it's clearer:
@pytest.fixture(scope="session")
def url(env_vars):
def domain_and_uri(uri):
if not uri.startswith("/"):
uri = "/" + uri
return env_vars.domain + uri
return domain_and_uri
It is understood as assigning the function name domain_and_uri
to the fixture name variable:
url = domain_and_uri
When using it, url("/api")
get the spliced result of the domain name and uri.
Line 2 def url(env_vars):
also has a parameter env_vars
, and the explanation continues.
The fixture parameter is another fixture
The parameters of the fixture can only be other fixtures. for example:
import pytest
@pytest.fixture
def chinese_name():
return "东方er"
@pytest.fixture
def english_name(chinese_name):
return "dongfanger"
Call english_name
, pytest will first execute other fixtures in the parameters chinese_name
, and then execute itself english_name
.
If you tep.fixture.url
look at it in two steps, it will be very clear. The first step:
@pytest.fixture(scope="session")
def url(env_vars):
func = None
return func
Step two:
@pytest.fixture(scope="session")
def url(env_vars):
func = None
def domain_and_uri(uri):
if not uri.startswith("/"):
uri = "/" + uri
return env_vars.domain + uri
func = domain_and_uri
return func
environment variable
tep.fixture.url
The parameter is another fixture env_vars
environment variable, which is defined as follows:
from tep.fixture import *
@pytest.fixture(scope="session")
def env_vars(config):
class Clazz(TepVars):
env = config["env"]
"""Variables define start"""
# Environment and variables
mapping = {
"qa": {
"domain": "https://qa.com",
},
"release": {
"domain": "https://release.com",
}
# Add your environment and variables
}
# Define properties for auto display
domain = mapping[env]["domain"]
"""Variables define end"""
return Clazz()
Just look at the middle comment """Variables define start"""
to """Variables define end"""
the part. url
The parameterized domain name is here, and mapping
the dictionary establishes the mapping between the environment and variables, and obtains different variable values according to different environment keys.
config
The role of fixture is to readconf.yaml
the configuration in the file.
There are many ways to parameterize. JMeter provides 4 kinds of parameterization ways. The fixture of tep env_vars
draws on the user-defined variables of JMeter:
env_vars.put()
andenv_vars.get()
borrowed from JMeter BeanShellvars.put()
andvars.get()
.
Example: testing multiple URLs
At the end of the talk, an idea was formed. Through practical examples, see how environment variables, fixtures, and use cases are used to deepen the impression. If qa
the environment has two websites, the school side and the institution side, both scripts need to be used.
The first modification env_vars
, edit fixture_env_vars.py
:
"""Variables define start"""
# Environment and variables
mapping = {
"qa": {
"domain": "https://qa.com",
"domain_school": "https://school.qa.com", # 新增
"domain_org": "https://org.qa.com" # 新增
},
"release": {
"domain": "https://release.com",
"domain_school": "https://school.release.com" # 新增
"domain_org": "https://org.release.com" # 新增
}
# Add your environment and variables
}
# Define properties for auto display
domain = mapping[env]["domain"]
domain_school = mapping[env]["domain_school"] # 新增
domain_org = mapping[env]["domain_org"] # 新增
"""Variables define end"""
Added 6 lines of code, defining env_vars.domain_school
and env_vars.domain_org
.
The second step is to define fixtures and create new ones fixture_url.py
:
@pytest.fixture(scope="session")
def url_school(env_vars):
def domain_and_uri(uri):
if not uri.startswith("/"):
uri = "/" + uri
return env_vars.domain_school + uri
return domain_and_uri
@pytest.fixture(scope="session")
def url_org(env_vars):
def domain_and_uri(uri):
if not uri.startswith("/"):
uri = "/" + uri
return env_vars.domain_org + uri
return domain_and_uri
Refer to tep.fixture.url
, modify env_vars.domain
to env_vars.domain_school
and env_vars.domain_org
, add 2 new fixtures url_school
and url_org
.
Going a step further, perhaps defining fixtures login_school
and login_org
flexible options.
summary
This article explains step by step the relationship between tep environment variables, fixtures and use cases, focusing on tep.fixture.url
the explanation. As long as you understand it, the overall relationship is very clear. The reason why fixtures are used is that multiple people collaborate and share. We need to use functions written by others and reuse return values. Some students are used to defining function parameters. It’s okay if the parameters don’t change. All use cases will report errors. Fixtures limit this very well. It cannot pass parameters by default. Although parameters can be passed by defining internal functions, it is not recommended to do so. I would rather add redundant code and define multiple Fixture is also better than code coupling. The second reason is import
the problem. pytest will automatically find conftest.py
the fixture in it, and tep will further automatically find fixtures
the fixture and import it into it conftest.py
. It can import
be used without needing it, reducing import
the code and avoiding the possible circular import problem.