上篇博客中讲述了什么是接口测试,已经自动化接口测试流程,这篇博客总结如何实现接口自动化测试
(一)requests
requests库是Python对HTTP通信的一个工具,将http协议操作封装成简单的接口,能够让我们高效的编写各种网络自动化任务
安装requests库
我们可以通过pip命令来进行安装,在控制板中输入
后面的版本号可以不必一致,可以去官网查看
我们看上面successfully就是安装成功了
那我们也可以通过另一个命令来查看当前项目有什么库
接下来我们看如何使用这个库
首先就是获取一个http中的get请求到指定url
我们看一下这个方法的参数,url都不陌生就是我们要请求的网址,params就是我们的参数类似于我们在postman中这个位置填的数据
**kwargs是常见可选参数,就比如我们可以在里面设置请求头,cookie等
使用时只需要调用get接口
传入对应参数,就会给我们返回一个response对象,该对象包含了服务器给我们返回的一系列信息
当然之前我们学习请求形式不止get请求还有post,put等请求,我们也可以调用对应方法来进行url请求
我们再点进去看他们的源码
我们发现调用的都是request方法,所以我们想访问url时,也可以直接调用request方法,只不过需要我们手动去传入method用什么方式访问
我们这里举个实例,我们选择用一个博客详情的接口来举例
其实和我们使用postman类似
但在上面我们传递参数发现,可以使用params,json,data来传参
那么三者有什么区别?
parasms:使用params传参,参数是在url上体现的传递的是简单的键值对,常用于get请求来获取对应数据,因为可见所以她不适合传入敏感数据
data:使用data传参,参数在data中,常用于put或者post请求传递的是表单类型的数据,但是data是不支持嵌套的。键值对用 &
连接
json:使用json传参,参数在data中,常用于put或者post请求传递的是json的数据,json是支持嵌套的
上面就是request库的一些操作及使用, requests库主要是发送http请求,但是对于测试执行和管理就没怎么涉及到,接下来我们会使用pytest来完成测试的组织,执行,管理功能。
(二)pytest
我们这里选择使用pytest接口来实现自动化接口测试,除了pytest我们也可使用其他框架比如unnittest或者robot framework等
我们选择pytest是因为她的语法较为简单,且插件比较多,我们可以通过下载插件来完成各种功能
pytest的安装
我们安装pytest有个版本对应表,不同版本的pytest有最低适配的python版本
下载操作和刚刚我们下载requests操作类似,都是使用pip install操作来下载
同样我们可以使用pip list来看
安装好后,我们来看一下有没有pytest框架对于代码编写的区别
有pytest
没有pytest
我们发现如果没有pytest框架,我们要执行一个方法,需要一个main函数,并在函数中调用,若我们有了pytest框架,我们可以直接运行该方法,但是我们需要遵守pytest的命名规范,下面就来说一下怎样命名是可以被pytest识别到的
pytest框架默认命名格则
文件名:文件名需要以test_**来命名或者**_test来命名
测试类:测试类必须以Test开头,并且不能有__init__方法
测试方法:测试方法必须以test**开头
init和构造方法
注:测试类中不推荐有init方法(类似于java中的构造方法),但是我们可以定义
之所以不推荐是因为init方法是无法访问pytest fixtures的(执行顺序为:init方法->fixtures->其他方法)
而且构造函数只负责初始化并没有对应清理机制,且每次测试方法都会创建新的实例。
这一点要做一下区分,此时在pytest中的init方法和构造方法是有很大区别的
就比如我们局一个简单的例子,我们在python正常类中写一个init方法和一个类方法,之后我们实例化一个对象并多次调用类方法
我们发现只会调用一次构造函数,在多次调用方法时,也不会再调用构造函数,而当我们在pytest中创建init方法,我们再来看一下
他的输出是这样的
init方法在每次测试方法执行前都会重新调用一次(这是因为每个测试方法都是新的实例,测试之间不共享状态)
以上就是我们为什么不推荐使用init方法,为了处理init方法初始化的作用,pytest给我们提供了其他的初始化方法比如:setup/teardowm或者使用fixture 这些在之后我们也会再讲
注:上面在pytest中使用init方法仅限pytest 4之后的版本,在pytest 4之前版本,是禁止任何类有init方法的
pytest命令参数
pytest提供了很多命令行来控制测试的执行,以下是一些常用的命令航参数以及说明
pytest:在当前目录和其子目录下找到符合命名规范的类和方法并运行测试
pytest -v:增加输出的详细程序
pytest -s:显示测试中的print语句
pytest test_module.py :运行指定的测试模块
pytest test_dir:运行指定目录下的所有测试
还有一些其他的命令
我们在自己项目中使用pytest命令如下:
会自动执行符合pytest命名规则的方法
但是我们看显示框,发现信息很少,如果我们要显示更全面的信息,需要使用-v
但是上面并没有打印出111,这是因为如果我们想显示print语句,我们需要-s
当我们想指定测试方法执行时,我们需要手动指定,或者手动点击
手动指定
我们发现每次要执行指令时,我们要手动输入很长的一段命令,如何解决这个问题呢?
我们就需要把相关配置参数,统一放到pytest配置文件中
pytest配置文件
需要手动在当前项目下创建pytest.ini文件(文本文件),以下时一些常见的配置选项
在配置文件中我们可以这样指定
这样就是说明,我们默认在后面加上-vs,搜索的py文件为test_*,搜索的类为Test*
此时我们执行pytest,我们发现可以自动打印出原来pytest -vs才能执行出的表现
当我们更改搜索的py文件名称时我们来看看表现
但是同时.ini是不区分大小写的,如果我们想区分大小写,我们可以使用yml文件(之后会说)
前后置方法
上面我们说pytest里面不推荐有些版本甚至不能使用init初始化
那如果我们想执行测试用例前后执行一些额外操作,我们就需要用pytest提供的三种额外方法做前后置操作
setup_method和teardown_method:这两个方法用于类中每个测试方法的前置与后置操作
setup_class和teardowm_class:这两个方法用于整个测试类的前置和后置操作
fixture:使用fixture是比较推荐到方式,之后会详细说到fixture的使用
setup_method和teardown_method我们来举个例子
def setup_method(self):
print("setup method")
def teardown_method(self):
print("teardown method")
def test_01(self):
print("True example")
def test_02(self):
print("True example02")
就比如上述代码,我们使用setup_method和teardown_method
我们看现象,在测试方法前我们调用setup_method方法,在测试方法后调用teardown_method方法,每个测试方法都会进行一次调用
setup_class和teardown_class我们同样举个例子看现象
def test_01(self):
print("True example01")
def test_02(self):
print("True example02")
def setup_class(self):
print("setup method")
def teardown_class(self):
print("teardown method")
我们看到在一个类内,只会调用一次setup/teardown方法
断言
断言能够帮我们检测程序的状态是否符合我们的预期,如果断言失败,那么python解释器会给我们抛出一个AssertionError异常,pytest中允许我们使用python中的断言语句来验证预期和值
条件必须是一个布尔表达式,错误信息选填
这里简单写几个示例
class Test01():
#断言整数
a=1
b=2
assert a==b
#断言字符串
s1="str"
assert s1=="str"
#断言列表
expectList=[1,'aaa',3.21]
realList=[1,'aaa',3.21]
assert expectList==realList
#断言元组
expectTuple=(1,'aaa',3.21)
realTuple=(1,'aaa',3.21)
assert expectTuple==realTuple
#断言字典
expectDict={'a':1,'b':2,'c':3}
realDict={'a':1,'b':2,'c':3}
assert expectDict==realDict
#断言集合
expectSet={'a','b','c'}
realSet={'a','b','c'}
assert expectSet==realSet
在执行后,我们看到报错信息还是非常的明显的,会告诉我们那个断言错误了
这样我们可以利用断言来判断接口的返回值是否符合我们的预期
def test1():
url = "http://jsonplaceholder.typicode.com/posts/1"
r = requests.get(url=url)
expect_data = {
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio
reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et
cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt
rem eveniet architecto"
}
print(r.json())
assert r.json() == expect_data
assert r.json()['userId'] == 1
如果结果不符合预期就会断言失败
参数化
上述的代码,所有参数都是固定的,那么有没有一种方式可以像方法一样传参,让过程更加灵活可控呢?
我们可以用pytest内置的pytest.mark.parametrize来对测试函数的参数进行参数化
直接看代码
@pytest.mark.parametrize("data",(1,2,3,4,5))
def test_01(self,data):
print("data: ",data)
我们给方法设定一个参数data,然后通过pytest
@pytest.mark.parametrize("data,result",[(3+5,8),(1+2,3)])
def test_01(self,data,result):
assert data==result
.mark.parametrize中第一个参数为要传的参数,第二个为参数的值
我们不止可以设定一个参数,也可以设置多个参数
代码如下:
此外除了可以在方法上使用这个参数化之外,在类上同样可以使用参数化
@pytest.mark.parametrize("data,result", [(3 + 5, 8), (1 + 2, 3)])
class Test01():
def test_01(self, data, result):
assert data == result
def test_02(self, data, result):
print(data,result)
现象如下,我们发现都会执行到
此时可能就会有疑问,当我们设定了同样的参数在类和方法上,会发生什么?可以来执行看下
@pytest.mark.parametrize("data,result", [(3 + 5, 8), (1 + 2, 3)])
class Test01():
def test_01(self, data, result):
assert data == result
@pytest.mark.parametrize("data,result", [(1 + 5, 6), (2 + 2, 4)])
def test_02(self, data, result):
print(data,result)
上述告诉我们多次尝试对同一个参数进行重复参数化问题,解决办法也很简单
1.我们可以把类层和方法层参数合并到同一个层级
2.类层和方法层使用不同的参数名
除了可以手动指定参数外,我们也可以使用方法返回值来当作参数,只需要把手动指定参数的地方换成函数调用即可,类似于这样
注:当我们设定了参数化找不到对应参数时,是会报错的

