前言
-
本章节以 用户注册->登录(注销)->用户中心的三个模块为例进行的单元测试
-
会用到很多 第二章节的知识,建议翻看了解第二章
-
有任何问题随时评论区提问
Django项目登录功能测试
Prepare-测试前的准备
一、基础资料/环境准备
- 接口文档->实际工作中,为了保证无效沟通,接口文档更新一定即时邮件传递!
- 测试服务器->数据库
- 测试用例编写->执行
- 生成测试报告->可以通过类似禅道的项目管理软件上报BUG以及测试报告
二、软件环境准备
-
测试的框架目录搭建
-
目的:方便管理、提升测试代码效率
-
目录结构说明
-
-
目录中文件代码准备
-
pytest.ini 配置文件修改
[pytest] # 1.给 pytest 传参数 三个插件的配置文件中写写法如下 # -s 输出测试用例中的print内容 # --html 插件 将测试结果输出为报告网页 # --rerun碰见测试报错重新测试几遍 addopts = -s --html=./a.html --reruns 3 # 2.指定 测试脚本的 路径 指定到scripts文件夹下 testpaths = ./scripts # 3.指定 测试文件的 名字 python_files = test_*.py # 4.指定 测试文件类的 名字 当前的目录下有指定名称文件才会执行 python_classes = Test* # 5.指定 测试文件函数的 名字 python_functions = test_*
-
base文件夹1 发送请求代码(因为我们每个单元测试都是要向后端发送请求)
import requests # get 请求方式 url必传项 params一般传递字典类似于向后端发送请求携带数据 def method_get(url, params=None, **kwargs): response = requests.get(url, params=params, **kwargs) return response # post请求方式 url必传项 data-表单数据 json-json格式数据 def method_post(url, data=None, json=None, **kwargs): response = requests.post(url, data=data, json=json, **kwargs) return response
-
base文件夹2 测试代码向后端发送请求携带数据 把数据统一存储到yaml文件中方便维护
# 文件读取涉及到文件路径问题 需要使用python自带的os模块进行文件路径的查找 # ======几个常用文件路径函数说明====== os.path.dirname() ../ 上级目录 os.path.abspath() 绝对路径 os.getcwd() ./ 获取当前目录(文件夹路径) os.sep === '/' 字符串/ 用于路径拼接 # =================================== import os import yaml class ReadData: def __init__(self, file_name): """ # =============方法1=============== # 1. 当前目录的绝对路径 的上级目录的上级目录 xxx/data/data.yaml print(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # 2. 路径作为属性传递给 filepath self.filepath=os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + '/data/' + file_name """ #===========方法2============ # 执行路径 在子目录中 # self.filepath = os.path.dirname(os.getcwd()) + os.sep + 'data' + os.sep + file_name # 执行路径 在测试文件根目录 self.filepath = os.getcwd() + os.sep + 'data' + os.sep + file_name print('当前文件夹路径:', os.getcwd()) def read_data(self): # 读的方式打开 f = open(self.filepath, 'r') # yaml自带load读取后转化为python中的dict形式 data = yaml.load(f, Loader=yaml.FullLoader) # 返回字典数据 return data
-
Register-注册单元测试
以下测试用例通用流程
- 测试需要的测试数据准备到yaml文件中(如用户名密码等)
- 测试用例中读取数据
- pytest.mark.parametrize(‘key’, [value]) 将测试数据传递到测试用例中
- 编写测试用例具体代码
- 执行测试函数
- 如需要数据库参与 开启mysql/redis等
RepeJudg-用户名+手机号重复判断
一、判断用户名是否重复
-
测试数据准备
# 1. 用户名是否重复 数据准备 url username 第1、4名字在数据库中存在 RepeJudg_username: url: "http://127.0.0.:8000/usernames/{username}/count/" usernames: ['python3', 'dreba', 'nazha', 'python3']
-
测试代码
from base.data_read import ReadData from base.response import method_get import pytest # ====测试数据读取函数==== def data_read(): # 1. 将yaml文件读取 data_dict = ReadData('data.yaml').read_data() # 2. 从字典中取数据 url usernames url = data_dict['RepeJudg_username']['url'] usernames = data_dict['RepeJudg_username']['usernames'] # 3. 返回数据 供测试用例调用 return url, usernames class Test_username(): # ===parametrize() 方式传递给测试用例参数=== # pytest.mark.parametrize('key', [value]) @pytest.mark.parametrize('url,usernames', [data_read()]) def test_username(self, url, usernames): while len(usernames) != 0: # 0.获取用户名 用pop删除并获取 name = usernames.pop() # 1.拼接完整的路由 username_count_url = url.format(username=name) print('\n', username_count_url) # 2.发送get请求 response = method_get(username_count_url) # 3.断言--可能访问失败 assert response.status_code == 200, '访问失败!' # print(username_count_url) # print(response.content.decode()) # 4. 根据对返回数据库username数量查询 count!=0重复 count=0不重复 count = response.json().get('count') if count == 1: print('{}重复了..'.format(name)) assert count == 1 else: print('{}不重复..'.format(name)) assert count == 0 # ============测试结果================ scripts/test_username.py http://127.0.0.1:8000/usernames/python3/count/ python3重复了.. http://127.0.0.1:8000/usernames/nazha/count/ nazha不重复.. http://127.0.0.1:8000/usernames/dilireba/count/ dilireba不重复.. http://127.0.0.1:8000/usernames/python3/count/ python3重复了..
二、判断手机号是否重复
-
测试数据准备
# 2. 手机号是否重复 数据准备 url mobile 第1、4手机号数据库中存在 RepeJudg_mobile: url: "http://127.0.0.1:8000/mobiles/{mobile}/count/" mobiles: ['13333101010', '13748484848', '13659595959', '13333109601']
-
测试代码
from base.data_read import ReadData from base.response import method_get import pytest # ====测试数据读取==== def data_read(): # 1. 将yaml文件读取 data_dict = ReadData('data.yaml').read_data() # 2. 从字典中取数据 url usernames url = data_dict['RepeJudg_mobile']['url'] mobiles = data_dict['RepeJudg_mobile']['mobiles'] # 3. 返回数据 供测试用例调用 return url, mobiles class Test_mobile(): # ===parametrize() 方式传递给测试用例参数=== # pytest.mark.parametrize('key', [value]) @pytest.mark.parametrize('url', [data_read()[0]]) @pytest.mark.parametrize('mobiles', [data_read()[1]]) def test_username(self, url, mobiles): for mobile in mobiles: # 1.拼接完整的路由 mobile_count_url = url.format(mobile=mobile) print('\n', mobile_count_url) # 2.发送get请求 response = method_get(mobile_count_url) # 3.断言--可能访问失败 assert response.status_code == 200, '访问失败!' # print(username_count_url) # print(response.content.decode()) # 4. 根据对返回数据库username数量查询 count!=0重复 count=0不重复 count = response.json().get('count') if count == 1: print('{}重复了..'.format(mobile)) assert count == 1 else: print('{}不重复..'.format(mobile)) assert count == 0 # ==========测试结果============ http://127.0.0.1:8000/mobiles/13333101010/count/ 13333101010重复了.. http://127.0.0.1:8000/mobiles/13748484848/count/ 13748484848不重复.. http://127.0.0.1:8000/mobiles/13659595959/count/ 13659595959不重复.. http://127.0.0.1:8000/mobiles/13333109601/count/ 13333109601重复了..
VercodeJudg-图片验证码+短信验证码判断
一、图片验证码判断
说明:
- 由于后端代码图片生成url采用直接将uuid添加在url中 所以我们访问时候需要准备 uuid数据
- uuid自动生成网站:点击访问
cc66c0f4-ddfd-4f1e-888b-a4a910c0ebd9 - 开启redis服务,这里的验证码都在服务器中存着
- 自动化测试图片验证码
- 图片自动识别采用深度学习 周期长、数据量庞大
- 采用第三方的识别平台 已经训练好的模型 准确率高
-
测试数据准备
# 3. 生成图形验证码 测试数据 url UUIDs 1 4 条数据自动生成 2 3分别全是数字 字母 RepeJudg_imagecode: url: 'http://127.0.0.1:8000/image_codes/{uuid}/' uuids: ['cc66c0f4-ddfd-4f1e-888b-a4a910c0ebd9', '12345678-1234-1234-1234-123456789123', 'asdfghjk-asdf-asdf-assa-qwertyuiopas', '1977ffc4-a8a0-4138-8e94-66c37520bbc3']
-
测试用例
from base.data_read import ReadData from base.response import method_get import pytest # ====测试数据读取==== def data_read(): # 1. 将yaml文件读取 data_dict = ReadData('data.yaml').read_data() # 2. 从字典中取数据 url usernames url = data_dict['RepeJudg_imagecode']['url'] uuids = data_dict['RepeJudg_imagecode']['uuids'] # 3. 返回数据 供测试用例调用 return url, uuids class Test_image(): @pytest.mark.parametrize('url, uuids', [data_read()]) def test_img_code(self, url, uuids): for uuid in uuids: # 1.拼接路由 uuid_url = url.format(uuid=uuid) print('\n', uuid_url) # 2.发请求 response = method_get(uuid_url) # 3.断言 200 assert response.status_code == 200, '访问失败!' # 4.将图片保存到本地 方便对比检查结果 with open('report/' + uuid + '.png', 'wb') as f: f.write(response.content) # 5. 根据返回的 header数据查询 content_type='image/jpg' 有这个数据则证明测试成功(因为后端代码业务逻辑中这样请求头类型) # print(response.headers) # print(type(response.json())) judge = response.headers['Content-Type'] if judge == 'image/jpg': print('{}---的图片生成成功!..'.format(uuid)) else: print('{}---的图片生成失败!..'.format(uuid)) # =========================测试结果===================== # 说明:1. 第一条是自动生成的UUID 第二条全数字的UUID可见复合Django的uuid转换器判断规则 第三条是全字母的**UUID不符合规则** 第四条由于循环到第三条终止 但第四条也是自动生成的 http://127.0.0.1:8000/image_codes/cc66c0f4-ddfd-4f1e-888b-a4a910c0ebd9/ cc66c0f4-ddfd-4f1e-888b-a4a910c0ebd9---的图片生成成功!.. http://127.0.0.1:8000/image_codes/12345678-1234-1234-1234-123456789123/ 12345678-1234-1234-1234-123456789123---的图片生成成功!.. http://127.0.0.1:8000/image_codes/asdfghjk-asdf-asdf-assa-qwertyuiopas/ F
二、短信验证码判断
说明:
- 因为我们注册账号的场景一般都是图片验证码输入正确之后才会发送短息,又因为这里短信验证码的url同样使用到了uuid+smscode
- 所以 在生成图片验证码后将图片验证码加入到准备数据中
- 联合redis进行判断 启动redis服务
数据准备
# 注意!:验证码是基于图片验证
RepeJudg_smscode:
url: 'http://127.0.0.1:8000/sms_codes/{mobile}/?image_code={image_code}&image_code_id={image_code_id}'
mobile: '13333109601'
image_code: '9JVV'
image_code_id: 'cc66c0f4-ddfd-4f1e-888b-a4a910c0ebd9'
测试用例
import pytest
from base.data_read import ReadData
from base.response import method_get
import pytest
def test_sms_codes():
# 1.解析参数
data = ReadData('data.yaml').read_data()
url = data['RepeJudg_smscode']['url']
mobile = data['RepeJudg_smscode']['mobile']
image_code = data['RepeJudg_smscode']['image_code']
image_code_id = data['RepeJudg_smscode']['image_code_id']
# 2.拼接路由 发请求
sms_code_url = url.format(mobile=mobile, image_code=image_code, image_code_id=image_code_id)
print(sms_code_url)
response = method_get(sms_code_url)
# 3.断言 防止请求失败
assert response.status_code == 200, '访问失败!'
# 4.对后端请求成功返回的json字符串的代表业务逻辑没问题的code字段判断
errmsg = response.json().get('errmsg')
print(response.json())
# 5.errmsg=ok 发送短信验证码成功
assert errmsg == 'ok', '发短信失败!'
# ==========测试结果=========
{
'errmsg': 'ok'}
Registe-注册
-
说明
- 这里用到的sms_code需要先进行短信验证码测试 短信验证码有redis的失效时间注意更新
- 同样的sms_code又依赖于image_code 也有失效时间
-
数据准备
# 5. 注册测试 # 注册 测试数据 test_register: url: 'http://127.0.0.1:8000/register/' username: 'oyangnanan' password: '123456789' password2: '123456789' mobile: '13888888888' sms_code: '083684' allow: 'True'
-
测试用例
from base.data_read import ReadData from base.response import method_post def test_registers(): # 1.解析参数 data = ReadData('data.yaml').read_data() url = data['test_register']['url'] json_dict = { "username": data['test_register']['username'], "password": data['test_register']['password'], "password2": data['test_register']['password2'], "mobile": data['test_register']['mobile'], "sms_code": data['test_register']['sms_code'], "allow": data['test_register']['allow'] } # option/alt # 2.发请求post response = method_post(url, json=json_dict) # 3.断言 assert response.status_code == 200, '访问失败' print(response.json()) code = response.json().get('code') assert code == '0', '注册失败!'
Login-登录(注销)单元测试
Login-普通登录
-
数据准备
#6. 普通登录测试 test_user_login: url: 'http://127.0.0.1:8000/login/' accounts: [{ 'username':'oyangnanan', 'password':'123456789', 'remembered':'True'}, { 'username':'xxxxxxx', 'password':'12345678', 'remembered':'True'}]
-
测试用例
from base.data_read import ReadData from base.response import method_post import pytest # ====测试数据读取==== def data_read(): # 1. 将yaml文件读取 data_dict = ReadData('data.yaml').read_data() # 2. 从字典中取数据 url accounts url = data_dict['test_user_login']['url'] accounts = data_dict['test_user_login']['accounts'] # 3. 返回数据 供测试用例调用 return url, accounts class TestLogin(): @pytest.mark.parametrize('url', [data_read()[0]]) @pytest.mark.parametrize('accounts', data_read()[1]) def test_login(self, url, accounts): # 1. 注意这里的account是列表格式 print('\n>>>', type(accounts)) print(accounts) # 2. 发请求 response = method_post(url, json=accounts) # 3. 判断是否请求成功 assert response.status_code == 200 # 4. 根据返回的code判断是否登录成功 code = response.json().get('code') print(response.json()) assert code == 0, '登录失败!' # =============测试结果============== ========= 1 failed, 1 passed in 11.81s ==========
QQLogin-QQ登录
-
说明
-
QQ登录第一步就是生成一个QQ对象根据对象进行登录页面跳转
-
第二步流程较复杂,之后的文章更新中可以测试,若不考虑内部实现逻辑,可以直接使用工厂函数
-
第二章节链接(内用django的FactoryRequest工厂请求函数使用):https://blog.csdn.net/Pei_Jobs/article/details/108980746
-
-
数据准备
test_qq_login: url1: 'http://127.0.0.1:8000/qq/authorization/'
-
测试用例
### =====QQ 登录即构造一个QQ的跳转链接去登录QQ==== class QQLoginUrlViews(View): """ QQ登录 """ def get(self, request): # 1. 初始化生成qq对象 QQ = OAuthQQ(client_id=settings.QQ_CLIENT_ID, client_secret=settings.QQ_CLIENT_SECRET, redirect_uri=settings.QQ_REDIRECT_URI, state=None) # 2. 使用qq对象生成跳转链接 login_url = QQ.get_qq_url() return JsonResponse({ 'code': 0, 'login_url': login_url, 'errmsg': 'ok'}) # ==================================测试用例================ from base.data_read import ReadData from base.response import method_get import pytest # ====测试数据读取==== def data_read(): # 1. 将yaml文件读取 data_dict = ReadData('data.yaml').read_data() # 2. 从字典中取数据 url accounts url1 = data_dict['test_qq_login']['url1'] # accounts = data_dict['test_user_login']['accounts'] # 3. 返回数据 供测试用例调用 return url1 # ====测试==== class Test_qqlogin(): def setup_method(self): # 1. 发请求 response = method_get(data_read()) # 3. 判断是否请求成功 assert response.status_code == 200 # 4. 根据返回的code判断是否登录成功 code = response.json().get('code') # print(response.json()) assert code == 0, '登录失败!' # 5. 登录成功输出跳转链接 print(response.json()['login_url'])
Logout-退出登录
-
说明
- 退出登陆之前一定要有一个 账号已经登录或保持登录状态 用之前的登录数据
- 退出登录 用到Seesion模拟请求方式 在第二章节有使用说明
- 第二章节链接(Request模块使用中):https://blog.csdn.net/Pei_Jobs/article/details/108980746
-
数据准备
#8. 退出登录 logout test_logout: url: 'http://127.0.0.1:8000/logout/'
-
测试用例
# 1.先登录 from requests import Session from base.data_read import ReadData from base.response import method_post import pytest class TestInfo(): # 准备登录状态 用到了之前的登录数据 def setup_class(self): # 1. 将yaml文件读取 data_dict = ReadData('data.yaml').read_data() # 2. 从字典中取数据 url accounts url = data_dict['test_user_login']['url'] accounts = data_dict['test_user_login']['accounts'][0] # 3.实例化session对象 实现用session模拟对象登录 self.session = Session() self.session.post(url, json=accounts) # 测试登出 def test_user_logout(self): # 1. 获取数据 data = ReadData('data.yaml').read_data() url = data['test_logout']['url'] # 2. 检查是否有cookie数据 print('登录:', self.session.cookies) # 3. 发送请求 删除session对象 cookie自然也就删除 response = self.session.delete(url) # 4. 断言判断 退出是否访问成功 assert response.status_code == 200 print('退出cookies:', self.session.cookies) # 5. 判断cookie是否为空 if self.session.cookies: assert 0, '退出失败!' else: print('退出成功!')
Userinfo-用户中心单元测试
-
说明
- 这里采用 fixture的方案实现 进入用户中心之前的用户登录状态cookie保持
- 第二章节链接(Pytes_fixture):https://blog.csdn.net/Pei_Jobs/article/details/108980746
-
数据准备
#9. 用户中心 test_user_info: url: 'http://127.0.0.1:8000/info/'
-
测试用例
# =====================conftest.py 共享(fixture函数)文件 实现登录============ from base.data_read import ReadData from base.response import method_post import pytest # 1. 实现 登录功能 返回有效的cookie @pytest.fixture() def login_user(): # 1. 获取之前的登录测试的用户数据 data = ReadData('data.yaml').read_data() url = data['test_user_login']['url'] account = data['test_user_login']['accounts'][0] response = method_post(url, json=account) assert response.status_code == 200 # 2. 返回登录成功后携带的cookie值 return response.cookies # =============================测试用例===================== from base.data_read import ReadData from base.response import method_get # 这里使用 fixture共享的准备函数 实现登录状态 并传递cookie def test_fixture_login_info(login_user): print('\n>>', login_user) # 1. 获取数据 data = ReadData('data.yaml').read_data() url = data['test_user_info']['url'] # 2. 携带cookie(来自login_user) 发送个人中心请求 resppnse = method_get(url, cookies=login_user) # 3. 断言判断个人中心是否请求成功 assert resppnse.status_code == 200 # 4. 根据返回code值判断是否接受到个人中心数据 code = resppnse.json().get('code') print(resppnse.json()) assert code == 0, '个人中心测试失败!' # ====================测试结果====================== { 'code': 0, 'errmsg': 'ok', 'info_data': { 'username': 'oyangnanan', 'mobile': '13888888888', 'email': '', 'email_active': False}}