优点:
工具:工具使用简单,学习成本低;
代码:灵活,逼格比较高(注意:也可以面试管说逼格比较高。还有面试时,可以夸讲面试官,说他问题问得好)
缺点:
工具:不灵活,
代码:学习成本比较高
什么时候选择工具,什么时候选择代码?
如果项目中,定制化需求比较少,测试人员编码水平不高时,可以选择工具,例如:postman、jmeter等
如果项目中,定制化需求比较多,测试人员编码水平都还可以时,可以选择代码,例如:python+requests+unittest
创建项目目录结构
安装依赖包
requests,pymysql,parameterized在File->Setting->Project Intepreter->点击+号 进行安装
1 编写日志的代码:写在app.py中
日志的等级:5个等级(了解)
低于日志等级的日志都不会打印
#app.py 初始化日志的函数代码
# 获取当前项目路径
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
# 初始化日志函数
def init_logging():
# 创建日志器
logger = logging.getLogger()
# 设置日志等级
logger.setLevel(logging.INFO)
# 设置处理器
# 设置控制台处理器
sh = logging.StreamHandler()
# 设置文件处理器
# TimedRotatingFileHandler 可以用来帮助我们切分日志:按照时间来设置日志
filename = BASE_DIR + "/log/ihrm.log"
fh = logging.handlers.TimedRotatingFileHandler(filename, when='M', interval=1, backupCount=7)
# 设置格式化器
fmt = '%(asctime)s %(levelname)s [%(name)s] [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s'
formatter = logging.Formatter(fmt)
# 将格式化器添加到处理器当中
sh.setFormatter(formatter)
fh.setFormatter(formatter)
# 将处理器添加到日志器
logger.addHandler(fh)
logger.addHandler(sh)
#api.__init__.py
import app
import logging
# 初始化日志
# 为什么要在api.__init__.py中初始化日志呢?
# 这是因为,我们后面进行接口测试时,都会调用封装的API接口,调用时,会自动运行__init__.py函数,初始化日志器,从而实现,自动初始化日志的功能
app.init_logging()
logging.info("TEST日志器能不能正常工作")
#app.py部分代码
HOST = "http://182.92.81.159"
HEADERS = {"Content-Type":"application/json"}
在script中新增test_login.py文件,用于实现登陆接口测试用例
#scritp.test_login.py
import unittest,logging
from api.login_api import LoginApi
class TestIHRMLogin(unittest.TestCase):
def setUp(self) -> None:
pass
@classmethod
def setUpClass(cls) -> None:
# 初始化登陆类
cls.login_api = LoginApi()
def tearDown(self) -> None:
pass
@classmethod
def tearDownClass(cls) -> None:
pass
def test01_login_success(self):
# 调用封装的登陆接口
response = self.login_api.login('13800000002','123456')
# 接收返回的json数据
jsonData = response.json()
# 调试输出登陆接口返回的数据,日志输出只能用作为{}占位符
logging.info("登陆成功接口返回的数据为: {}".format(jsonData))
# 断言
self.assertEqual(200, response.status_code) # 断言响应状态码
self.assertEqual(True, jsonData.get("success")) # 断言success
self.assertEqual(10000, jsonData.get("code")) # 断言code
self.assertIn("操作成功", jsonData.get("message")) # 断言message
分析登陆成功的代码,发现登陆成功中,断言有4行
# 断言
self.assertEqual(200, response.status_code) # 断言响应状态码
self.assertEqual(True, jsonData.get("success")) # 断言success
self.assertEqual(10000, jsonData.get("code")) # 断言code
self.assertIn("操作成功", jsonData.get("message")) # 断言message
这4行中可以考虑利用封装技术优化成一行
封装:减少代码冗余,
思路:1.我们的断言代码中,self,response,都是高度重复的代码,可以优化。
2.剩余4个预期结果,都是会动态变化的,所以也可以优化
优化方式:把self,response,4个断言数据作为参数传递给通用断言类处理。
实现代码:通用断言工具类写在utils.py中
# utils.py
# 编写断言代码函数
# 可能的问题:self,response为什么可以作为参数传递?
# 答:这是因为self,response都是对象
def assert_common(self, response, http_code, success, code, message):
self.assertEqual(http_code, response.status_code) # 断言响应状态码
self.assertEqual(success, response.json().get("success")) # 断言success
self.assertEqual(code, response.json().get("code")) # 断言code
self.assertIn(message, response.json().get("message")) # 断言message
优化后再改写登陆成功案例,至此断言优化完毕
# script.test_login.py
import unittest,logging
from api.login_api import LoginApi
from utils import assert_common
class TestIHRMLogin(unittest.TestCase):
def setUp(self) -> None:
pass
@classmethod
def setUpClass(cls) -> None:
# 初始化登陆类
cls.login_api = LoginApi()
def tearDown(self) -> None:
pass
@classmethod
def tearDownClass(cls) -> None:
pass
def test01_login_success(self):
# 调用封装的登陆接口
response = self.login_api.login('13800000002','123456')
# 接收返回的json数据
jsonData = response.json()
# 调试输出登陆接口返回的数据,日志输出只能用作为{}占位符
logging.info("登陆成功接口返回的数据为: {}".format(jsonData))
# 断言
# self.assertEqual(200, response.status_code) # 断言响应状态码
# self.assertEqual(True, jsonData.get("success")) # 断言success
# self.assertEqual(10000, jsonData.get("code")) # 断言code
# self.assertIn("操作成功", jsonData.get("message")) # 断言message
assert_common(self,
response,
200, True, 10000, "操作成功")
import unittest,logging
from api.login_api import LoginApi
from utils import assert_common
class TestIHRMLogin(unittest.TestCase):
def setUp(self) -> None:
pass
@classmethod
def setUpClass(cls) -> None:
# 初始化登陆类
cls.login_api = LoginApi()
def tearDown(self) -> None:
pass
@classmethod
def tearDownClass(cls) -> None:
pass
def test01_login_success(self):
# 调用封装的登陆接口
response = self.login_api.login('13800000002','123456')
# 接收返回的json数据
jsonData = response.json()
# 调试输出登陆接口返回的数据,日志输出只能用作为{}占位符
logging.info("登陆成功接口返回的数据为: {}".format(jsonData))
# 断言
# self.assertEqual(200, response.status_code) # 断言响应状态码
# self.assertEqual(True, jsonData.get("success")) # 断言success
# self.assertEqual(10000, jsonData.get("code")) # 断言code
# self.assertIn("操作成功", jsonData.get("message")) # 断言message
assert_common(self,
response,
200, True, 10000, "操作成功")
def test02_username_is_not_exist(self):
# 调用封装的登陆接口
response = self.login_api.login('13900000002','123456')
# 接收返回的json数据
jsonData = response.json()
# 输出
logging.info("账号不存在时输出的数据为: {}".format(jsonData))
# 断言
assert_common(self, response, 200, False, 20001, "用户名或密码错误")
def test03_password_is_error(self):
# 调用封装的登陆接口
response = self.login_api.login('13800000002','error')
# 接收返回的json数据
jsonData = response.json()
# 输出
logging.info("密码错误时输出的数据为: {}".format(jsonData))
# 断言
assert_common(self, response, 200, False, 20001, "用户名或密码错误")
def test04_username_have_special_char(self):
# 调用封装的登陆接口
response = self.login_api.login('!@#$%^&*()_-+=','error')
# 接收返回的json数据
jsonData = response.json()
# 输出
logging.info("账号输入特殊字符时输出的数据为: {}".format(jsonData))
# 断言
assert_common(self, response, 200, False, 20001, "用户名或密码错误")
def test05_username_is_empty(self):
# 调用封装的登陆接口
response = self.login_api.login('','error')
# 接收返回的json数据
jsonData = response.json()
# 输出
logging.info("账号为空输出的数据为: {}".format(jsonData))
# 断言
assert_common(self, response, 200, False, 20001, "用户名或密码错误")
def test06_password_is_empty(self):
# 调用封装的登陆接口
response = self.login_api.login('13800000002','')
# 接收返回的json数据
jsonData = response.json()
# 输出
logging.info("密码为空输出的数据为: {}".format(jsonData))
# 断言
assert_common(self, response, 200, False, 20001, "用户名或密码错误")
def test07_username_have_chinese(self):
# 调用封装的登陆接口
response = self.login_api.login('1380三毛0000002','123456')
# 接收返回的json数据
jsonData = response.json()
# 输出
logging.info("账号中有中文输出的数据为: {}".format(jsonData))
# 断言
assert_common(self, response, 200, False, 20001, "用户名或密码错误")
def test08_username_middler_have_space(self):
# 调用封装的登陆接口
response = self.login_api.login('1380 0000002','123456')
# 接收返回的json数据
jsonData = response.json()
# 输出
logging.info("账号中有空格输出的数据为: {}".format(jsonData))
# 断言
assert_common(self, response, 200, False, 20001, "用户名或密码错误")
运行结果:
编写代码步骤:
1 初始化测试套件
2 将测试用例添加到测试套件
3 使用HTMLTestRunner运行测试套件,生成测试报告
# run_suite.py
import time
import unittest
import app
from script.test_login import TestIHRMLogin
from tools.HTMLTestRunner import HTMLTestRunner
# 1 初始化测试套件
suite = unittest.TestSuite()
# 2 将测试用例添加到测试套件
suite.addTest(unittest.makeSuite(TestIHRMLogin))
# 3 使用HTMLTestRunner执行测试套件,生成测试报告
report_path = app.BASE_DIR + "/report/ihrm{}.html".format(time.strftime('%Y%m%d %H%M%S'))
with open(report_path, mode='wb') as f:
# 初始化HTMLTestRunner
runner = HTMLTestRunner(f, verbosity=1, title="IHRM人力资源接口测试", description="V1.0.0")
# 使用Runner运行测试套件
runner.run(suite)
运行结果:
测试报告:
报告打开截图:
在data中新建login_data.json文件,参考登陆的入参和断言数据设计数据如下:
[
{
"case_name": "登陆成功",
"mobile": "13800000002",
"password": "123456",
"http_code": 200,
"success": true,
"code": 10000,
"message": "操作成功"
},
{
"case_name": "账号不存在",
"mobile": "13900000002",
"password": "123456",
"http_code": 200,
"success": false,
"code": 20001,
"message": "用户名或密码错误"
},
{
"case_name": "密码错误",
"mobile": "13800000002",
"password": "error",
"http_code": 200,
"success": false,
"code": 20001,
"message": "用户名或密码错误"
},
{
"case_name": "账号输入特殊字符",
"mobile": "!@#$%^&*()_-+=",
"password": "123456",
"http_code": 200,
"success": false,
"code": 20001,
"message": "用户名或密码错误"
},
{
"case_name": "账号为空",
"mobile": "",
"password": "error",
"http_code": 200,
"success": false,
"code": 20001,
"message": "用户名或密码错误"
},
{
"case_name": "密码为空",
"mobile": "13800000002",
"password": "",
"http_code": 200,
"success": false,
"code": 20001,
"message": "用户名或密码错误"
},
{
"case_name": "账号有中文",
"mobile": "13800咸鱼000002",
"password": "123456",
"http_code": 200,
"success": false,
"code": 20001,
"message": "用户名或密码错误"
},
{
"case_name": "账号中间有空格",
"mobile": "138 00000002",
"password": "123456",
"http_code": 200,
"success": false,
"code": 20001,
"message": "用户名或密码错误"
}
]
在utils.py中继续编写读取文件的函数
函数名:read_login_data,代码:
# utils.py
import app
import json
# 编写断言代码函数
def assert_common(self, response, http_code, success, code, message):
self.assertEqual(http_code, response.status_code) # 断言响应状态码
self.assertEqual(success, response.json().get("success")) # 断言success
self.assertEqual(code, response.json().get("code")) # 断言code
self.assertIn(message, response.json().get("message")) # 断言message
def read_login_data():
data_path = app.BASE_DIR + "/data/login_data.json"
with open(data_path, mode='r',encoding='utf-8') as f:
# 加载文件为json格式的数据
jsonData = json.load(f)
# 遍历文件取出其中数据并保存在列表中
p_list = []
for data in jsonData:
mobile = data.get("mobile")
password = data.get("password")
http_code = data.get("http_code")
success = data.get("success")
code = data.get("code")
message = data.get("message")
p_list.append( (mobile,password,http_code,success,code,message) )
print(p_list)
return p_list
if __name__ == '__main__':
# main函数的作用?
# 防止调用这个模块或者类时,自动执行代码
read_login_data()
为了不与未参数化的登陆脚本冲突,单独新增一个参数化的py文件,文件名:test_login_para.py
代码:
# test_lgin_para.py
import unittest, logging
from api.login_api import LoginApi
from utils import assert_common, read_login_data
from parameterized.parameterized import parameterized
class TestIHRMLoginPara(unittest.TestCase):
def setUp(self) -> None:
pass
@classmethod
def setUpClass(cls) -> None:
# 初始化登陆类
cls.login_api = LoginApi()
def tearDown(self) -> None:
pass
@classmethod
def tearDownClass(cls) -> None:
pass
@parameterized.expand(read_login_data)
def test_login(self, mobile,password,http_code,success,code,message):
# 调用封装的登陆接口
response = self.login_api.login(mobile, password)
# 接收返回的json数据
jsonData = response.json()
# 调试输出登陆接口返回的数据,日志输出只能用作为{}占位符
logging.info("登陆接口返回的数据为: {}".format(jsonData))
assert_common(self, response, http_code, success, code, message)
至此登陆模块的自动化接口测试完美完成
使用到的技术:requests,unittest,parameterized,python
实现了:登陆的8个测试用例实现
分析:由于员工模块依赖了登陆接口中返回的令牌,所以测试员工模块时,要先获取到登陆模块中返回的令牌,并添加到员工模块接口的请求头当中。
因此,我们首先第一个接口,应该实现登陆接口,并保存登陆接口的返回的令牌到全局变量中的HEADERS中,然后在后续调用员工模块的接口时引用设置的请求头
这里,我们有好几种实现方案:
第一种: 在员工的增删改查中先调用登陆接口,保存令牌,最后调用员工模块接口,如截图所示:
缺点:是代码集中在一个函数体内,显得冗长;并且,登陆接口在用例中调用,如果有其他测试场景时,也需要重复调用登陆接口。
第二种: 新增一个login.py文件,单独实现登陆接口,并保存令牌到全局变量,然后后续调用添加员工接口时引用即可。
缺点:1 需要对python的顺序执行和封装的概念要理解得更透彻。2 script一般是存放用例,这个登陆既不是测试用例,存放这里有些突兀。但是这个不影响执行,所以我们推荐这一种。3 需要控制代码的执行顺序。
控制代码执行顺序:可以在run_suite.py中通过测试用例的添加顺序控制执行顺序。
其他方式:代码的实现方式是多种多样的,我们这里通过这一种方式入门接口自动化测试框架,虽然对于小白来理解起来有一定难度,但是这也是进步的好机会,不能因为觉得难就放弃。
步骤:
# script.login.py
import unittest, logging
from api.login_api import LoginApi
import app
class Login(unittest.TestCase):
def setUp(self) -> None:
pass
@classmethod
def setUpClass(cls) -> None:
# 初始化登陆类
cls.login_api = LoginApi()
def tearDown(self) -> None:
pass
@classmethod
def tearDownClass(cls) -> None:
pass
def test_login(self):
# 调用封装的登陆接口
response = self.login_api.login('13800000002', '123456')
# 接收返回的json数据
jsonData = response.json()
# 调试输出登陆接口返回的数据,日志输出只能用作为{}占位符
logging.info("登陆成功接口返回的数据为: {}".format(jsonData))
# 获取令牌,并拼接成以Bearer 开头的令牌字符串
token = jsonData.get("data")
# 保存令牌到全局变量
app.HEADERS['Authorization'] = "Bearer " + token
logging.info("保存的令牌是:{}".format(app.HEADERS))
import requests
import app
class EmpApi:
def __init__(self):
self.emp_url = app.HOST + "/api/sys/user"
# 注意:如果我们调用员工管理模块的相关接口时,先执行login.py文件
# 获取到的app.HEADERS 才会是{"Content-Type":"application","Authorization":"Bearer xxxx-xxx-xxxxx"}
self.headers = app.HEADERS
def add_emp(self, username, mobile):
data = {
"username": username,
"mobile": mobile,
"timeOfEntry": "2019-12-02",
"formOfEmployment": 1,
"workNumber": "1234",
"departmentName": "测试",
"departmentId": "1210411411066695680",
"correctionTime": "2019-12-15T16:00:00.000Z"
}
# 发送添加员工接口请求
response = requests.post(self.emp_url, json=data, headers=self.headers)
# 返回添加员工接口的响应数据
return response
在script中新增test_emp.py文件
编写添加员工的测试用例,注意:由于我们不能在这里调试代码,只能通过run_suite.py来进行调试
import logging
import unittest
from api.emp_api import EmpApi
from utils import assert_common
import app
class TestIHRMEmp(unittest.TestCase):
def setUp(self) -> None:
pass
@classmethod
def setUpClass(cls) -> None:
# 初始化员工类
cls.emp_api = EmpApi()
def tearDown(self) -> None:
pass
@classmethod
def tearDownClass(cls) -> None:
pass
def test01_add_emp(self):
# 调用添加员工接口
response = self.emp_api.add_emp("熊猫222123", "18799994321")
# 获取添加员工接口的json数据
jsonData = response.json()
# 输出json数据
logging.info("添加员工接口返回数据为:{}".format(jsonData))
# 断言
assert_common(self, response, 200, True, 10000, "操作成功")
run_suite.py代码
导入部分增加了
from script.login import Login
from script.test_login import TestIHRMLogin
并且添加到测试用例中
suite.addTest(unittest.makeSuite(Login))
suite.addTest(unittest.makeSuite(TestIHRMEmp))
其他不变
# run_suite.py
import time
import unittest
import app
from script.login import Login
from script.test_emp import TestIHRMEmp
from script.test_login import TestIHRMLogin
from tools.HTMLTestRunner import HTMLTestRunner
# 1 初始化测试套件
suite = unittest.TestSuite()
# 2 将测试用例添加到测试套件
suite.addTest(unittest.makeSuite(Login))
suite.addTest(unittest.makeSuite(TestIHRMEmp))
suite.addTest(unittest.makeSuite(TestIHRMLogin))
# 3 使用HTMLTestRunner执行测试套件,生成测试报告
report_path = app.BASE_DIR + "/report/ihrm{}.html".format(time.strftime('%Y%m%d %H%M%S'))
with open(report_path, mode='wb') as f:
# 初始化HTMLTestRunner
runner = HTMLTestRunner(f, verbosity=1, title="IHRM人力资源接口测试", description="V1.0.0")
# 使用Runner运行测试套件
runner.run(suite)
执行结果:
分析:由于查询员工接口的URL中,需要使用添加员工接口返回的ID,所以,我们需要在添加员工的代码中增加保存员工ID的操作,并把ID保存在全局变量。但是员工ID必须提前定义,这里定义在app.py中,变量名为EMP_ID。
app.py中增加员工ID的全局变量。见下方代码的EMP_ID=""
# app.py
# 编写初始化日志的函数
import logging
from logging import handlers
import os
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
HOST = "http://182.92.81.159"
HEADERS = {"Content-Type":"application/json"}
EMP_ID = ""
def init_logging():
# 创建日志器
logger = logging.getLogger()
# 设置日志等级
logger.setLevel(logging.INFO)
# 设置处理器
# 设置控制台处理器
sh = logging.StreamHandler()
# 设置文件处理器
# TimedRotatingFileHandler 可以用来帮助我们切分日志:按照时间来设置日志
filename = BASE_DIR + "/log/ihrm.log"
fh = logging.handlers.TimedRotatingFileHandler(filename, when='M', interval=1, backupCount=7)
# 设置格式化器
fmt = '%(asctime)s %(levelname)s [%(name)s] [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s'
formatter = logging.Formatter(fmt)
# 将格式化器添加到处理器当中
sh.setFormatter(formatter)
fh.setFormatter(formatter)
# 将处理器添加到日志器
logger.addHandler(fh)
logger.addHandler(sh)
添加员工的代码更新为:
# script.test_emp
def test01_add_emp(self):
# 调用添加员工接口
response = self.emp_api.add_emp(username, mobile)
# 获取添加员工接口的json数据
jsonData = response.json()
# 输出json数据
logging.info("添加员工接口返回数据为:{}".format(jsonData))
# 获取员工ID保存在app.EMP_ID中
app.EMP_ID = jsonData.get("data").get("id")
logging.info("员工ID: {}".format(app.EMP_ID))
# 断言
assert_common(self, response, 200, True, 10000, "操作成功")
url: 拼接查询员工接口返回的app.EMP_ID,组成查询员工接口所需的URL
# 封装查询员工接口
def query_emp(self):
# 查询员工接口的URL结构是http://182.92.81.159/api/sys/user/12344343221
# 所以拼接URL时需要加上“/”
url = self.emp_url + "/" + app.EMP_ID
# 返回查询的结果,headers是{"Content-Type":"application/json", "Authorization":"Bearer xxxx"}
return requests.get(url, headers=self.headers)
def test02_query_emp(self):
# 调用查询员工接口
response = self.emp_api.query_emp()
# 获取查询员工接口的返回json数据
jsonData = response.json()
# 输出json数据
logging.info("查询员工接口的返回数据为:{}".format(jsonData))
# 断言
assert_common(self, response, 200, True, 10000, "操作成功")
import requests
import app
class EmpApi:
def __init__(self):
self.emp_url = app.HOST + "/api/sys/user"
# 从全局变量中获取app.HEADERS,但是需要注意的是,只有先运行会保存令牌的登陆接口后
# app.HEADERS的值才会是:
# {"Content-Type":"application/json","Authorizaition":"Bearer xxxx-xxx-xxx"}
self.headers = app.HEADERS
def add_emp(self, username, mobile):
data = {
"username": username,
"mobile": mobile,
"timeOfEntry": "2019-12-02",
"formOfEmployment": 1,
"workNumber": "1234",
"departmentName": "测试",
"departmentId": "1210411411066695680",
"correctionTime": "2019-12-15T16:00:00.000Z"
}
# 发送添加员工接口请求
response = requests.post(self.emp_url, json=data, headers=self.headers)
# 返回添加员工接口的响应数据
return response
# 封装查询员工接口
def query_emp(self):
# 查询员工接口的URL结构是http://182.92.81.159/api/sys/user/12344343221
# 所以拼接URL时需要加上“/”
url = self.emp_url + "/" + app.EMP_ID
# 返回查询的结果,headers是{"Content-Type":"application/json", "Authorization":"Bearer xxxx"}
return requests.get(url, headers=self.headers)
# 封装修改员工接口
def modify_emp(self, username):
# 修改员工的URL应该和查询员工是一样的,所以拼接的时候也需要加上“/”
# http://182.92.81.159/api/sys/user/12344343221
url = self.emp_url + "/" + app.EMP_ID
# 从外部接收要修改的username,拼接成json数据
data = {
"username":username
}
# 返回查询结果
return requests.put(url,
json=data,
headers=self.headers)
# 封装删除员工接口
def delete_emp(self):
url = self.emp_url + "/" + app.EMP_ID
# 调用删除的http接口并返回响应数据
return requests.delete(url, headers=self.headers)
import logging
import unittest
from api.emp_api import EmpApi
from utils import assert_common, DBUtils
import app
from parameterized.parameterized import parameterized
from utils import read_add_emp_data, read_query_emp_data, read_modify_emp_data, read_delete_emp_data
import pymysql
class TestIHRMEmp(unittest.TestCase):
def setUp(self) -> None:
pass
@classmethod
def setUpClass(cls) -> None:
# 初始化员工类
cls.emp_api = EmpApi()
def tearDown(self) -> None:
pass
@classmethod
def tearDownClass(cls) -> None:
pass
def test01_add_emp(self):
# 调用添加员工接口
response = self.emp_api.add_emp("葫芦娃", "13145201314")
# 获取添加员工接口的json数据
jsonData = response.json()
# 输出json数据
logging.info("添加员工接口返回数据为:{}".format(jsonData))
# 获取员工ID保存在全局变量
app.EMP_ID = jsonData.get("data").get("id")
logging.info("员工ID: {}".format(app.EMP_ID))
# 断言
assert_common(self, response, 200, True, 10000, "操作成功")
def test02_query_emp(self, success, code, message, http_code):
# 调用查询员工接口
response = self.emp_api.query_emp()
# 获取查询员工接口的返回json数据
jsonData = response.json()
# 输出json数据
logging.info("查询员工接口的返回数据为:{}".format(jsonData))
# 断言
assert_common(self, response, 200, True, 10000, "操作成功")
def test03_modify_emp(self):
# 调用修改员工接口
response = self.emp_api.modify_emp("全知全能")
# 获取修改员工接口的返回json数据
jsonData = response.json()
# 输出json数据
logging.info("修改员工接口的返回数据为:{}".format(jsonData))
with DBUtils() as db_utils:
# 执行查询语句,查询修改之后的username
sql = "select username from bs_user where id={}".format(app.EMP_ID)
db_utils.execute(sql)
# 获取执行结果
result = db_utils.fetchone()[0]
logging.info("从数据库中查询出的员工的用户名是:{}".format(result))
# 断言
assert_common(self, response, 200, True, 10000, "操作成功")
def test04_delete_emp(self):
# 调用删除员工接口
response = self.emp_api.delete_emp()
# 获取修改员工接口的返回json数据
jsonData = response.json()
# 输出json数据
logging.info("删除员工接口的返回数据为:{}".format(jsonData))
# 断言
assert_common(self, response, 200, True, 10000, "操作成功")
运行结果部分截图:
构造测试数据文件:
在data中新增employee.json文件,文件内容如下:
{
"add_emp": {
"username": "强哥xxx12",
"mobile": "16589987635",
"success": true,
"code": 10000,
"message": "操作成功",
"http_code": 200
},
"query_emp": {
"success": true,
"code": 10000,
"message": "操作成功",
"http_code": 200
},
"modify_emp": {
"username": "王强",
"success": true,
"code": 10000,
"message": "操作成功",
"http_code": 200
},
"delete_emp": {
"success": true,
"code": 10000,
"message": "操作成功",
"http_code": 200
}
}
在utils.py中编写读取员工的函数,一共4个函数:
read_add_emp_data,read_query_emp_data,read_modify_emp_data,read_delete_emp_data
def read_add_emp_data():
path = app.BASE_DIR + "/data/employee.json"
# 打开文件
with open(path, mode='r', encoding='utf-8') as f:
jsonData = json.load(f)
# 由于employee.json是一个字典数据,那么我们可以使用字典的get方法获取其中数据
add_emp_data = jsonData.get("add_emp")
result_list = []
username = add_emp_data.get("username")
mobile = add_emp_data.get("mobile")
success = add_emp_data.get("success")
code = add_emp_data.get("code")
message = add_emp_data.get("message")
http_code = add_emp_data.get("http_code")
result_list.append((username, mobile, success, code, message, http_code))
print("读取添加员工的数据: ", result_list)
return result_list
def read_query_emp_data():
path = app.BASE_DIR + "/data/employee.json"
# 打开文件
with open(path, mode='r', encoding='utf-8') as f:
# 加载Json数据文件
jsonData = json.load(f)
# 由于employee.json是一个字典数据,那么我们可以使用字典的get方法获取
result_list = []
query_emp_data = jsonData.get("query_emp")
success = query_emp_data.get("success")
code = query_emp_data.get("code")
message = query_emp_data.get("message")
http_code = query_emp_data.get("http_code")
result_list.append((success, code, message, http_code))
print("查询员工数据为:", result_list)
return result_list
def read_modify_emp_data():
path = app.BASE_DIR + "/data/employee.json"
# 打开文件
with open(path, mode='r', encoding='utf-8') as f:
# 加载Json数据文件
jsonData = json.load(f)
# 由于employee.json是一个字典数据,那么我们可以使用字典的get方法获取
result_list = []
modify_emp_data = jsonData.get("modify_emp")
username = modify_emp_data.get("username")
success = modify_emp_data.get("success")
code = modify_emp_data.get("code")
message = modify_emp_data.get("message")
http_code = modify_emp_data.get("http_code")
result_list.append((username, success, code, message, http_code))
print("修改员工数据为:", result_list)
return result_list
def read_delete_emp_data():
path = app.BASE_DIR + "/data/employee.json"
# 打开文件
with open(path, mode='r', encoding='utf-8') as f:
# 加载Json数据文件
jsonData = json.load(f)
# 由于employee.json是一个字典数据,那么我们可以使用字典的get方法获取
result_list = []
delete_emp_data = jsonData.get("delete_emp")
success = delete_emp_data.get("success")
code = delete_emp_data.get("code")
message = delete_emp_data.get("message")
http_code = delete_emp_data.get("http_code")
result_list.append((success, code, message, http_code))
print("删除员工数据为:", result_list)
return result_list
在script.test_emp中实现参数化
import logging
import unittest
from api.emp_api import EmpApi
from utils import assert_common, DBUtils
import app
from parameterized.parameterized import parameterized
from utils import read_add_emp_data, read_query_emp_data, read_modify_emp_data, read_delete_emp_data
class TestIHRMEmp(unittest.TestCase):
def setUp(self) -> None:
pass
@classmethod
def setUpClass(cls) -> None:
# 初始化员工类
cls.emp_api = EmpApi()
def tearDown(self) -> None:
pass
@classmethod
def tearDownClass(cls) -> None:
pass
@parameterized.expand(read_add_emp_data)
def test01_add_emp(self, username, mobile, success, code, message, http_code):
# 调用添加员工接口
response = self.emp_api.add_emp(username, mobile)
# 获取添加员工接口的json数据
jsonData = response.json()
# 输出json数据
logging.info("添加员工接口返回数据为:{}".format(jsonData))
# 获取员工ID保存在全局变量
app.EMP_ID = jsonData.get("data").get("id")
logging.info("员工ID: {}".format(app.EMP_ID))
# 断言
assert_common(self, response, http_code, success, code, message)
@parameterized.expand(read_query_emp_data)
def test02_query_emp(self, success, code, message, http_code):
# 调用查询员工接口
response = self.emp_api.query_emp()
# 获取查询员工接口的返回json数据
jsonData = response.json()
# 输出json数据
logging.info("查询员工接口的返回数据为:{}".format(jsonData))
# 断言
assert_common(self, response, http_code, success, code, message)
@parameterized.expand(read_modify_emp_data)
def test03_modify_emp(self, username, success, code, message, http_code):
# 调用修改员工接口
response = self.emp_api.modify_emp(username)
# 获取修改员工接口的返回json数据
jsonData = response.json()
# 输出json数据
logging.info("修改员工接口的返回数据为:{}".format(jsonData))
# 断言
assert_common(self, response, http_code, success, code, message)
@parameterized.expand(read_delete_emp_data)
def test04_delete_emp(self, success, code, message, http_code):
# 调用删除员工接口
response = self.emp_api.delete_emp()
# 获取修改员工接口的返回json数据
jsonData = response.json()
# 输出json数据
logging.info("删除员工接口的返回数据为:{}".format(jsonData))
# 断言
assert_common(self, response, http_code, success, code, message)
在utils.py的末尾增加代码
class DBUtils:
def __init__(self, host="182.92.81.159",
user='readuser',
password='iHRM_user_2019',
database='ihrm'):
self.host = host
self.user = user
self.password = password
self.database = database
# __enter__和__exit__是一对魔法方法,主要要和with结合使用
# 如:with DBUtils() as db:
# 我们就可以在with的代码快中使用db来执行sql语句。这个db就相当于cursor
def __enter__(self):
self.conn = pymysql.connect(self.host, self.user, self.password, self.database)
self.cursor = self.conn.cursor()
return self.cursor
def __exit__(self, exc_type, exc_val, exc_tb):
if self.cursor:
self.cursor.close()
if self.conn:
self.conn.close()
13 使用封装的数据库工具类,在修改员工(test_modify_emp)增加查询数据的代码
# script.test_emp
import logging
import unittest
from api.emp_api import EmpApi
from utils import assert_common, DBUtils
import app
from parameterized.parameterized import parameterized
from utils import read_add_emp_data, read_query_emp_data, read_modify_emp_data, read_delete_emp_data
import pymysql
class TestIHRMEmp(unittest.TestCase):
def setUp(self) -> None:
pass
@classmethod
def setUpClass(cls) -> None:
# 初始化员工类
cls.emp_api = EmpApi()
def tearDown(self) -> None:
pass
@classmethod
def tearDownClass(cls) -> None:
pass
@parameterized.expand(read_add_emp_data)
def test01_add_emp(self, username, mobile, success, code, message, http_code):
# 调用添加员工接口
response = self.emp_api.add_emp(username, mobile)
# 获取添加员工接口的json数据
jsonData = response.json()
# 输出json数据
logging.info("添加员工接口返回数据为:{}".format(jsonData))
# 获取员工ID保存在全局变量
app.EMP_ID = jsonData.get("data").get("id")
logging.info("员工ID: {}".format(app.EMP_ID))
# 断言
assert_common(self, response, http_code, success, code, message)
@parameterized.expand(read_query_emp_data)
def test02_query_emp(self, success, code, message, http_code):
# 调用查询员工接口
response = self.emp_api.query_emp()
# 获取查询员工接口的返回json数据
jsonData = response.json()
# 输出json数据
logging.info("查询员工接口的返回数据为:{}".format(jsonData))
# 断言
assert_common(self, response, http_code, success, code, message)
@parameterized.expand(read_modify_emp_data)
def test03_modify_emp(self, username, success, code, message, http_code):
# 调用修改员工接口
response = self.emp_api.modify_emp(username)
# 获取修改员工接口的返回json数据
jsonData = response.json()
# 输出json数据
logging.info("修改员工接口的返回数据为:{}".format(jsonData))
with DBUtils() as db_utils:
# 执行查询语句,查询修改之后的username
sql = "select username from bs_user where id={}".format(app.EMP_ID)
db_utils.execute(sql)
# 获取执行结果
result = db_utils.fetchone()[0]
logging.info("从数据库中查询出的员工的用户名是:{}".format(result))
# 断言
assert_common(self, response, http_code, success, code, message)
@parameterized.expand(read_delete_emp_data)
def test04_delete_emp(self, success, code, message, http_code):
# 调用删除员工接口
response = self.emp_api.delete_emp()
# 获取修改员工接口的返回json数据
jsonData = response.json()
# 输出json数据
logging.info("删除员工接口的返回数据为:{}".format(jsonData))
# 断言
assert_common(self, response, http_code, success, code, message)
# run_suite.py
import time
import unittest
import app
from script.login import Login
from script.test_emp import TestIHRMEmp
from script.test_login import TestIHRMLogin
from tools.HTMLTestRunner import HTMLTestRunner
# 1 初始化测试套件
suite = unittest.TestSuite()
# 2 将测试用例添加到测试套件
suite.addTest(unittest.makeSuite(Login))
suite.addTest(unittest.makeSuite(TestIHRMEmp))
suite.addTest(unittest.makeSuite(TestIHRMLogin))
# 3 使用HTMLTestRunner执行测试套件,生成测试报告
report_path = app.BASE_DIR + "/report/ihrm{}.html".format(time.strftime('%Y%m%d %H%M%S'))
with open(report_path, mode='wb') as f:
# 初始化HTMLTestRunner
runner = HTMLTestRunner(f, verbosity=1, title="IHRM人力资源接口测试", description="V1.0.0")
# 使用Runner运行测试套件
runner.run(suite)
查看Web页面的结果