├── .gitignore ├── README.md ├── api ├── __init__.py └── user.py ├── common ├── __init__.py ├── logger.py ├── mysql_operate.py └── read_data.py ├── config ├── __init__.py └── setting.ini ├── core ├── __init__.py ├── rest_client.py └── result_base.py ├── data ├── __init__.py ├── api_test_data.yml ├── base_data.yml └── scenario_test_data.yml ├── operation ├── __init__.py └── user.py ├── pytest.ini ├── requirements.txt └── testcases ├── __init__.py ├── api_test ├── __init__.py ├── conftest.py ├── test_01_get_user_info.py ├── test_02_register.py ├── test_03_login.py ├── test_04_update_user.py └── test_05_delete_user.py ├── conftest.py └── scenario_test ├── __init__.py ├── conftest.py ├── test_01_register_login_list.py ├── test_02_register_login_update.py ├── test_03_register_login_delete.py └── test_04_repeat_register.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pytestDemo 2 | 3 | 本项目实现接口自动化的技术选型:**Python+Requests+Pytest+YAML+Allure** ,主要是针对本人的一个接口项目来开展的,通过 Python+Requests 来发送和处理HTTP协议的请求接口,使用 Pytest 作为测试执行器,使用 YAML 来管理测试数据,使用 Allure 来生成测试报告。 4 | 5 | >相关接口项目:[使用 Python+Flask+MySQL+Redis 开发简单接口实例](https://github.com/wintests/flaskDemo) 6 | 7 | ## 项目说明 8 | 9 | 本项目在实现过程中,把整个项目拆分成请求方法封装、HTTP接口封装、关键字封装、测试用例等模块。 10 | 11 | 首先利用Python把HTTP接口封装成Python接口,接着把这些Python接口组装成一个个的关键字,再把关键字组装成测试用例,而测试数据则通过YAML文件进行统一管理,然后再通过Pytest测试执行器来运行这些脚本,并结合Allure输出测试报告。 12 | 13 | 当然,如果感兴趣的话,还可以再对接口自动化进行Jenkins持续集成。 14 | 15 | ## 项目部署 16 | 17 | 首先,下载项目源码后,在根目录下找到 ```requirements.txt``` 文件,然后通过 pip 工具安装 requirements.txt 依赖,执行命令: 18 | 19 | ``` 20 | pip3 install -r requirements.txt 21 | ``` 22 | 23 | 接着,修改 ```config/setting.ini``` 配置文件,在Windows环境下,安装相应依赖之后,在命令行窗口执行命令: 24 | 25 | ``` 26 | pytest 27 | ``` 28 | 29 | **注意**:因为我这里是针对自己的接口项目进行测试,如果想直接执行我的测试用例来查看效果,需要提前部署上面提到的 [flaskDemo](https://github.com/wintests/flaskDemo) 接口项目。 30 | 31 | ## 项目结构 32 | 33 | - api ====>> 接口封装层,如封装HTTP接口为Python接口 34 | - common ====>> 各种工具类 35 | - core ====>> requests请求方法封装、关键字返回结果类 36 | - config ====>> 配置文件 37 | - data ====>> 测试数据文件管理 38 | - operation ====>> 关键字封装层,如把多个Python接口封装为关键字 39 | - pytest.ini ====>> pytest配置文件 40 | - requirements.txt ====>> 相关依赖包文件 41 | - testcases ====>> 测试用例 42 | 43 | ## 关键字封装说明 44 | 45 | 关键字应该是具有一定业务意义的,在封装关键字的时候,可以通过调用多个接口来完成。在某些情况下,比如测试一个充值接口的时候,在充值后可能需要调用查询接口得到最新账户余额,来判断查询结果与预期结果是否一致,那么可以这样来进行测试: 46 | 47 | - 1, 首先,可以把 **```充值-查询```** 的操作封装为一个关键字,在这个关键字中依次调用充值和查询的接口,并可以自定义关键字的返回结果。 48 | - 2, 接着,在编写测试用例的时候,直接调用关键字来进行测试,这时就可以拿到关键字返回的结果,那么断言的时候,就可以直接对关键字返回结果进行断言。 49 | 50 | ## 测试报告效果展示 51 | 52 | 在命令行执行命令:```pytest``` 运行用例后,会得到一个测试报告的原始文件,但这个时候还不能打开成HTML的报告,还需要在项目根目录下,执行命令启动 ```allure``` 服务: 53 | 54 | ``` 55 | # 需要提前配置allure环境,才可以直接使用命令行 56 | allure serve ./report 57 | ``` 58 | 59 | 最终,可以看到测试报告的效果图如下: 60 | 61 | ![image.png](https://upload-images.jianshu.io/upload_images/16853007-248f805c82dbf99c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 62 | -------------------------------------------------------------------------------- /api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wintests/pytestDemo/d22dd897db7604b67f6dcdd80e95e79279718397/api/__init__.py -------------------------------------------------------------------------------- /api/user.py: -------------------------------------------------------------------------------- 1 | import os 2 | from core.rest_client import RestClient 3 | from common.read_data import data 4 | 5 | BASE_PATH = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 6 | data_file_path = os.path.join(BASE_PATH, "config", "setting.ini") 7 | api_root_url = data.load_ini(data_file_path)["host"]["api_root_url"] 8 | 9 | 10 | class User(RestClient): 11 | 12 | def __init__(self, api_root_url, **kwargs): 13 | super(User, self).__init__(api_root_url, **kwargs) 14 | 15 | def list_all_users(self, **kwargs): 16 | return self.get("/users", **kwargs) 17 | 18 | def list_one_user(self, username, **kwargs): 19 | return self.get("/users/{}".format(username), **kwargs) 20 | 21 | def register(self, **kwargs): 22 | return self.post("/register", **kwargs) 23 | 24 | def login(self, **kwargs): 25 | return self.post("/login", **kwargs) 26 | 27 | def update(self, user_id, **kwargs): 28 | return self.put("/update/user/{}".format(user_id), **kwargs) 29 | 30 | def delete(self, name, **kwargs): 31 | return self.post("/delete/user/{}".format(name), **kwargs) 32 | 33 | 34 | user = User(api_root_url) -------------------------------------------------------------------------------- /common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wintests/pytestDemo/d22dd897db7604b67f6dcdd80e95e79279718397/common/__init__.py -------------------------------------------------------------------------------- /common/logger.py: -------------------------------------------------------------------------------- 1 | import logging, time, os 2 | 3 | BASE_PATH = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 4 | # 定义日志文件路径 5 | LOG_PATH = os.path.join(BASE_PATH, "log") 6 | if not os.path.exists(LOG_PATH): 7 | os.mkdir(LOG_PATH) 8 | 9 | 10 | class Logger(): 11 | 12 | def __init__(self): 13 | self.logname = os.path.join(LOG_PATH, "{}.log".format(time.strftime("%Y%m%d"))) 14 | self.logger = logging.getLogger("log") 15 | self.logger.setLevel(logging.DEBUG) 16 | 17 | self.formater = logging.Formatter( 18 | '[%(asctime)s][%(filename)s %(lineno)d][%(levelname)s]: %(message)s') 19 | 20 | self.filelogger = logging.FileHandler(self.logname, mode='a', encoding="UTF-8") 21 | self.console = logging.StreamHandler() 22 | self.console.setLevel(logging.DEBUG) 23 | self.filelogger.setLevel(logging.DEBUG) 24 | self.filelogger.setFormatter(self.formater) 25 | self.console.setFormatter(self.formater) 26 | self.logger.addHandler(self.filelogger) 27 | self.logger.addHandler(self.console) 28 | 29 | 30 | logger = Logger().logger 31 | 32 | if __name__ == '__main__': 33 | logger.info("---测试开始---") 34 | logger.debug("---测试结束---") 35 | -------------------------------------------------------------------------------- /common/mysql_operate.py: -------------------------------------------------------------------------------- 1 | import pymysql 2 | import os 3 | from common.read_data import data 4 | from common.logger import logger 5 | 6 | BASE_PATH = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 7 | data_file_path = os.path.join(BASE_PATH, "config", "setting.ini") 8 | data = data.load_ini(data_file_path)["mysql"] 9 | 10 | DB_CONF = { 11 | "host": data["MYSQL_HOST"], 12 | "port": int(data["MYSQL_PORT"]), 13 | "user": data["MYSQL_USER"], 14 | "password": data["MYSQL_PASSWD"], 15 | "db": data["MYSQL_DB"] 16 | } 17 | 18 | 19 | class MysqlDb(): 20 | 21 | def __init__(self, db_conf=DB_CONF): 22 | # 通过字典拆包传递配置信息,建立数据库连接 23 | self.conn = pymysql.connect(**db_conf, autocommit=True) 24 | # 通过 cursor() 创建游标对象,并让查询结果以字典格式输出 25 | self.cur = self.conn.cursor(cursor=pymysql.cursors.DictCursor) 26 | 27 | def __del__(self): # 对象资源被释放时触发,在对象即将被删除时的最后操作 28 | # 关闭游标 29 | self.cur.close() 30 | # 关闭数据库连接 31 | self.conn.close() 32 | 33 | def select_db(self, sql): 34 | """查询""" 35 | # 检查连接是否断开,如果断开就进行重连 36 | self.conn.ping(reconnect=True) 37 | # 使用 execute() 执行sql 38 | self.cur.execute(sql) 39 | # 使用 fetchall() 获取查询结果 40 | data = self.cur.fetchall() 41 | return data 42 | 43 | def execute_db(self, sql): 44 | """更新/新增/删除""" 45 | try: 46 | # 检查连接是否断开,如果断开就进行重连 47 | self.conn.ping(reconnect=True) 48 | # 使用 execute() 执行sql 49 | self.cur.execute(sql) 50 | # 提交事务 51 | self.conn.commit() 52 | except Exception as e: 53 | logger.info("操作MySQL出现错误,错误原因:{}".format(e)) 54 | # 回滚所有更改 55 | self.conn.rollback() 56 | 57 | 58 | db = MysqlDb(DB_CONF) 59 | -------------------------------------------------------------------------------- /common/read_data.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | import json 3 | from configparser import ConfigParser 4 | from common.logger import logger 5 | 6 | 7 | class MyConfigParser(ConfigParser): 8 | # 重写 configparser 中的 optionxform 函数,解决 .ini 文件中的 键option 自动转为小写的问题 9 | def __init__(self, defaults=None): 10 | ConfigParser.__init__(self, defaults=defaults) 11 | 12 | def optionxform(self, optionstr): 13 | return optionstr 14 | 15 | class ReadFileData(): 16 | 17 | def __init__(self): 18 | pass 19 | 20 | def load_yaml(self, file_path): 21 | logger.info("加载 {} 文件......".format(file_path)) 22 | with open(file_path, encoding='utf-8') as f: 23 | data = yaml.safe_load(f) 24 | logger.info("读到数据 ==>> {} ".format(data)) 25 | return data 26 | 27 | def load_json(self, file_path): 28 | logger.info("加载 {} 文件......".format(file_path)) 29 | with open(file_path, encoding='utf-8') as f: 30 | data = json.load(f) 31 | logger.info("读到数据 ==>> {} ".format(data)) 32 | return data 33 | 34 | def load_ini(self, file_path): 35 | logger.info("加载 {} 文件......".format(file_path)) 36 | config = MyConfigParser() 37 | config.read(file_path, encoding="UTF-8") 38 | data = dict(config._sections) 39 | # print("读到数据 ==>> {} ".format(data)) 40 | return data 41 | 42 | data = ReadFileData() -------------------------------------------------------------------------------- /config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wintests/pytestDemo/d22dd897db7604b67f6dcdd80e95e79279718397/config/__init__.py -------------------------------------------------------------------------------- /config/setting.ini: -------------------------------------------------------------------------------- 1 | [host] 2 | # 测试环境 3 | api_root_url = http://192.168.89.128:9999 4 | 5 | [mysql] 6 | # MySQL配置 7 | MYSQL_HOST = 192.168.89.128 8 | MYSQL_PORT = 3306 9 | MYSQL_USER = root 10 | MYSQL_PASSWD = 123456 11 | MYSQL_DB = flask_demo -------------------------------------------------------------------------------- /core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wintests/pytestDemo/d22dd897db7604b67f6dcdd80e95e79279718397/core/__init__.py -------------------------------------------------------------------------------- /core/rest_client.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json as complexjson 3 | from common.logger import logger 4 | 5 | 6 | class RestClient(): 7 | 8 | def __init__(self, api_root_url): 9 | self.api_root_url = api_root_url 10 | self.session = requests.session() 11 | 12 | def get(self, url, **kwargs): 13 | return self.request(url, "GET", **kwargs) 14 | 15 | def post(self, url, data=None, json=None, **kwargs): 16 | return self.request(url, "POST", data, json, **kwargs) 17 | 18 | def put(self, url, data=None, **kwargs): 19 | return self.request(url, "PUT", data, **kwargs) 20 | 21 | def delete(self, url, **kwargs): 22 | return self.request(url, "DELETE", **kwargs) 23 | 24 | def patch(self, url, data=None, **kwargs): 25 | return self.request(url, "PATCH", data, **kwargs) 26 | 27 | def request(self, url, method, data=None, json=None, **kwargs): 28 | url = self.api_root_url + url 29 | headers = dict(**kwargs).get("headers") 30 | params = dict(**kwargs).get("params") 31 | files = dict(**kwargs).get("params") 32 | cookies = dict(**kwargs).get("params") 33 | self.request_log(url, method, data, json, params, headers, files, cookies) 34 | if method == "GET": 35 | return self.session.get(url, **kwargs) 36 | if method == "POST": 37 | return requests.post(url, data, json, **kwargs) 38 | if method == "PUT": 39 | if json: 40 | # PUT 和 PATCH 中没有提供直接使用json参数的方法,因此需要用data来传入 41 | data = complexjson.dumps(json) 42 | return self.session.put(url, data, **kwargs) 43 | if method == "DELETE": 44 | return self.session.delete(url, **kwargs) 45 | if method == "PATCH": 46 | if json: 47 | data = complexjson.dumps(json) 48 | return self.session.patch(url, data, **kwargs) 49 | 50 | def request_log(self, url, method, data=None, json=None, params=None, headers=None, files=None, cookies=None, **kwargs): 51 | logger.info("接口请求地址 ==>> {}".format(url)) 52 | logger.info("接口请求方式 ==>> {}".format(method)) 53 | # Python3中,json在做dumps操作时,会将中文转换成unicode编码,因此设置 ensure_ascii=False 54 | logger.info("接口请求头 ==>> {}".format(complexjson.dumps(headers, indent=4, ensure_ascii=False))) 55 | logger.info("接口请求 params 参数 ==>> {}".format(complexjson.dumps(params, indent=4, ensure_ascii=False))) 56 | logger.info("接口请求体 data 参数 ==>> {}".format(complexjson.dumps(data, indent=4, ensure_ascii=False))) 57 | logger.info("接口请求体 json 参数 ==>> {}".format(complexjson.dumps(json, indent=4, ensure_ascii=False))) 58 | logger.info("接口上传附件 files 参数 ==>> {}".format(files)) 59 | logger.info("接口 cookies 参数 ==>> {}".format(complexjson.dumps(cookies, indent=4, ensure_ascii=False))) 60 | -------------------------------------------------------------------------------- /core/result_base.py: -------------------------------------------------------------------------------- 1 | 2 | class ResultBase(): 3 | pass -------------------------------------------------------------------------------- /data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wintests/pytestDemo/d22dd897db7604b67f6dcdd80e95e79279718397/data/__init__.py -------------------------------------------------------------------------------- /data/api_test_data.yml: -------------------------------------------------------------------------------- 1 | test_get_all_user_info: 2 | # 期望结果,期望返回码,期望返回信息 3 | # except_result, except_code, except_msg 4 | - [True, 0, "查询成功"] 5 | 6 | test_get_get_one_user_info: 7 | # 用户名,期望结果,期望返回码,期望返回信息 8 | # username, except_result, except_code, except_msg 9 | - ["wintest4", True, 0, "查询成功"] 10 | - ["wintest1111", False, "1004", "查不到相关用户"] 11 | 12 | test_register_user: 13 | # 用户名,密码,手机号,性别,联系地址,期望结果,期望返回码,期望返回信息 14 | # username, password, telephone, sex, address, except_result, except_code, except_msg 15 | - ["测试test", "123456", "13599999999", "1", "深圳市宝安区", True, 0, "注册成功"] 16 | - ["测试test", "123456", "13599999999", "3", "深圳市宝安区", False, 2003, "输入的性别只能是 0(男) 或 1(女)"] 17 | - ["wintest4", "123456", "13599999999", "1", "深圳市宝安区", False, 2002, "用户名已存在"] 18 | 19 | test_login_user: 20 | # 用户名,密码,期望结果,期望返回码,期望返回信息 21 | # username, password, except_result, except_code, except_msg 22 | - ["wintest", "123456", True, 0, "登录成功"] 23 | - ["测试test", "123456", False, 1003, "用户名不存在"] 24 | 25 | test_update_user: 26 | # 修改的用户ID,新密码,新手机号,新性别,新联系地址,期望结果,期望返回码,期望返回信息 27 | # id, new_password, new_telephone, new_sex, new_address, except_result, except_code, except_msg 28 | - [4, "123456", "13500010014", "1", "深圳市宝安区", True, 0, "修改用户信息成功"] 29 | - [4, "123456", "1350001001", "1", "深圳市宝安区", False, 4008, "手机号格式不正确"] 30 | - [111, "123456", "13500010014", "1", "深圳市宝安区", False, 4005, "用户ID不存在"] 31 | 32 | test_delete_user: 33 | # 删除的用户名,期望结果,期望返回码,期望返回信息 34 | # username, except_result, except_code, except_msg 35 | - ["测试test", True, 0, "删除用户信息成功"] 36 | - ["wintest3", False, 3006, "该用户不允许删除"] 37 | -------------------------------------------------------------------------------- /data/base_data.yml: -------------------------------------------------------------------------------- 1 | init_admin_user: 2 | username: "wintest" 3 | password: "123456" 4 | 5 | init_sql: 6 | insert_delete_user: 7 | - "INSERT INTO user(username, password, role, sex, telephone, address) VALUES('测试test', '123456', '1', '1', '13488888888', '北京市海淀区')" 8 | - "DELETE FROM user WHERE username = '测试test'" 9 | delete_register_user: "DELETE FROM user WHERE username = '测试test'" 10 | update_user_telephone: "UPDATE user SET telephone = '13500010004' WHERE id = 4" -------------------------------------------------------------------------------- /data/scenario_test_data.yml: -------------------------------------------------------------------------------- 1 | test_user_register_login_list: 2 | username: "测试test" 3 | password: "123456" 4 | telephone: "13599999999" 5 | sex: "1" 6 | address: "深圳市宝安区" 7 | except_result: True 8 | except_code: 0 9 | except_msg: "查询成功" 10 | 11 | test_user_repeat_register: 12 | username: "测试test" 13 | password: "123456" 14 | telephone: "13599999999" 15 | sex: "1" 16 | address: "深圳市宝安区" 17 | except_result: False 18 | except_code: 2002 19 | except_msg: "用户名已存在,注册失败" 20 | 21 | test_user_register_login_update_success: 22 | register: 23 | username: "测试test" 24 | password: "123456" 25 | telephone: "13599999999" 26 | sex: "1" 27 | address: "深圳市宝安区" 28 | login: 29 | admin_user: "wintest" 30 | admin_pwd: "123456" 31 | update: 32 | new_password: "123456" 33 | new_telephone: "13599999998" 34 | new_sex: "1" 35 | new_address: "深圳市坪山区" 36 | except_result: True 37 | except_code: 0 38 | except_msg: "修改用户信息成功" 39 | 40 | test_user_register_login_update_fail: 41 | register: 42 | username: "测试test" 43 | password: "123456" 44 | telephone: "13599999999" 45 | sex: "1" 46 | address: "深圳市宝安区" 47 | login: 48 | admin_user: "wintest" 49 | admin_pwd: "123456" 50 | update: 51 | new_password: "123456" 52 | new_telephone: "13599999998" 53 | new_sex: "1" 54 | new_address: "深圳市坪山区" 55 | except_result: False 56 | except_code: 4005 57 | except_msg: "修改的用户ID不存在" 58 | 59 | test_user_register_login_delete_success: 60 | admin_user: "wintest" 61 | admin_pwd: "123456" 62 | username: "测试test" 63 | password: "123456" 64 | telephone: "13599999999" 65 | sex: "1" 66 | address: "深圳市宝安区" 67 | except_result: True 68 | except_code: 0 69 | except_msg: "删除用户信息成功" 70 | 71 | test_user_register_login_delete_fail: 72 | admin_user: "wintest4" 73 | admin_pwd: "123456" 74 | username: "测试test" 75 | password: "123456" 76 | telephone: "13599999999" 77 | sex: "1" 78 | address: "深圳市宝安区" 79 | except_result: False 80 | except_code: 3004 81 | except_msg: "当前用户不是管理员用户" 82 | -------------------------------------------------------------------------------- /operation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wintests/pytestDemo/d22dd897db7604b67f6dcdd80e95e79279718397/operation/__init__.py -------------------------------------------------------------------------------- /operation/user.py: -------------------------------------------------------------------------------- 1 | from core.result_base import ResultBase 2 | from api.user import user 3 | from common.logger import logger 4 | 5 | 6 | def get_all_user_info(): 7 | """ 8 | 获取全部用户信息 9 | :return: 自定义的关键字返回结果 result 10 | """ 11 | result = ResultBase() 12 | res = user.list_all_users() 13 | result.success = False 14 | if res.json()["code"] == 0: 15 | result.success = True 16 | else: 17 | result.error = "接口返回码是 【 {} 】, 返回信息:{} ".format(res.json()["code"], res.json()["msg"]) 18 | result.msg = res.json()["msg"] 19 | result.response = res 20 | return result 21 | 22 | 23 | def get_one_user_info(username): 24 | """ 25 | 获取单个用户信息 26 | :param username: 用户名 27 | :return: 自定义的关键字返回结果 result 28 | """ 29 | result = ResultBase() 30 | res = user.list_one_user(username) 31 | result.success = False 32 | if res.json()["code"] == 0: 33 | result.success = True 34 | else: 35 | result.error = "查询用户 ==>> 接口返回码是 【 {} 】, 返回信息:{} ".format(res.json()["code"], res.json()["msg"]) 36 | result.msg = res.json()["msg"] 37 | result.response = res 38 | logger.info("查看单个用户 ==>> 返回结果 ==>> {}".format(result.response.text)) 39 | return result 40 | 41 | 42 | def register_user(username, password, telephone, sex="", address=""): 43 | """ 44 | 注册用户信息 45 | :param username: 用户名 46 | :param password: 密码 47 | :param telephone: 手机号 48 | :param sex: 性别 49 | :param address: 联系地址 50 | :return: 自定义的关键字返回结果 result 51 | """ 52 | result = ResultBase() 53 | json_data = { 54 | "username": username, 55 | "password": password, 56 | "sex": sex, 57 | "telephone": telephone, 58 | "address": address 59 | } 60 | header = { 61 | "Content-Type": "application/json" 62 | } 63 | res = user.register(json=json_data, headers=header) 64 | result.success = False 65 | if res.json()["code"] == 0: 66 | result.success = True 67 | else: 68 | result.error = "接口返回码是 【 {} 】, 返回信息:{} ".format(res.json()["code"], res.json()["msg"]) 69 | result.msg = res.json()["msg"] 70 | result.response = res 71 | logger.info("注册用户 ==>> 返回结果 ==>> {}".format(result.response.text)) 72 | return result 73 | 74 | 75 | def login_user(username, password): 76 | """ 77 | 登录用户 78 | :param username: 用户名 79 | :param password: 密码 80 | :return: 自定义的关键字返回结果 result 81 | """ 82 | result = ResultBase() 83 | payload = { 84 | "username": username, 85 | "password": password 86 | } 87 | header = { 88 | "Content-Type": "application/x-www-form-urlencoded" 89 | } 90 | res = user.login(data=payload, headers=header) 91 | result.success = False 92 | if res.json()["code"] == 0: 93 | result.success = True 94 | result.token = res.json()["login_info"]["token"] 95 | else: 96 | result.error = "接口返回码是 【 {} 】, 返回信息:{} ".format(res.json()["code"], res.json()["msg"]) 97 | result.msg = res.json()["msg"] 98 | result.response = res 99 | logger.info("登录用户 ==>> 返回结果 ==>> {}".format(result.response.text)) 100 | return result 101 | 102 | 103 | def update_user(id, admin_user, new_password, new_telephone, token, new_sex="", new_address=""): 104 | """ 105 | 根据用户ID,修改用户信息 106 | :param id: 用户ID 107 | :param admin_user: 当前操作的管理员用户 108 | :param new_password: 新密码 109 | :param new_telephone: 新手机号 110 | :param token: 当前管理员用户的token 111 | :param new_sex: 新性别 112 | :param new_address: 新联系地址 113 | :return: 自定义的关键字返回结果 result 114 | """ 115 | result = ResultBase() 116 | header = { 117 | "Content-Type": "application/json" 118 | } 119 | json_data = { 120 | "admin_user": admin_user, 121 | "password": new_password, 122 | "token": token, 123 | "sex": new_sex, 124 | "telephone": new_telephone, 125 | "address": new_address 126 | } 127 | res = user.update(id, json=json_data, headers=header) 128 | result.success = False 129 | if res.json()["code"] == 0: 130 | result.success = True 131 | else: 132 | result.error = "接口返回码是 【 {} 】, 返回信息:{} ".format(res.json()["code"], res.json()["msg"]) 133 | result.msg = res.json()["msg"] 134 | result.response = res 135 | logger.info("修改用户 ==>> 返回结果 ==>> {}".format(result.response.text)) 136 | return result 137 | 138 | 139 | def delete_user(username, admin_user, token): 140 | """ 141 | 根据用户名,删除用户信息 142 | :param username: 用户名 143 | :param admin_user: 当前操作的管理员用户 144 | :param token: 当前管理员用户的token 145 | :return: 自定义的关键字返回结果 result 146 | """ 147 | result = ResultBase() 148 | json_data = { 149 | "admin_user": admin_user, 150 | "token": token, 151 | } 152 | header = { 153 | "Content-Type": "application/json" 154 | } 155 | res = user.delete(username, json=json_data, headers=header) 156 | result.success = False 157 | if res.json()["code"] == 0: 158 | result.success = True 159 | else: 160 | result.error = "接口返回码是 【 {} 】, 返回信息:{} ".format(res.json()["code"], res.json()["msg"]) 161 | result.msg = res.json()["msg"] 162 | result.response = res 163 | logger.info("删除用户 ==>> 返回结果 ==>> {}".format(result.response.text)) 164 | return result 165 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | 3 | testpaths = testcases/ 4 | markers = 5 | single: single api test page 6 | multiple: multiple api test page 7 | negative: abnormal test case 8 | 9 | ;xfail_strict = True 10 | 11 | log_cli = False 12 | 13 | addopts = --alluredir ./report -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pytest==4.5.0 2 | PyMySQL==0.9.3 3 | allure-pytest==2.8.6 4 | requests==2.20.0 5 | PyYAML==5.3.1 6 | -------------------------------------------------------------------------------- /testcases/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wintests/pytestDemo/d22dd897db7604b67f6dcdd80e95e79279718397/testcases/__init__.py -------------------------------------------------------------------------------- /testcases/api_test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wintests/pytestDemo/d22dd897db7604b67f6dcdd80e95e79279718397/testcases/api_test/__init__.py -------------------------------------------------------------------------------- /testcases/api_test/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from testcases.conftest import api_data 3 | 4 | 5 | @pytest.fixture(scope="function") 6 | def testcase_data(request): 7 | testcase_name = request.function.__name__ 8 | return api_data.get(testcase_name) -------------------------------------------------------------------------------- /testcases/api_test/test_01_get_user_info.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import allure 3 | from operation.user import get_all_user_info, get_one_user_info 4 | from testcases.conftest import api_data 5 | from common.logger import logger 6 | 7 | 8 | @allure.step("步骤1 ==>> 获取所有用户信息") 9 | def step_1(): 10 | logger.info("步骤1 ==>> 获取所有用户信息") 11 | 12 | 13 | @allure.step("步骤1 ==>> 获取某个用户信息") 14 | def step_2(username): 15 | logger.info("步骤1 ==>> 获取某个用户信息:{}".format(username)) 16 | 17 | 18 | @allure.severity(allure.severity_level.TRIVIAL) 19 | @allure.epic("针对单个接口的测试") 20 | @allure.feature("获取用户信息模块") 21 | class TestGetUserInfo(): 22 | """获取用户信息模块""" 23 | 24 | @allure.story("用例--获取全部用户信息") 25 | @allure.description("该用例是针对获取所有用户信息接口的测试") 26 | @allure.issue("https://www.cnblogs.com/wintest", name="点击,跳转到对应BUG的链接地址") 27 | @allure.testcase("https://www.cnblogs.com/wintest", name="点击,跳转到对应用例的链接地址") 28 | @pytest.mark.single 29 | @pytest.mark.parametrize("except_result, except_code, except_msg", 30 | api_data["test_get_all_user_info"]) 31 | def test_get_all_user_info(self, except_result, except_code, except_msg): 32 | logger.info("*************** 开始执行用例 ***************") 33 | step_1() 34 | result = get_all_user_info() 35 | # print(result.__dict__) 36 | assert result.response.status_code == 200 37 | assert result.success == except_result, result.error 38 | logger.info("code ==>> 期望结果:{}, 实际结果:{}".format(except_code, result.response.json().get("code"))) 39 | assert result.response.json().get("code") == except_code 40 | assert except_msg in result.msg 41 | logger.info("*************** 结束执行用例 ***************") 42 | 43 | @allure.story("用例--获取某个用户信息") 44 | @allure.description("该用例是针对获取单个用户信息接口的测试") 45 | @allure.issue("https://www.cnblogs.com/wintest", name="点击,跳转到对应BUG的链接地址") 46 | @allure.testcase("https://www.cnblogs.com/wintest", name="点击,跳转到对应用例的链接地址") 47 | @allure.title("测试数据:【 {username},{except_result},{except_code},{except_msg} 】") 48 | @pytest.mark.single 49 | @pytest.mark.parametrize("username, except_result, except_code, except_msg", 50 | api_data["test_get_get_one_user_info"]) 51 | def test_get_get_one_user_info(self, username, except_result, except_code, except_msg): 52 | logger.info("*************** 开始执行用例 ***************") 53 | step_2(username) 54 | result = get_one_user_info(username) 55 | # print(result.__dict__) 56 | assert result.response.status_code == 200 57 | assert result.success == except_result, result.error 58 | logger.info("code ==>> 期望结果:{}, 实际结果:【 {} 】".format(except_code, result.response.json().get("code"))) 59 | assert result.response.json().get("code") == except_code 60 | assert except_msg in result.msg 61 | logger.info("*************** 结束执行用例 ***************") 62 | 63 | 64 | if __name__ == '__main__': 65 | pytest.main(["-q", "-s", "test_01_get_user_info.py"]) 66 | -------------------------------------------------------------------------------- /testcases/api_test/test_02_register.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import allure 3 | from operation.user import register_user 4 | from testcases.conftest import api_data 5 | from common.logger import logger 6 | 7 | 8 | @allure.step("步骤1 ==>> 注册用户") 9 | def step_1(username, password, telephone, sex, address): 10 | logger.info("步骤1 ==>> 注册用户 ==>> {}, {}, {}, {}, {}".format(username, password, telephone, sex, address)) 11 | 12 | 13 | @allure.severity(allure.severity_level.NORMAL) 14 | @allure.epic("针对单个接口的测试") 15 | @allure.feature("用户注册模块") 16 | class TestUserRegister(): 17 | """用户注册""" 18 | 19 | @allure.story("用例--注册用户信息") 20 | @allure.description("该用例是针对获取用户注册接口的测试") 21 | @allure.issue("https://www.cnblogs.com/wintest", name="点击,跳转到对应BUG的链接地址") 22 | @allure.testcase("https://www.cnblogs.com/wintest", name="点击,跳转到对应用例的链接地址") 23 | @allure.title( 24 | "测试数据:【 {username},{password},{telephone},{sex},{address},{except_result},{except_code},{except_msg}】") 25 | @pytest.mark.single 26 | @pytest.mark.parametrize("username, password, telephone, sex, address, except_result, except_code, except_msg", 27 | api_data["test_register_user"]) 28 | @pytest.mark.usefixtures("delete_register_user") 29 | def test_register_user(self, username, password, telephone, sex, address, except_result, except_code, except_msg): 30 | logger.info("*************** 开始执行用例 ***************") 31 | result = register_user(username, password, telephone, sex, address) 32 | step_1(username, password, telephone, sex, address) 33 | assert result.success == except_result, result.error 34 | assert result.response.status_code == 200 35 | assert result.success == except_result, result.error 36 | logger.info("code ==>> 期望结果:{}, 实际结果:【 {} 】".format(except_code, result.response.json().get("code"))) 37 | assert result.response.json().get("code") == except_code 38 | assert except_msg in result.msg 39 | logger.info("*************** 结束执行用例 ***************") 40 | 41 | 42 | if __name__ == '__main__': 43 | pytest.main(["-q", "-s", "test_02_register.py"]) 44 | -------------------------------------------------------------------------------- /testcases/api_test/test_03_login.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import allure 3 | from operation.user import login_user 4 | from testcases.conftest import api_data 5 | from common.logger import logger 6 | 7 | 8 | @allure.step("步骤1 ==>> 登录用户") 9 | def step_1(username): 10 | logger.info("步骤1 ==>> 登录用户:{}".format(username)) 11 | 12 | 13 | @allure.severity(allure.severity_level.NORMAL) 14 | @allure.epic("针对单个接口的测试") 15 | @allure.feature("用户登录模块") 16 | class TestUserLogin(): 17 | 18 | @allure.story("用例--登录用户") 19 | @allure.description("该用例是针对获取用户登录接口的测试") 20 | @allure.issue("https://www.cnblogs.com/wintest", name="点击,跳转到对应BUG的链接地址") 21 | @allure.testcase("https://www.cnblogs.com/wintest", name="点击,跳转到对应用例的链接地址") 22 | @allure.title("测试数据:【 {username},{password},{except_result},{except_code},{except_msg}】") 23 | @pytest.mark.single 24 | @pytest.mark.parametrize("username, password, except_result, except_code, except_msg", 25 | api_data["test_login_user"]) 26 | def test_login_user(self, username, password, except_result, except_code, except_msg): 27 | logger.info("*************** 开始执行用例 ***************") 28 | result = login_user(username, password) 29 | step_1(username) 30 | assert result.success == except_result, result.error 31 | assert result.response.status_code == 200 32 | assert result.success == except_result, result.error 33 | logger.info("code ==>> 期望结果:{}, 实际结果:【 {} 】".format(except_code, result.response.json().get("code"))) 34 | assert result.response.json().get("code") == except_code 35 | assert except_msg in result.msg 36 | logger.info("*************** 结束执行用例 ***************") 37 | 38 | 39 | if __name__ == '__main__': 40 | pytest.main(["-q", "-s", "test_03_login.py"]) 41 | -------------------------------------------------------------------------------- /testcases/api_test/test_04_update_user.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import allure 3 | from operation.user import update_user 4 | from testcases.conftest import api_data 5 | from common.logger import logger 6 | 7 | 8 | @allure.step("步骤1 ==>> 根据ID修改用户信息") 9 | def step_1(id): 10 | logger.info("步骤1 ==>> 修改用户ID:{}".format(id)) 11 | 12 | 13 | @allure.step("前置登录步骤 ==>> 管理员登录") 14 | def step_login(admin_user, token): 15 | logger.info("前置登录步骤 ==>> 管理员 {} 登录 ==>> 返回的 token 为:{}".format(admin_user, token)) 16 | 17 | 18 | @allure.severity(allure.severity_level.NORMAL) 19 | @allure.epic("针对单个接口的测试") 20 | @allure.feature("用户修改模块") 21 | class TestUpdate(): 22 | """修改用户""" 23 | 24 | @allure.story("用例--修改用户信息") 25 | @allure.description("该用例是针对获取用户修改接口的测试") 26 | @allure.issue("https://www.cnblogs.com/wintest", name="点击,跳转到对应BUG的链接地址") 27 | @allure.testcase("https://www.cnblogs.com/wintest", name="点击,跳转到对应用例的链接地址") 28 | @allure.title( 29 | "测试数据:【 {id},{new_password},{new_telephone},{new_sex},{new_address},{except_result},{except_code},{except_msg}】") 30 | @pytest.mark.single 31 | @pytest.mark.parametrize("id, new_password, new_telephone, new_sex, new_address, " 32 | "except_result, except_code, except_msg", 33 | api_data["test_update_user"]) 34 | @pytest.mark.usefixtures("update_user_telephone") 35 | def test_update_user(self, login_fixture, id, new_password, new_telephone, new_sex, new_address, 36 | except_result, except_code, except_msg): 37 | logger.info("*************** 开始执行用例 ***************") 38 | user_info = login_fixture 39 | admin_user = user_info.get("login_info").get("username") 40 | token = user_info.get("login_info").get("token") 41 | step_login(admin_user, token) 42 | result = update_user(id, admin_user, new_password, new_telephone, token, new_sex, new_address) 43 | step_1(id) 44 | assert result.success == except_result, result.error 45 | assert result.response.status_code == 200 46 | assert result.success == except_result, result.error 47 | logger.info("code ==>> 期望结果:{}, 实际结果:【 {} 】".format(except_code, result.response.json().get("code"))) 48 | assert result.response.json().get("code") == except_code 49 | assert except_msg in result.msg 50 | logger.info("*************** 结束执行用例 ***************") 51 | 52 | 53 | if __name__ == '__main__': 54 | pytest.main(["-q", "-s", "test_04_update_user.py"]) 55 | -------------------------------------------------------------------------------- /testcases/api_test/test_05_delete_user.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import allure 3 | from operation.user import delete_user 4 | from testcases.conftest import api_data 5 | from common.logger import logger 6 | 7 | 8 | @allure.step("步骤1 ==>> 根据用户名来删除用户信息") 9 | def step_1(username): 10 | logger.info("步骤1 ==>> 删除用户:{}".format(username)) 11 | 12 | 13 | @allure.step("前置登录步骤 ==>> 管理员登录") 14 | def step_login(admin_user, token): 15 | logger.info("前置登录步骤 ==>> 管理员 {} 登录 ==>> 返回的 token 为:{}".format(admin_user, token)) 16 | 17 | 18 | @allure.severity(allure.severity_level.NORMAL) 19 | @allure.epic("针对单个接口的测试") 20 | @allure.feature("用户删除模块") 21 | class TestUserDelete(): 22 | """删除用户""" 23 | 24 | @allure.story("用例--删除用户信息") 25 | @allure.description("该用例是针对获取用户删除接口的测试") 26 | @allure.issue("https://www.cnblogs.com/wintest", name="点击,跳转到对应BUG的链接地址") 27 | @allure.testcase("https://www.cnblogs.com/wintest", name="点击,跳转到对应用例的链接地址") 28 | @allure.title("测试数据:【 {username},{except_result},{except_code},{except_msg} 】") 29 | @pytest.mark.single 30 | @pytest.mark.parametrize("username, except_result, except_code, except_msg", 31 | api_data["test_delete_user"]) 32 | @pytest.mark.usefixtures("insert_delete_user") 33 | def test_delete_user(self, login_fixture, username, except_result, except_code, except_msg): 34 | logger.info("*************** 开始执行用例 ***************") 35 | user_info = login_fixture 36 | admin_user = user_info.get("login_info").get("username") 37 | token = user_info.get("login_info").get("token") 38 | step_login(admin_user, token) 39 | result = delete_user(username, admin_user, token) 40 | step_1(username) 41 | assert result.success == except_result, result.error 42 | assert result.response.status_code == 200 43 | assert result.success == except_result, result.error 44 | logger.info("code ==>> 期望结果:{}, 实际结果:【 {} 】".format(except_code, result.response.json().get("code"))) 45 | assert result.response.json().get("code") == except_code 46 | assert except_msg in result.msg 47 | logger.info("*************** 结束执行用例 ***************") 48 | 49 | 50 | if __name__ == '__main__': 51 | pytest.main(["-q", "-s", "test_05_delete_user.py"]) 52 | -------------------------------------------------------------------------------- /testcases/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | import allure 4 | from api.user import user 5 | from common.mysql_operate import db 6 | from common.read_data import data 7 | from common.logger import logger 8 | 9 | BASE_PATH = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 10 | 11 | 12 | def get_data(yaml_file_name): 13 | try: 14 | data_file_path = os.path.join(BASE_PATH, "data", yaml_file_name) 15 | yaml_data = data.load_yaml(data_file_path) 16 | except Exception as ex: 17 | pytest.skip(str(ex)) 18 | else: 19 | return yaml_data 20 | 21 | 22 | base_data = get_data("base_data.yml") 23 | api_data = get_data("api_test_data.yml") 24 | scenario_data = get_data("scenario_test_data.yml") 25 | 26 | 27 | @allure.step("前置步骤 ==>> 清理数据") 28 | def step_first(): 29 | logger.info("******************************") 30 | logger.info("前置步骤开始 ==>> 清理数据") 31 | 32 | 33 | @allure.step("后置步骤 ==>> 清理数据") 34 | def step_last(): 35 | logger.info("后置步骤开始 ==>> 清理数据") 36 | 37 | 38 | @allure.step("前置步骤 ==>> 管理员用户登录") 39 | def step_login(username, password): 40 | logger.info("前置步骤 ==>> 管理员 {} 登录,返回信息 为:{}".format(username, password)) 41 | 42 | 43 | @pytest.fixture(scope="session") 44 | def login_fixture(): 45 | username = base_data["init_admin_user"]["username"] 46 | password = base_data["init_admin_user"]["password"] 47 | header = { 48 | "Content-Type": "application/x-www-form-urlencoded" 49 | } 50 | payload = { 51 | "username": username, 52 | "password": password 53 | } 54 | loginInfo = user.login(data=payload, headers=header) 55 | step_login(username, password) 56 | yield loginInfo.json() 57 | 58 | 59 | @pytest.fixture(scope="function") 60 | def insert_delete_user(): 61 | """删除用户前,先在数据库插入一条用户数据""" 62 | insert_sql = base_data["init_sql"]["insert_delete_user"][0] 63 | db.execute_db(insert_sql) 64 | step_first() 65 | logger.info("删除用户操作:插入新用户--准备用于删除用户") 66 | logger.info("执行前置SQL:{}".format(insert_sql)) 67 | yield 68 | # 因为有些情况是不给删除管理员用户的,这种情况需要手动清理上面插入的数据 69 | del_sql = base_data["init_sql"]["insert_delete_user"][1] 70 | db.execute_db(del_sql) 71 | step_last() 72 | logger.info("删除用户操作:手工清理处理失败的数据") 73 | logger.info("执行后置SQL:{}".format(del_sql)) 74 | 75 | 76 | @pytest.fixture(scope="function") 77 | def delete_register_user(): 78 | """注册用户前,先删除数据,用例执行之后,再次删除以清理数据""" 79 | del_sql = base_data["init_sql"]["delete_register_user"] 80 | db.execute_db(del_sql) 81 | step_first() 82 | logger.info("注册用户操作:清理用户--准备注册新用户") 83 | logger.info("执行前置SQL:{}".format(del_sql)) 84 | yield 85 | db.execute_db(del_sql) 86 | step_last() 87 | logger.info("注册用户操作:删除注册的用户") 88 | logger.info("执行后置SQL:{}".format(del_sql)) 89 | 90 | 91 | @pytest.fixture(scope="function") 92 | def update_user_telephone(): 93 | """修改用户前,因为手机号唯一,为了使用例重复执行,每次需要先修改手机号,再执行用例""" 94 | update_sql = base_data["init_sql"]["update_user_telephone"] 95 | db.execute_db(update_sql) 96 | step_first() 97 | logger.info("修改用户操作:手工修改用户的手机号,以便用例重复执行") 98 | logger.info("执行SQL:{}".format(update_sql)) -------------------------------------------------------------------------------- /testcases/scenario_test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wintests/pytestDemo/d22dd897db7604b67f6dcdd80e95e79279718397/testcases/scenario_test/__init__.py -------------------------------------------------------------------------------- /testcases/scenario_test/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from testcases.conftest import scenario_data 3 | 4 | 5 | @pytest.fixture(scope="function") 6 | def testcase_data(request): 7 | testcase_name = request.function.__name__ 8 | return scenario_data.get(testcase_name) -------------------------------------------------------------------------------- /testcases/scenario_test/test_01_register_login_list.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import allure 3 | from operation.user import register_user, login_user, get_one_user_info 4 | from common.logger import logger 5 | 6 | 7 | @allure.step("步骤1 ==>> 注册用户") 8 | def step_1(username, password, telephone, sex, address): 9 | logger.info("步骤1 ==>> 注册用户 ==>> {}, {}, {}, {}, {}".format(username, password, telephone, sex, address)) 10 | 11 | 12 | @allure.step("步骤2 ==>> 登录用户") 13 | def step_2(username): 14 | logger.info("步骤2 ==>> 登录用户:{}".format(username)) 15 | 16 | 17 | @allure.step("步骤3 ==>> 获取某个用户信息") 18 | def step_3(username): 19 | logger.info("步骤3 ==>> 获取某个用户信息:{}".format(username)) 20 | 21 | 22 | @allure.severity(allure.severity_level.CRITICAL) 23 | @allure.epic("针对业务场景的测试") 24 | @allure.feature("场景:用户注册-用户登录-查看用户") 25 | class TestRegLogList(): 26 | 27 | @allure.story("用例--注册/登录/查看--预期成功") 28 | @allure.description("该用例是针对 注册-登录-查看 场景的测试") 29 | @allure.issue("https://www.cnblogs.com/wintest", name="点击,跳转到对应BUG的链接地址") 30 | @allure.testcase("https://www.cnblogs.com/wintest", name="点击,跳转到对应用例的链接地址") 31 | @allure.title("用户注册登录查看-预期成功") 32 | @pytest.mark.multiple 33 | @pytest.mark.usefixtures("delete_register_user") 34 | def test_user_register_login_list(self, testcase_data): 35 | username = testcase_data["username"] 36 | password = testcase_data["password"] 37 | telephone = testcase_data["telephone"] 38 | sex = testcase_data["sex"] 39 | address = testcase_data["address"] 40 | except_result = testcase_data["except_result"] 41 | except_code = testcase_data["except_code"] 42 | except_msg = testcase_data["except_msg"] 43 | logger.info("*************** 开始执行用例 ***************") 44 | result = register_user(username, password, telephone, sex, address) 45 | step_1(username, password, telephone, sex, address) 46 | assert result.success is True, result.error 47 | result = login_user(username, password) 48 | step_2(username) 49 | assert result.success is True, result.error 50 | result = get_one_user_info(username) 51 | step_3(username) 52 | assert result.success == except_result, result.error 53 | logger.info("code ==>> 期望结果:{}, 实际结果:【 {} 】".format(except_code, result.response.json().get("code"))) 54 | assert result.response.json().get("code") == except_code 55 | assert except_msg in result.msg 56 | logger.info("*************** 结束执行用例 ***************") 57 | 58 | 59 | if __name__ == '__main__': 60 | pytest.main(["-q", "-s", "test_01_register_login_list.py"]) 61 | -------------------------------------------------------------------------------- /testcases/scenario_test/test_02_register_login_update.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import allure 3 | from operation.user import register_user, login_user, get_one_user_info, update_user 4 | from common.logger import logger 5 | 6 | 7 | @allure.step("步骤1 ==>> 注册用户") 8 | def step_1(username, password, telephone, sex, address): 9 | logger.info("步骤1 ==>> 注册用户 ==>> {}, {}, {}, {}, {}".format(username, password, telephone, sex, address)) 10 | 11 | 12 | @allure.step("步骤2 ==>> 登录用户") 13 | def step_2(username): 14 | logger.info("步骤2 ==>> 登录管理员用户:{}".format(username)) 15 | 16 | 17 | @allure.step("步骤3 ==>> 查看新注册用户ID") 18 | def step_3(id): 19 | logger.info("步骤3 ==>> 查看新注册用户ID:{}".format(id)) 20 | 21 | 22 | @allure.step("步骤4 ==>> 根据ID修改用户信息") 23 | def step_4(id): 24 | logger.info("步骤4 ==>> 修改用户ID:{}".format(id)) 25 | 26 | 27 | @allure.severity(allure.severity_level.BLOCKER) 28 | @allure.epic("针对业务场景的测试") 29 | @allure.feature("场景:用户注册-用户登录-修改用户") 30 | class TestRegLogUpdate(): 31 | 32 | @allure.story("用例--注册/登录/修改--预期成功") 33 | @allure.description("该用例是针对 注册-登录-修改 场景的测试") 34 | @allure.issue("https://www.cnblogs.com/wintest", name="点击,跳转到对应BUG的链接地址") 35 | @allure.testcase("https://www.cnblogs.com/wintest", name="点击,跳转到对应用例的链接地址") 36 | @allure.title("用户注册登录修改--预期成功") 37 | @pytest.mark.multiple 38 | @pytest.mark.usefixtures("delete_register_user") 39 | def test_user_register_login_update_success(self, testcase_data): 40 | username = testcase_data["register"]["username"] 41 | password = testcase_data["register"]["password"] 42 | telephone = testcase_data["register"]["telephone"] 43 | sex = testcase_data["register"]["sex"] 44 | address = testcase_data["register"]["address"] 45 | admin_user = testcase_data["login"]["admin_user"] 46 | admin_pwd = testcase_data["login"]["admin_pwd"] 47 | new_password = testcase_data["update"]["new_password"] 48 | new_telephone = testcase_data["update"]["new_telephone"] 49 | new_sex = testcase_data["update"]["new_sex"] 50 | new_address = testcase_data["update"]["new_address"] 51 | except_result = testcase_data["except_result"] 52 | except_code = testcase_data["except_code"] 53 | except_msg = testcase_data["except_msg"] 54 | logger.info("*************** 开始执行用例 ***************") 55 | result = register_user(username, password, telephone, sex, address) 56 | step_1(username, password, telephone, sex, address) 57 | assert result.success is True, result.error 58 | result = login_user(admin_user, admin_pwd) 59 | step_2(admin_user) 60 | assert result.success is True, result.error 61 | admin_token = result.token 62 | result = get_one_user_info(username) 63 | id = result.response.json().get("data")[0].get("id") 64 | step_3(id) 65 | assert result.success is True, result.error 66 | result = update_user(id, admin_user, new_password, new_telephone, admin_token, new_sex, new_address) 67 | step_4(id) 68 | assert result.success == except_result, result.error 69 | logger.info("code ==>> 期望结果:{}, 实际结果:【 {} 】".format(except_code, result.response.json().get("code"))) 70 | assert result.response.json().get("code") == except_code 71 | assert except_msg in result.msg 72 | logger.info("*************** 结束执行用例 ***************") 73 | 74 | @allure.story("用例--注册/登录/修改--预期失败") 75 | @allure.description("该用例是针对 注册-登录-修改 场景的测试") 76 | @allure.issue("https://www.cnblogs.com/wintest", name="点击,跳转到对应BUG的链接地址") 77 | @allure.testcase("https://www.cnblogs.com/wintest", name="点击,跳转到对应用例的链接地址") 78 | @allure.title("用户注册登录修改--预期失败") 79 | @pytest.mark.multiple 80 | @pytest.mark.usefixtures("delete_register_user") 81 | def test_user_register_login_update_fail(self, testcase_data): 82 | username = testcase_data["register"]["username"] 83 | password = testcase_data["register"]["password"] 84 | telephone = testcase_data["register"]["telephone"] 85 | sex = testcase_data["register"]["sex"] 86 | address = testcase_data["register"]["address"] 87 | admin_user = testcase_data["login"]["admin_user"] 88 | admin_pwd = testcase_data["login"]["admin_pwd"] 89 | new_password = testcase_data["update"]["new_password"] 90 | new_telephone = testcase_data["update"]["new_telephone"] 91 | new_sex = testcase_data["update"]["new_sex"] 92 | new_address = testcase_data["update"]["new_address"] 93 | except_result = testcase_data["except_result"] 94 | except_code = testcase_data["except_code"] 95 | except_msg = testcase_data["except_msg"] 96 | logger.info("*************** 开始执行用例 ***************") 97 | result = register_user(username, password, telephone, sex, address) 98 | step_1(username, password, telephone, sex, address) 99 | assert result.success is True, result.error 100 | result = login_user(admin_user, admin_pwd) 101 | step_2(admin_user) 102 | assert result.success is True, result.error 103 | admin_token = result.token 104 | result = get_one_user_info(username) 105 | id = result.response.json().get("data")[0].get("id") 106 | step_3(id) 107 | assert result.success is True, result.error 108 | result = update_user(id + 1, admin_user, new_password, new_telephone, admin_token, new_sex, new_address) 109 | step_4(id) 110 | assert result.success == except_result, result.error 111 | logger.info("code ==>> 期望结果:{}, 实际结果:【 {} 】".format(except_code, result.response.json().get("code"))) 112 | assert result.response.json().get("code") == except_code 113 | assert except_msg in result.msg 114 | logger.info("*************** 结束执行用例 ***************") 115 | 116 | @pytest.mark.negative 117 | @pytest.mark.skip(reason="skip跳过示例:暂时无法运行该用例") 118 | def test_user_register_login_update_fail_2(self): 119 | pass 120 | 121 | 122 | if __name__ == '__main__': 123 | pytest.main(["-q", "-s", "test_02_register_login_update.py"]) 124 | -------------------------------------------------------------------------------- /testcases/scenario_test/test_03_register_login_delete.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import allure 3 | from operation.user import register_user, login_user, delete_user 4 | from common.logger import logger 5 | 6 | 7 | @allure.step("步骤1 ==>> 注册用户") 8 | def step_1(username, password, telephone, sex, address): 9 | logger.info("步骤1 ==>> 注册用户 ==>> {}, {}, {}, {}, {}".format(username, password, telephone, sex, address)) 10 | 11 | 12 | @allure.step("步骤2 ==>> 登录用户") 13 | def step_2(username): 14 | logger.info("步骤2 ==>> 登录管理员用户:{}".format(username)) 15 | 16 | 17 | @allure.step("步骤3 ==>> 根据用户名来删除用户信息") 18 | def step_3(username): 19 | logger.info("步骤3 ==>> 删除用户:{}".format(username)) 20 | 21 | 22 | @allure.severity(allure.severity_level.MINOR) 23 | @allure.epic("针对业务场景的测试") 24 | @allure.feature("场景:用户注册-用户登录-删除用户") 25 | class TestRegLogDelete(): 26 | 27 | @allure.story("用例--注册/登录/删除--预期成功") 28 | @allure.description("该用例是针对 注册-登录-删除 场景的测试") 29 | @allure.issue("https://www.cnblogs.com/wintest", name="点击,跳转到对应BUG的链接地址") 30 | @allure.testcase("https://www.cnblogs.com/wintest", name="点击,跳转到对应用例的链接地址") 31 | @allure.title("用户注册登录删除-预期成功") 32 | @pytest.mark.multiple 33 | def test_user_register_login_delete_success(self, testcase_data): 34 | username = testcase_data["username"] 35 | password = testcase_data["password"] 36 | telephone = testcase_data["telephone"] 37 | sex = testcase_data["sex"] 38 | address = testcase_data["address"] 39 | admin_user = testcase_data["admin_user"] 40 | admin_pwd = testcase_data["admin_pwd"] 41 | except_result = testcase_data["except_result"] 42 | except_code = testcase_data["except_code"] 43 | except_msg = testcase_data["except_msg"] 44 | logger.info("*************** 开始执行用例 ***************") 45 | result = register_user(username, password, telephone, sex, address) 46 | step_1(username, password, telephone, sex, address) 47 | assert result.success is True, result.error 48 | result = login_user(admin_user, admin_pwd) 49 | step_2(username) 50 | assert result.success is True, result.error 51 | result = delete_user(username, admin_user, result.token) 52 | step_3(username) 53 | assert result.success == except_result, result.error 54 | logger.info("code ==>> 期望结果:{}, 实际结果:【 {} 】".format(except_code, result.response.json().get("code"))) 55 | assert result.response.json().get("code") == except_code 56 | assert except_msg in result.msg 57 | logger.info("*************** 结束执行用例 ***************") 58 | 59 | 60 | """ 61 | # 标记 strict=True 输出 allure 报告会报错,暂不处理 62 | # strict=True 可以让那些mark为xfail, 实际通显示XPASS的测试用例被置为失败 63 | """ 64 | 65 | @allure.story("用例--注册/登录/删除--预期失败") 66 | @allure.description("该用例是针对 注册-登录-删除 场景的测试") 67 | @allure.issue("https://www.cnblogs.com/wintest", name="点击,跳转到对应BUG的链接地址") 68 | @allure.testcase("https://www.cnblogs.com/wintest", name="点击,跳转到对应用例的链接地址") 69 | @allure.title("用户注册登录删除-预期失败") 70 | @pytest.mark.negative 71 | @pytest.mark.xfail(reason="xfail示例:预期失败的用例,如存在尚未解决的Bug等") 72 | @pytest.mark.usefixtures("delete_register_user") 73 | def test_user_register_login_delete_fail(self, testcase_data): 74 | username = testcase_data["username"] 75 | password = testcase_data["password"] 76 | telephone = testcase_data["telephone"] 77 | sex = testcase_data["sex"] 78 | address = testcase_data["address"] 79 | admin_user = testcase_data["admin_user"] 80 | admin_pwd = testcase_data["admin_pwd"] 81 | except_result = testcase_data["except_result"] 82 | except_code = testcase_data["except_code"] 83 | except_msg = testcase_data["except_msg"] 84 | logger.info("*************** 开始执行用例 ***************") 85 | result = register_user(username, password, telephone, sex, address) 86 | step_1(username, password, telephone, sex, address) 87 | assert result.success is True, result.error 88 | result = login_user(admin_user, admin_pwd) 89 | step_2(username) 90 | assert result.success is True, result.error 91 | result = delete_user(username, admin_user, result.token) 92 | step_3(username) 93 | assert result.success == except_result, result.error 94 | logger.info("code ==>> 期望结果:{}, 实际结果:【 {} 】".format(except_code, result.response.json().get("code"))) 95 | assert result.response.json().get("code") == except_code 96 | assert except_msg in result.msg 97 | logger.info("*************** 结束执行用例 ***************") 98 | 99 | 100 | if __name__ == '__main__': 101 | pytest.main(["-q", "-s", "test_03_register_login_delete.py"]) 102 | -------------------------------------------------------------------------------- /testcases/scenario_test/test_04_repeat_register.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import allure 3 | from operation.user import register_user 4 | from common.logger import logger 5 | 6 | 7 | @allure.step("步骤1 ==>> 注册用户") 8 | def step_1(username, password, telephone, sex, address): 9 | logger.info("步骤1 ==>> 注册用户 ==>> {}, {}, {}, {}, {}".format(username, password, telephone, sex, address)) 10 | 11 | 12 | @allure.step("步骤2 ==>> 重复注册用户") 13 | def step_2(username, password, telephone, sex, address): 14 | logger.info("步骤2 ==>> 重复注册用户 ==>> {}, {}, {}, {}, {}".format(username, password, telephone, sex, address)) 15 | 16 | 17 | @allure.severity(allure.severity_level.CRITICAL) 18 | @allure.epic("针对业务场景的测试") 19 | @allure.feature("场景:用户注册-重复注册") 20 | class TestRepeatReg(): 21 | 22 | @allure.story("用例--注册/重复注册--预期成功") 23 | @allure.description("该用例是针对 注册-重复注册 场景的测试") 24 | @allure.issue("https://www.cnblogs.com/wintest", name="点击,跳转到对应BUG的链接地址") 25 | @allure.testcase("https://www.cnblogs.com/wintest", name="点击,跳转到对应用例的链接地址") 26 | @allure.title("用户注册/重复注册-预期成功") 27 | @pytest.mark.single 28 | @pytest.mark.usefixtures("delete_register_user") 29 | def test_user_repeat_register(self, testcase_data): 30 | username = testcase_data["username"] 31 | password = testcase_data["password"] 32 | telephone = testcase_data["telephone"] 33 | sex = testcase_data["sex"] 34 | address = testcase_data["address"] 35 | except_result = testcase_data["except_result"] 36 | except_code = testcase_data["except_code"] 37 | except_msg = testcase_data["except_msg"] 38 | logger.info("*************** 开始执行用例 ***************") 39 | result = register_user(username, password, telephone, sex, address) 40 | step_1(username, password, telephone, sex, address) 41 | assert result.success is True, result.error 42 | result = register_user(username, password, telephone, sex, address) 43 | step_2(username, password, telephone, sex, address) 44 | assert result.success == except_result, result.error 45 | logger.info("code ==>> 期望结果:{}, 实际结果:【 {} 】".format(except_code, result.response.json().get("code"))) 46 | assert result.response.json().get("code") == except_code 47 | assert except_msg in result.msg 48 | logger.info("*************** 结束执行用例 ***************") 49 | 50 | 51 | if __name__ == '__main__': 52 | pytest.main(["-q", "-s", "test_04_repeat_register.py"]) 53 | --------------------------------------------------------------------------------