fixture
用来提供测试函数所需要的资源或者上下文,有点类似于AOP面向切面编程的思想
以下是fixture的一些概念及使用场景
我们来看fixture的基础使用
class Test01():
def fixture01(self):
print("fixture1111")
def test02(self):
Test01.fixture01(self)
print("test02")
class Test01():
@pytest.fixture()
def fixture01(self):
print("fixture1111")
def test02(self,fixture01):
# Test01.fixture01(self)
print("test02")
我们看上述两个代码,第一种是在第二个方法中调用第一个方法
而第二种方法,是直接将函数名作为参数进行调用
我们看结果都是一样的
当然也可以在第一种的fixture方法上加上注解,也可以正常使用
那传到参数和方法中调用有什么区别呢?
除了基本使用fixture,也可以嵌套的使用fixture
class Test01():
@pytest.fixture()
def fixture01(self):
return "01"
@pytest.fixture()
def est02(self,fixture01):
# Test01.fixture01(self)
return fixture01
def test03(self, est02):
# Test01.fixture01(self)
print(est02)
与函数嵌套很类似
不止嵌套,我们同样可以在参数列表中调用多个fixture注解修饰的函数
class Test01():
@pytest.fixture()
def fixture01(self):
return "01"
@pytest.fixture()
def est02(self,fixture01):
# Test01.fixture01(self)
return "010203"
def test03(self, fixture01,est02):
# Test01.fixture01(self)
assert fixture01 in est02
fixture不止传参提供资源这一个用处,之前说过它可以用作于上下文
此时我们需要在代码中加上yield,这个主要是为了在我们运行测试时,确保它能够正确的自我清理,以便他不会干扰到其他的测试
我们使用yield而不是return,这样我们可以运行一些代码后,把对象返回给其他请求方法
但与return不同的是,该fixture的任何拆解代码要放在yield之后
一旦pytest确定了fixture,他会运行所有的fixture知道返回或者yield,然后执行下一个fixture重复此工作
测试完成后,pytest会逆向遍历fixture,对于每个yield后的fixture,运行yield语句之后的代码
class Test01():
@pytest.fixture()
def fixture01(self):
print("start")
yield
print("stop")
def test03(self, fixture01):
# Test01.fixture01(self)
print("test03")
我们预期的表现是先打印对应方法中调用的fixture在yield前的代码,然后执行函数体,最后执行yield后的代码,看现象
带参数的fixture
看下这个注解的源码,我们看到有很多参数,但是我们上面在使用时,暂时没涉及到,这里来讲一下参数都有什么用
scope:
function:每个测试函数会调用fixture(也是默认的值)
class:在同一个测试类中共享这个fixture
class Test01():
@pytest.fixture(scope="class")
def fixture01(self):
print("start")
yield
print("stop")
def test05(self):
# Test01.fixture01(self)
print("test03")
def test03(self, fixture01):
# Test01.fixture01(self)
print("test03")
def test04(self):
# Test01.fixture01(self)
print("test04")
按顺序先执行test05方法,因不涉及到fixture方法所以还没有调用,到了test03,先执行fixture方法中yield前的部分,等到class结束后执行yield后的部分
module:在一个文件里共享这个fixture
session:整个测试会话中共享这个fixture
autouse:默认参数为false,代表我们需要显示传入,才会调用,如果设置为true,就代表每个测试函数都会自动的调用fixture
params:用于参数化,fixture支持列表,每个参数都会让fixture执行一次,类似for循环
ids:与params搭配使用,为每个参数化实例指定标识符
类似这样
@pytest.fixture(
params=[1, 2, 3],
ids=["测试值1", "测试值2", "测试值3"]
)
def number(request):
return request.param
name:用来给fixture方法设定一个名称,如果使用name,则在测试函数中需要使用这个名称来引用fixture
剩下的命令,下一篇再说