点击文末小卡片免费获取软件测试全套资料资料在手涨薪更快一、数据分离:从Excel中读取数据之前的用例中数据直接写在代码文件里不利于修改和构造数据这里我们使用Excel保存测试数据实现代码和数据的分离新建Excel文件test_user_data.xlsx包含两个工作簿TestUserLogin和TestUserReg并复制到项目根目录下。“更新: excel表格中,增加一个headers列,内容为json格式, 如下”二、Excel读取方法Python我们使用三方库xlrd来读取Excel使用pip install xlrd安装xlrd。import xlrd wb xlrd.open_workbook(test_user_data.xlsx) # 打开excel sh wb.sheet_by_name(TestUserLogin) # 按工作簿名定位工作表 print(sh.nrows) # 有效数据行数 print(sh.ncols) # 有效数据列数 print(sh.cell(0, 0).value) # 输出第一行第一列的值case_name print(sh.row_values(0)) # 输出第1行的所有值列表格式 # 将数据和标题组装成字典使数据更清晰 print(dict(zip(sh.row_values(0), sh.row_values(1)))) # 遍历excel,打印所有的数据 for i in range(sh.nrows): print(sh.row_values(i))输出结果3 5 case_name [case_name, url, method, data, expect_res] {case_name: test_user_login_normal, url: http://115.28.108.130:5000/api/user/login/, method: POST, data: {name: 张三,password:123456}, expect_res: h1登录成功/h1} [case_name, url, method, data, expect_res] [test_user_login_normal, http://115.28.108.130:5000/api/user/login/, POST, {name: 张三,password:123456}, h1登录成功/h1] [test_user_login_password_wrong, http://115.28.108.130:5000/api/user/login/, POST, {name: 张三,password:1234567}, h1失败用户不存在/h1]三、封装读取excel操作1.新建read_excel.py我们的目的是获取某条用例的数据需要3个参数excel数据文件名data_file工作簿名sheet用例名case_name。如果我们只封装一个函数每次调用每条用例都要打开一次excel并遍历一次这样效率比较低我们可以拆分成两个函数一个函数excel_to_list(data_file, sheet)一次获取一个工作表的所有数据另一个函数get_test_data(data_list, case_name)从所有数据中去查找到该条用例的数据。import xlrd def excel_to_list(data_file, sheet): data_list [] # 新建个空列表来乘装所有的数据 wb xlrd.open_workbook(data_file) # 打开excel sh wb.sheet_by_name(sheet) # 获取工作簿 header sh.row_values(0) # 获取标题行数据 for i in range(1, sh.nrows): # 跳过标题行从第二行开始取数据 d dict(zip(header, sh.row_values(i))) # 将标题和每行数据组装成字典 data_list.append(d) return data_list # 列表嵌套字典格式每个元素是一个字典 def get_test_data(data_list, case_name): for case_data in data_list: if case_name case_data[case_name]: # 如果字典数据中case_name与参数一致 return case_data # 如果查询不到会返回None if __name__ __main__: # 测试一下自己的代码 data_list excel_to_list(test_user_data.xlsx, TestUserLogin) # 读取excelTestUserLogin工作簿的所有数据 case_data get_test_data(data_list, test_user_login_normal) # 查找用例test_user_login_normal的数据 print(case_data)输出结果{case_name: test_user_login_normal, url: http://115.28.108.130:5000/api/user/login/, method: POST, data: {name: 张三,password:123456}, expect_res: h1登录成功/h1}2.用例中使用方法(test_user_login.py)import unittest import requests from read_excel import * # 导入read_excel中的方法 import json # 用来转化excel中的json字符串为字典 class TestUserLogin(unittest.TestCase): classmethod def setUpClass(cls): # 整个测试类只执行一次 cls.data_list excel_to_list(test_user_data.xlsx, TestUserLogin) # 读取该测试类所有用例数据 # cls.data_list 同 self.data_list 都是该类的公共属性 def test_user_login_normal(self): case_data get_test_data(self.data_list, test_user_login_normal) # 从数据列表中查找到该用例数据 if not case_data: # 有可能为None print(用例数据不存在) url case_data.get(url) # 从字典中取数据excel中的标题也必须是小写url data case_data.get(data) # 注意字符串格式需要用json.loads()转化为字典格式 expect_res case_data.get(expect_res) # 期望数据 res requests.post(urlurl, datajson.loads(data)) # 表单请求数据转为字典格式 self.assertEqual(res.text, expect_res) # 改为assertEqual断言 if __name__ __main__: # 非必要用于测试我们的代码 unittest.main(verbosity2)3.用例中使用方法(test_user_reg.py)import unittest import requests from db import * from read_excel import * import json class TestUserReg(unittest.TestCase): classmethod def setUpClass(cls): cls.data_list excel_to_list(test_user_data.xlsx, TestUserReg) # 读取TestUserReg工作簿的所有数据 def test_user_reg_normal(self): case_data get_test_data(self.data_list, test_user_reg_normal) if not case_data: print(用例数据不存在) url case_data.get(url) data json.loads(case_data.get(data)) # 转为字典需要取里面的name进行数据库检查 expect_res json.loads(case_data.get(expect_res)) # 转为字典断言时直接断言两个字典是否相等 name data.get(name) # 范冰冰 # 环境检查 if check_user(name): del_user(name) # 发送请求 res requests.post(urlurl, jsondata) # 用datadata 传字符串也可以 # 响应断言整体断言 self.assertDictEqual(res.json(), expect_res) # 数据库断言 self.assertTrue(check_user(name)) # 环境清理由于注册接口向数据库写入了用户信息 del_user(name) if __name__ __main__: # 非必要用于测试我们的代码 unittest.main(verbosity2)四、增加日志(log)功能1.新建config.py文件import logging logging.basicConfig(levellogging.DEBUG, # log level format[%(asctime)s] %(levelname)s [%(funcName)s: %(filename)s, %(lineno)d] %(message)s, # log格式 datefmt%Y-%m-%d %H:%M:%S, # 日期格式 filenamelog.txt, # 日志输出文件 filemodea) # 追加模式 if __name__ __main__: logging.info(hello)运行后在当前目录下生成log.txt内容如下[2018-09-11 18:08:17] INFO [module: config.py, 38] hello2.日志别级(Log Level)CRITICAL: 用于输出严重错误信息ERROR: 用于输出错误信息WARNING: 用于输出警示信息INFO: 用于输出一些提升信息DEBUG: 用于输出一些调试信息3.日志优先级CRITICALERRORWARNINGINFODEBUG指定level logging.DEBUG所有等级大于等于DEBUG的信息都会输出若指定level logging.ERROR WARNING,INFO,DEBUG小于设置级别的信息不会输出。4.日志格式说明%(levelno)s: 打印日志级别的数值%(levelname)s: 打印日志级别名称%(pathname)s: 打印当前执行程序的路径其实就是sys.argv[0]%(filename)s: 打印当前执行程序名%(funcName)s: 打印日志的当前函数%(lineno)d: 打印日志的当前行号%(asctime)s: 打印日志的时间%(thread)d: 打印线程ID%(threadName)s: 打印线程名称%(process)d: 打印进程ID%(message)s: 打印日志信息5.日志在项目中的使用db.pyimport pymysql from config import * # 封装数据库查询操作 def query_db(sql): conn get_db_conn() cur conn.cursor() logging.debug(sql) # 输出执行的sql cur.execute(sql) conn.commit() result cur.fetchall() logging.debug(result) # 输出查询结果 cur.close() conn.close() return result # 封装更改数据库操作 def change_db(sql): conn get_db_conn() cur conn.cursor() logging.debug(sql) # 输出执行的sql try: cur.execute(sql) conn.commit() except Exception as e: conn.rollback() logging.error(str(e)) # 输出错误信息 finally: cur.close() conn.close()用例import unittest import requests from read_excel import * # 导入read_excel中的方法 import json # 用来转化excel中的json字符串为字典 from config import * class TestUserLogin(unittest.TestCase): classmethod def setUpClass(cls): # 整个测试类只执行一次 cls.data_list excel_to_list(test_user_data.xlsx, TestUserLogin) # 读取该测试类所有用例数据 # cls.data_list 同 self.data_list 都是该类的公共属性 def test_user_login_normal(self): case_data get_test_data(self.data_list, test_user_login_normal) # 从数据列表中查找到该用例数据 if not case_data: # 有可能为None logging.error(用例数据不存在) url case_data.get(url) # excel中的标题也必须是小写url data case_data.get(data) # 注意字符串格式需要用json.loads()转化为字典格式 expect_res case_data.get(expect_res) # 期望数据 res requests.post(urlurl, datajson.loads(data)) # 表单请求数据转为字典格式 logging.info(测试用例{}.format(test_user_login_normal)) logging.info(url{}.format(url)) logging.info(请求参数{}.format(data)) logging.info(期望结果{}.format(expect_res)) logging.info(实际结果{}.format(res.text) self.assertEqual(res.text, expect_res) # 断言 if __name__ __main__: unittest.main(verbosity2)输出结果[2018-09-13 10:34:49] INFO [log_case_info: case_log.py, 8] 测试用例test_user_login_normal [2018-09-13 10:34:49] INFO [log_case_info: case_log.py, 9] urlhttp://115.28.108.130:5000/api/user/login/ [2018-09-13 10:34:49] INFO [log_case_info: case_log.py, 10] 请求参数{name: 张三,password:123456} [2018-09-13 10:34:49] INFO [log_case_info: case_log.py, 11] 期望结果h1登录成功/h1 [2018-09-13 10:34:49] INFO [log_case_info: case_log.py, 12] 实际结果h1登录成功/h1因为每个用例都要输出很多log信息我们封装一个case_log的函数from config import * import json def log_case_info(case_name, url, data, expect_res, res_text): if isinstance(data,dict): data json.dumps(data, ensure_asciiFalse) # 如果data是字典格式转化为字符串 logging.info(测试用例{}.format(case_name)) logging.info(url{}.format(url)) logging.info(请求参数{}.format(data)) logging.info(期望结果{}.format(expect_res)) logging.info(实际结果{}.format(res_text)简化后的用例log输出import unittest import requests from read_excel import * import json from config import * from case_log import log_case_info # 导入方法 class TestUserLogin(unittest.TestCase): classmethod def setUpClass(cls): cls.data_list excel_to_list(test_user_data.xlsx, TestUserLogin) def test_user_login_normal(self): case_data get_test_data(self.data_list, test_user_login_normal) if not case_data: logging.error(用例数据不存在) url case_data.get(url) data case_data.get(data) expect_res case_data.get(expect_res) res requests.post(urlurl, datajson.loads(data)) log_case_info(test_user_login_normal, url, data, expect_res, res_text) # 输出用例log信息 self.assertEqual(res.text, expect_res) if __name__ __main__: unittest.main(verbosity2)五、发送邮件在生成报告后我们希望框架能自动把报告发送到我们的邮箱中。和outlookfoxmail等邮件客户端一样Python中发送邮件需要通过Email的smtp服务发送。1.首先需要确认用来发送邮件的邮箱是否启用了smtp服务2.编写邮件内容Email邮件需要专门的MIME格式3.组装Email头发件人收件人主题4.连接smtp服务器并发送邮件import smtplib # 用于建立smtp连接 from email.mime.text import MIMEText # 邮件需要专门的MIME格式 # 1. 编写邮件内容Email邮件需要专门的MIME格式 msg MIMEText(this is a test email, plain, utf-8) # plain指普通文本格式邮件内容 # 2. 组装Email头发件人收件人主题 msg[From] test_resultssina.com # 发件人 msg[To] 2375247815qq.com # 收件人 msg[Subject] Api Test Report # 邮件主题 # 3. 连接smtp服务器并发送邮件 smtp smtplib.SMTP_SSL(smtp.sina.com) # smtp服务器地址 使用SSL模式 smtp.login(自己的邮箱地址, 自己的邮箱密码) # 用户名和密码 smtp.sendmail(接收邮件地址1, 接收邮件地址2, msg.as_string()) smtp.quit()5.中文邮件主题、HTML邮件内容及附件import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart # 混合MIME格式支持上传附件 from email.header import Header # 用于使用中文邮件主题 # 1. 编写邮件内容 with open(report.html, encodingutf-8) as f: # 打开html报告 email_body f.read() # 读取报告内容 msg MIMEMultipart() # 混合MIME格式 msg.attach(MIMEText(email_body, html, utf-8)) # 添加html格式邮件正文会丢失css格式 # 2. 组装Email头发件人收件人主题 msg[From] test_resultssina.com # 发件人 msg[To] 2375247815qq.com # 收件人 msg[Subject] Header(接口测试报告, utf-8) # 中文邮件主题指定utf-8编码 # 3. 构造附件1传送当前目录下的 test.txt 文件 att1 MIMEText(open(report.html, rb).read(), base64, utf-8) # 二进制格式打开 att1[Content-Type] application/octet-stream att1[Content-Disposition] attachment; filenamereport.html # filename为邮件中附件显示的名字 msg.attach(att1) # 4. 连接smtp服务器并发送邮件 smtp smtplib.SMTP_SSL(smtp.sina.com) # smtp服务器地址 使用SSL模式 smtp.login(test_resultssina.com, hanzhichao123) # 用户名和密码 smtp.sendmail(test_resultssina.com, 2375247815qq.com, msg.as_string()) smtp.sendmail(test_resultssina.com, superhin126.com, msg.as_string()) # 发送给另一个邮箱 smtp.quit()6.封装发送邮件方法import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart # 混合MIME格式支持上传附件 from email.header import Header # 用于使用中文邮件主题 from config import * def send_email(report_file): msg MIMEMultipart() # 混合MIME格式 msg.attach(MIMEText(open(report_file, encodingutf-8).read(), html, utf-8)) # 添加html格式邮件正文会丢失css格式 msg[From] test_resultssina.com # 发件人 msg[To] 2375247815qq.com # 收件人 msg[Subject] Header(接口测试报告, utf-8) # 中文邮件主题指定utf-8编码 att1 MIMEText(open(report_file, rb).read(), base64, utf-8) # 二进制格式打开 att1[Content-Type] application/octet-stream att1[Content-Disposition] attachment; filenamereport.html # filename为邮件中附件显示的名字 msg.attach(att1) try: smtp smtplib.SMTP_SSL(smtp.sina.com) # smtp服务器地址 使用SSL模式 smtp.login(test_resultssina.com, hanzhichao123) # 用户名和密码 smtp.sendmail(test_resultssina.com, 2375247815qq.com, msg.as_string()) smtp.sendmail(test_resultssina.com, superhin126.com, msg.as_string()) # 发送给另一个邮箱 logging.info(邮件发送完成) except Exception as e: logging.error(str(e)) finally: smtp.quit()7.run_all.py中结束后发送邮件import unittest from HTMLTestReportCN import HTMLTestRunner from config import * from send_email import send_email logging.info( 测试开始 ) suite unittest.defaultTestLoader.discover(./) with open(report.html, wb) as f: # 改为with open 格式 HTMLTestRunner(streamf, titleApi Test, description测试描述, tester卡卡).run(suite) send_email(report.html) # 发送邮件 logging.info( 测试结束 )六、使用配置文件和项目的log配置一样数据库服务器地址邮件服务地址我们一般放到配置文件config.py中。import logging import os # 项目路径 prj_path os.path.dirname(os.path.abspath(__file__)) # 当前文件的绝对路径的上一级__file__指当前文件 data_path prj_path # 数据目录暂时在项目目录下 test_path prj_path # 用例目录暂时在项目目录下 log_file os.path.join(prj_path, log.txt) # 也可以每天生成新的日志文件 report_file os.path.join(prj_path, report.html) # 也可以每次生成新的报告 # log配置 logging.basicConfig(levellogging.DEBUG, # log level format[%(asctime)s] %(levelname)s [%(funcName)s: %(filename)s, %(lineno)d] %(message)s, # log格式 datefmt%Y-%m-%d %H:%M:%S, # 日期格式 filenamelog_file, # 日志输出文件 filemodea) # 追加模式 # 数据库配置 db_host 127.0.0.1 # 自己的服务器地址 db_port 3306 db_user test db_passwd 123456 db api_test # 邮件配置 smtp_server smtp.sina.com smtp_user test_resultssina.com smtp_password hanzhichao123 sender smtp_user # 发件人 receiver 2375247815qq.com # 收件人 subject 接口测试报告 # 邮件主题修改db.pysend_email.pyrun_all.py等对配置文件的引用db.pyimport pymysql from config import * # 获取连接方法 def get_db_conn(): conn pymysql.connect(hostdb_host, # 从配置文件中读取 portdb_port, userdb_user, passwddb_passwd, # passwd 不是 password dbdb, charsetutf8) # 如果查询有中文需要指定测试集编码send_email.pyimport smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from email.header import Header from config import * def send_email(report_file): msg MIMEMultipart() msg.attach(MIMEText(open(report_file, encodingutf-8).read(), html, utf-8)) msg[From] test_resultssina.com msg[To] 2375247815qq.com msg[Subject] Header(subject, utf-8) # 从配置文件中读取 att1 MIMEText(open(report_file, rb).read(), base64, utf-8) # 从配置文件中读取 att1[Content-Type] application/octet-stream att1[Content-Disposition] attachment; filename{}.format(report_file) # 参数化一下report_file msg.attach(att1) try: smtp smtplib.SMTP_SSL(smtp_server) # 从配置文件中读取 smtp.login(smtp_user, smtp_password) # 从配置文件中读取 smtp.sendmail(sender, receiver, msg.as_string()) logging.info(邮件发送完成) except Exception as e: logging.error(str(e)) finally: smtp.quit()run_all.pyimport unittest from HTMLTestReportCN import HTMLTestRunner from config import * from send_email import send_email logging.info( 测试开始 ) suite unittest.defaultTestLoader.discover(test_path) # 从配置文件中读取用例路径 with open(report_file, wb) as f: # 从配置文件中读取 HTMLTestRunner(streamf, titleApi Test, description测试描述).run(suite) send_email(report_file) # 从配置文件中读取 logging.info( 测试结束 )七、框架整理当前所有文件配置文件公共方法测试用例数据报告log都在项目根目录下随着用例的增加和功能的补充文件会越来越多不便于维护和管理因此我们要建立不同的文件夹对文件进行分类组织。1.在项目中新建以下文件夹config: 存放项目配置文件data: 存放用例数据文件lib: 公共方法库log: 存放日志文件report: 存放报告文件test: 存放测试用例user: 存放user模块用例(模块下要有__init__.py这样里面的用例才能读取到)2.代码目录整理将配置文件config.py移动到config目录下将数据文件test_user_data.xlsx移动到data目录下将公共方法db.pysend_email.pycase_log.pyread_excel.pyHTMLTestReportCN.py移动到lib目录下将测试用例test_user_login.pytest_user_reg.py移动到test/user目录下保留run_all.py在项目根目录下如图3.修改配置文件(config/config.py)import logging import os # 项目路径 prj_path os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 当前文件的上一级的上一级目录增加一级 data_path os.path.join(prj_path, data) # 数据目录 test_path os.path.join(prj_path, test) # 用例目录 log_file os.path.join(prj_path, log, log.txt) # 更改路径到log目录下 report_file os.path.join(prj_path, report, report.html) # 更改路径到report目录下4.修改对配置文件及公共方法的引用 为避免相对路径导包出错的问题我们统一把导包搜索路径(sys.path)提升到项目根目录下如lib/db.pydb.pyimport pymysql import sys sys.path.append(..) # 提升一级到项目更目录下 from config.config import * # 从项目根目录下导入test_user_login.pyimport unittest import requests import json import os # 增加了一个os需要用来组装路径 import sys sys.path.append(../..) # 提升2级到项目根目录下 from config.config import * # 从项目路径下导入 from lib.read_excel import * # 从项目路径下导入 from lib.case_log import log_case_info # 从项目路径下导入 class TestUserLogin(unittest.TestCase): classmethod def setUpClass(cls): # 整个测试类只执行一次 cls.data_list excel_to_list(os.path.join(data_path, test_user_data.xlsx),TestUserLogin) # 增加data路径run_all.pyimport unittest from lib.HTMLTestReportCN import HTMLTestRunner # 修改导入路径 from config.config import * # 修改导入路径 from lib.send_email import send_email # 修改导入路径 logging.info( 测试开始 ) suite unittest.defaultTestLoader.discover(test_path) # 从配置文件中读取 with open(report_file, wb) as f: # 从配置文件中读取 HTMLTestRunner(streamf, titleApi Test, description测试描述).run(suite) send_email(report_file) # 从配置文件中读取 logging.info( 测试结束 )“如果同一文件夹下的方法相互引用如lib/read_excel.py假如需要引用lib/db.py也需要采用这种从项目路径下导入的方式run_all.py直接在项目路径下不需要提升sys.path无需相对导入我们自己的包时如read_excel.py不需要提升”5.运行run_all.py根据log和报告调试代码直至所有用例全部通过最后感谢每一个认真阅读我文章的人礼尚往来总是要有的虽然不是什么很值钱的东西如果你用得到的话可以直接拿走这些资料对于做【软件测试】的朋友来说应该是最全面最完整的备战仓库这个仓库也陪伴我走过了最艰难的路程希望也能帮助到你凡事要趁早特别是技术行业一定要提升技术功底。
Python接口测试实战之搭建自动化测试框架
发布时间:2026/5/27 1:54:01
点击文末小卡片免费获取软件测试全套资料资料在手涨薪更快一、数据分离:从Excel中读取数据之前的用例中数据直接写在代码文件里不利于修改和构造数据这里我们使用Excel保存测试数据实现代码和数据的分离新建Excel文件test_user_data.xlsx包含两个工作簿TestUserLogin和TestUserReg并复制到项目根目录下。“更新: excel表格中,增加一个headers列,内容为json格式, 如下”二、Excel读取方法Python我们使用三方库xlrd来读取Excel使用pip install xlrd安装xlrd。import xlrd wb xlrd.open_workbook(test_user_data.xlsx) # 打开excel sh wb.sheet_by_name(TestUserLogin) # 按工作簿名定位工作表 print(sh.nrows) # 有效数据行数 print(sh.ncols) # 有效数据列数 print(sh.cell(0, 0).value) # 输出第一行第一列的值case_name print(sh.row_values(0)) # 输出第1行的所有值列表格式 # 将数据和标题组装成字典使数据更清晰 print(dict(zip(sh.row_values(0), sh.row_values(1)))) # 遍历excel,打印所有的数据 for i in range(sh.nrows): print(sh.row_values(i))输出结果3 5 case_name [case_name, url, method, data, expect_res] {case_name: test_user_login_normal, url: http://115.28.108.130:5000/api/user/login/, method: POST, data: {name: 张三,password:123456}, expect_res: h1登录成功/h1} [case_name, url, method, data, expect_res] [test_user_login_normal, http://115.28.108.130:5000/api/user/login/, POST, {name: 张三,password:123456}, h1登录成功/h1] [test_user_login_password_wrong, http://115.28.108.130:5000/api/user/login/, POST, {name: 张三,password:1234567}, h1失败用户不存在/h1]三、封装读取excel操作1.新建read_excel.py我们的目的是获取某条用例的数据需要3个参数excel数据文件名data_file工作簿名sheet用例名case_name。如果我们只封装一个函数每次调用每条用例都要打开一次excel并遍历一次这样效率比较低我们可以拆分成两个函数一个函数excel_to_list(data_file, sheet)一次获取一个工作表的所有数据另一个函数get_test_data(data_list, case_name)从所有数据中去查找到该条用例的数据。import xlrd def excel_to_list(data_file, sheet): data_list [] # 新建个空列表来乘装所有的数据 wb xlrd.open_workbook(data_file) # 打开excel sh wb.sheet_by_name(sheet) # 获取工作簿 header sh.row_values(0) # 获取标题行数据 for i in range(1, sh.nrows): # 跳过标题行从第二行开始取数据 d dict(zip(header, sh.row_values(i))) # 将标题和每行数据组装成字典 data_list.append(d) return data_list # 列表嵌套字典格式每个元素是一个字典 def get_test_data(data_list, case_name): for case_data in data_list: if case_name case_data[case_name]: # 如果字典数据中case_name与参数一致 return case_data # 如果查询不到会返回None if __name__ __main__: # 测试一下自己的代码 data_list excel_to_list(test_user_data.xlsx, TestUserLogin) # 读取excelTestUserLogin工作簿的所有数据 case_data get_test_data(data_list, test_user_login_normal) # 查找用例test_user_login_normal的数据 print(case_data)输出结果{case_name: test_user_login_normal, url: http://115.28.108.130:5000/api/user/login/, method: POST, data: {name: 张三,password:123456}, expect_res: h1登录成功/h1}2.用例中使用方法(test_user_login.py)import unittest import requests from read_excel import * # 导入read_excel中的方法 import json # 用来转化excel中的json字符串为字典 class TestUserLogin(unittest.TestCase): classmethod def setUpClass(cls): # 整个测试类只执行一次 cls.data_list excel_to_list(test_user_data.xlsx, TestUserLogin) # 读取该测试类所有用例数据 # cls.data_list 同 self.data_list 都是该类的公共属性 def test_user_login_normal(self): case_data get_test_data(self.data_list, test_user_login_normal) # 从数据列表中查找到该用例数据 if not case_data: # 有可能为None print(用例数据不存在) url case_data.get(url) # 从字典中取数据excel中的标题也必须是小写url data case_data.get(data) # 注意字符串格式需要用json.loads()转化为字典格式 expect_res case_data.get(expect_res) # 期望数据 res requests.post(urlurl, datajson.loads(data)) # 表单请求数据转为字典格式 self.assertEqual(res.text, expect_res) # 改为assertEqual断言 if __name__ __main__: # 非必要用于测试我们的代码 unittest.main(verbosity2)3.用例中使用方法(test_user_reg.py)import unittest import requests from db import * from read_excel import * import json class TestUserReg(unittest.TestCase): classmethod def setUpClass(cls): cls.data_list excel_to_list(test_user_data.xlsx, TestUserReg) # 读取TestUserReg工作簿的所有数据 def test_user_reg_normal(self): case_data get_test_data(self.data_list, test_user_reg_normal) if not case_data: print(用例数据不存在) url case_data.get(url) data json.loads(case_data.get(data)) # 转为字典需要取里面的name进行数据库检查 expect_res json.loads(case_data.get(expect_res)) # 转为字典断言时直接断言两个字典是否相等 name data.get(name) # 范冰冰 # 环境检查 if check_user(name): del_user(name) # 发送请求 res requests.post(urlurl, jsondata) # 用datadata 传字符串也可以 # 响应断言整体断言 self.assertDictEqual(res.json(), expect_res) # 数据库断言 self.assertTrue(check_user(name)) # 环境清理由于注册接口向数据库写入了用户信息 del_user(name) if __name__ __main__: # 非必要用于测试我们的代码 unittest.main(verbosity2)四、增加日志(log)功能1.新建config.py文件import logging logging.basicConfig(levellogging.DEBUG, # log level format[%(asctime)s] %(levelname)s [%(funcName)s: %(filename)s, %(lineno)d] %(message)s, # log格式 datefmt%Y-%m-%d %H:%M:%S, # 日期格式 filenamelog.txt, # 日志输出文件 filemodea) # 追加模式 if __name__ __main__: logging.info(hello)运行后在当前目录下生成log.txt内容如下[2018-09-11 18:08:17] INFO [module: config.py, 38] hello2.日志别级(Log Level)CRITICAL: 用于输出严重错误信息ERROR: 用于输出错误信息WARNING: 用于输出警示信息INFO: 用于输出一些提升信息DEBUG: 用于输出一些调试信息3.日志优先级CRITICALERRORWARNINGINFODEBUG指定level logging.DEBUG所有等级大于等于DEBUG的信息都会输出若指定level logging.ERROR WARNING,INFO,DEBUG小于设置级别的信息不会输出。4.日志格式说明%(levelno)s: 打印日志级别的数值%(levelname)s: 打印日志级别名称%(pathname)s: 打印当前执行程序的路径其实就是sys.argv[0]%(filename)s: 打印当前执行程序名%(funcName)s: 打印日志的当前函数%(lineno)d: 打印日志的当前行号%(asctime)s: 打印日志的时间%(thread)d: 打印线程ID%(threadName)s: 打印线程名称%(process)d: 打印进程ID%(message)s: 打印日志信息5.日志在项目中的使用db.pyimport pymysql from config import * # 封装数据库查询操作 def query_db(sql): conn get_db_conn() cur conn.cursor() logging.debug(sql) # 输出执行的sql cur.execute(sql) conn.commit() result cur.fetchall() logging.debug(result) # 输出查询结果 cur.close() conn.close() return result # 封装更改数据库操作 def change_db(sql): conn get_db_conn() cur conn.cursor() logging.debug(sql) # 输出执行的sql try: cur.execute(sql) conn.commit() except Exception as e: conn.rollback() logging.error(str(e)) # 输出错误信息 finally: cur.close() conn.close()用例import unittest import requests from read_excel import * # 导入read_excel中的方法 import json # 用来转化excel中的json字符串为字典 from config import * class TestUserLogin(unittest.TestCase): classmethod def setUpClass(cls): # 整个测试类只执行一次 cls.data_list excel_to_list(test_user_data.xlsx, TestUserLogin) # 读取该测试类所有用例数据 # cls.data_list 同 self.data_list 都是该类的公共属性 def test_user_login_normal(self): case_data get_test_data(self.data_list, test_user_login_normal) # 从数据列表中查找到该用例数据 if not case_data: # 有可能为None logging.error(用例数据不存在) url case_data.get(url) # excel中的标题也必须是小写url data case_data.get(data) # 注意字符串格式需要用json.loads()转化为字典格式 expect_res case_data.get(expect_res) # 期望数据 res requests.post(urlurl, datajson.loads(data)) # 表单请求数据转为字典格式 logging.info(测试用例{}.format(test_user_login_normal)) logging.info(url{}.format(url)) logging.info(请求参数{}.format(data)) logging.info(期望结果{}.format(expect_res)) logging.info(实际结果{}.format(res.text) self.assertEqual(res.text, expect_res) # 断言 if __name__ __main__: unittest.main(verbosity2)输出结果[2018-09-13 10:34:49] INFO [log_case_info: case_log.py, 8] 测试用例test_user_login_normal [2018-09-13 10:34:49] INFO [log_case_info: case_log.py, 9] urlhttp://115.28.108.130:5000/api/user/login/ [2018-09-13 10:34:49] INFO [log_case_info: case_log.py, 10] 请求参数{name: 张三,password:123456} [2018-09-13 10:34:49] INFO [log_case_info: case_log.py, 11] 期望结果h1登录成功/h1 [2018-09-13 10:34:49] INFO [log_case_info: case_log.py, 12] 实际结果h1登录成功/h1因为每个用例都要输出很多log信息我们封装一个case_log的函数from config import * import json def log_case_info(case_name, url, data, expect_res, res_text): if isinstance(data,dict): data json.dumps(data, ensure_asciiFalse) # 如果data是字典格式转化为字符串 logging.info(测试用例{}.format(case_name)) logging.info(url{}.format(url)) logging.info(请求参数{}.format(data)) logging.info(期望结果{}.format(expect_res)) logging.info(实际结果{}.format(res_text)简化后的用例log输出import unittest import requests from read_excel import * import json from config import * from case_log import log_case_info # 导入方法 class TestUserLogin(unittest.TestCase): classmethod def setUpClass(cls): cls.data_list excel_to_list(test_user_data.xlsx, TestUserLogin) def test_user_login_normal(self): case_data get_test_data(self.data_list, test_user_login_normal) if not case_data: logging.error(用例数据不存在) url case_data.get(url) data case_data.get(data) expect_res case_data.get(expect_res) res requests.post(urlurl, datajson.loads(data)) log_case_info(test_user_login_normal, url, data, expect_res, res_text) # 输出用例log信息 self.assertEqual(res.text, expect_res) if __name__ __main__: unittest.main(verbosity2)五、发送邮件在生成报告后我们希望框架能自动把报告发送到我们的邮箱中。和outlookfoxmail等邮件客户端一样Python中发送邮件需要通过Email的smtp服务发送。1.首先需要确认用来发送邮件的邮箱是否启用了smtp服务2.编写邮件内容Email邮件需要专门的MIME格式3.组装Email头发件人收件人主题4.连接smtp服务器并发送邮件import smtplib # 用于建立smtp连接 from email.mime.text import MIMEText # 邮件需要专门的MIME格式 # 1. 编写邮件内容Email邮件需要专门的MIME格式 msg MIMEText(this is a test email, plain, utf-8) # plain指普通文本格式邮件内容 # 2. 组装Email头发件人收件人主题 msg[From] test_resultssina.com # 发件人 msg[To] 2375247815qq.com # 收件人 msg[Subject] Api Test Report # 邮件主题 # 3. 连接smtp服务器并发送邮件 smtp smtplib.SMTP_SSL(smtp.sina.com) # smtp服务器地址 使用SSL模式 smtp.login(自己的邮箱地址, 自己的邮箱密码) # 用户名和密码 smtp.sendmail(接收邮件地址1, 接收邮件地址2, msg.as_string()) smtp.quit()5.中文邮件主题、HTML邮件内容及附件import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart # 混合MIME格式支持上传附件 from email.header import Header # 用于使用中文邮件主题 # 1. 编写邮件内容 with open(report.html, encodingutf-8) as f: # 打开html报告 email_body f.read() # 读取报告内容 msg MIMEMultipart() # 混合MIME格式 msg.attach(MIMEText(email_body, html, utf-8)) # 添加html格式邮件正文会丢失css格式 # 2. 组装Email头发件人收件人主题 msg[From] test_resultssina.com # 发件人 msg[To] 2375247815qq.com # 收件人 msg[Subject] Header(接口测试报告, utf-8) # 中文邮件主题指定utf-8编码 # 3. 构造附件1传送当前目录下的 test.txt 文件 att1 MIMEText(open(report.html, rb).read(), base64, utf-8) # 二进制格式打开 att1[Content-Type] application/octet-stream att1[Content-Disposition] attachment; filenamereport.html # filename为邮件中附件显示的名字 msg.attach(att1) # 4. 连接smtp服务器并发送邮件 smtp smtplib.SMTP_SSL(smtp.sina.com) # smtp服务器地址 使用SSL模式 smtp.login(test_resultssina.com, hanzhichao123) # 用户名和密码 smtp.sendmail(test_resultssina.com, 2375247815qq.com, msg.as_string()) smtp.sendmail(test_resultssina.com, superhin126.com, msg.as_string()) # 发送给另一个邮箱 smtp.quit()6.封装发送邮件方法import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart # 混合MIME格式支持上传附件 from email.header import Header # 用于使用中文邮件主题 from config import * def send_email(report_file): msg MIMEMultipart() # 混合MIME格式 msg.attach(MIMEText(open(report_file, encodingutf-8).read(), html, utf-8)) # 添加html格式邮件正文会丢失css格式 msg[From] test_resultssina.com # 发件人 msg[To] 2375247815qq.com # 收件人 msg[Subject] Header(接口测试报告, utf-8) # 中文邮件主题指定utf-8编码 att1 MIMEText(open(report_file, rb).read(), base64, utf-8) # 二进制格式打开 att1[Content-Type] application/octet-stream att1[Content-Disposition] attachment; filenamereport.html # filename为邮件中附件显示的名字 msg.attach(att1) try: smtp smtplib.SMTP_SSL(smtp.sina.com) # smtp服务器地址 使用SSL模式 smtp.login(test_resultssina.com, hanzhichao123) # 用户名和密码 smtp.sendmail(test_resultssina.com, 2375247815qq.com, msg.as_string()) smtp.sendmail(test_resultssina.com, superhin126.com, msg.as_string()) # 发送给另一个邮箱 logging.info(邮件发送完成) except Exception as e: logging.error(str(e)) finally: smtp.quit()7.run_all.py中结束后发送邮件import unittest from HTMLTestReportCN import HTMLTestRunner from config import * from send_email import send_email logging.info( 测试开始 ) suite unittest.defaultTestLoader.discover(./) with open(report.html, wb) as f: # 改为with open 格式 HTMLTestRunner(streamf, titleApi Test, description测试描述, tester卡卡).run(suite) send_email(report.html) # 发送邮件 logging.info( 测试结束 )六、使用配置文件和项目的log配置一样数据库服务器地址邮件服务地址我们一般放到配置文件config.py中。import logging import os # 项目路径 prj_path os.path.dirname(os.path.abspath(__file__)) # 当前文件的绝对路径的上一级__file__指当前文件 data_path prj_path # 数据目录暂时在项目目录下 test_path prj_path # 用例目录暂时在项目目录下 log_file os.path.join(prj_path, log.txt) # 也可以每天生成新的日志文件 report_file os.path.join(prj_path, report.html) # 也可以每次生成新的报告 # log配置 logging.basicConfig(levellogging.DEBUG, # log level format[%(asctime)s] %(levelname)s [%(funcName)s: %(filename)s, %(lineno)d] %(message)s, # log格式 datefmt%Y-%m-%d %H:%M:%S, # 日期格式 filenamelog_file, # 日志输出文件 filemodea) # 追加模式 # 数据库配置 db_host 127.0.0.1 # 自己的服务器地址 db_port 3306 db_user test db_passwd 123456 db api_test # 邮件配置 smtp_server smtp.sina.com smtp_user test_resultssina.com smtp_password hanzhichao123 sender smtp_user # 发件人 receiver 2375247815qq.com # 收件人 subject 接口测试报告 # 邮件主题修改db.pysend_email.pyrun_all.py等对配置文件的引用db.pyimport pymysql from config import * # 获取连接方法 def get_db_conn(): conn pymysql.connect(hostdb_host, # 从配置文件中读取 portdb_port, userdb_user, passwddb_passwd, # passwd 不是 password dbdb, charsetutf8) # 如果查询有中文需要指定测试集编码send_email.pyimport smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from email.header import Header from config import * def send_email(report_file): msg MIMEMultipart() msg.attach(MIMEText(open(report_file, encodingutf-8).read(), html, utf-8)) msg[From] test_resultssina.com msg[To] 2375247815qq.com msg[Subject] Header(subject, utf-8) # 从配置文件中读取 att1 MIMEText(open(report_file, rb).read(), base64, utf-8) # 从配置文件中读取 att1[Content-Type] application/octet-stream att1[Content-Disposition] attachment; filename{}.format(report_file) # 参数化一下report_file msg.attach(att1) try: smtp smtplib.SMTP_SSL(smtp_server) # 从配置文件中读取 smtp.login(smtp_user, smtp_password) # 从配置文件中读取 smtp.sendmail(sender, receiver, msg.as_string()) logging.info(邮件发送完成) except Exception as e: logging.error(str(e)) finally: smtp.quit()run_all.pyimport unittest from HTMLTestReportCN import HTMLTestRunner from config import * from send_email import send_email logging.info( 测试开始 ) suite unittest.defaultTestLoader.discover(test_path) # 从配置文件中读取用例路径 with open(report_file, wb) as f: # 从配置文件中读取 HTMLTestRunner(streamf, titleApi Test, description测试描述).run(suite) send_email(report_file) # 从配置文件中读取 logging.info( 测试结束 )七、框架整理当前所有文件配置文件公共方法测试用例数据报告log都在项目根目录下随着用例的增加和功能的补充文件会越来越多不便于维护和管理因此我们要建立不同的文件夹对文件进行分类组织。1.在项目中新建以下文件夹config: 存放项目配置文件data: 存放用例数据文件lib: 公共方法库log: 存放日志文件report: 存放报告文件test: 存放测试用例user: 存放user模块用例(模块下要有__init__.py这样里面的用例才能读取到)2.代码目录整理将配置文件config.py移动到config目录下将数据文件test_user_data.xlsx移动到data目录下将公共方法db.pysend_email.pycase_log.pyread_excel.pyHTMLTestReportCN.py移动到lib目录下将测试用例test_user_login.pytest_user_reg.py移动到test/user目录下保留run_all.py在项目根目录下如图3.修改配置文件(config/config.py)import logging import os # 项目路径 prj_path os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 当前文件的上一级的上一级目录增加一级 data_path os.path.join(prj_path, data) # 数据目录 test_path os.path.join(prj_path, test) # 用例目录 log_file os.path.join(prj_path, log, log.txt) # 更改路径到log目录下 report_file os.path.join(prj_path, report, report.html) # 更改路径到report目录下4.修改对配置文件及公共方法的引用 为避免相对路径导包出错的问题我们统一把导包搜索路径(sys.path)提升到项目根目录下如lib/db.pydb.pyimport pymysql import sys sys.path.append(..) # 提升一级到项目更目录下 from config.config import * # 从项目根目录下导入test_user_login.pyimport unittest import requests import json import os # 增加了一个os需要用来组装路径 import sys sys.path.append(../..) # 提升2级到项目根目录下 from config.config import * # 从项目路径下导入 from lib.read_excel import * # 从项目路径下导入 from lib.case_log import log_case_info # 从项目路径下导入 class TestUserLogin(unittest.TestCase): classmethod def setUpClass(cls): # 整个测试类只执行一次 cls.data_list excel_to_list(os.path.join(data_path, test_user_data.xlsx),TestUserLogin) # 增加data路径run_all.pyimport unittest from lib.HTMLTestReportCN import HTMLTestRunner # 修改导入路径 from config.config import * # 修改导入路径 from lib.send_email import send_email # 修改导入路径 logging.info( 测试开始 ) suite unittest.defaultTestLoader.discover(test_path) # 从配置文件中读取 with open(report_file, wb) as f: # 从配置文件中读取 HTMLTestRunner(streamf, titleApi Test, description测试描述).run(suite) send_email(report_file) # 从配置文件中读取 logging.info( 测试结束 )“如果同一文件夹下的方法相互引用如lib/read_excel.py假如需要引用lib/db.py也需要采用这种从项目路径下导入的方式run_all.py直接在项目路径下不需要提升sys.path无需相对导入我们自己的包时如read_excel.py不需要提升”5.运行run_all.py根据log和报告调试代码直至所有用例全部通过最后感谢每一个认真阅读我文章的人礼尚往来总是要有的虽然不是什么很值钱的东西如果你用得到的话可以直接拿走这些资料对于做【软件测试】的朋友来说应该是最全面最完整的备战仓库这个仓库也陪伴我走过了最艰难的路程希望也能帮助到你凡事要趁早特别是技术行业一定要提升技术功底。