单元测试——django单元测试
1、介绍
单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。
1.1、单元测试和接口测试的区别
单元测试注重代码逻辑,接口测试注重业务逻辑
单元测试的粒度最小,是测试最小独立的单元模块(不依赖其他模块);接口测试不是,会覆盖很多
单元测试关注的是代码的实现和逻辑,测试范围较小,保证实现逻辑通过就行;接口测试因为关注业务,所以测试范围较广,会用更多的测试数据去测试
1.2、单元测试的优点
消灭低级错误
- 基本的单元测试,可以在系统测试之前,把大部分比较低级的错误都消灭掉,减少系统测试过程中的问题,这样也就减少了系统测试中定位和解决问题的时间成本。
减少bug,快速定位bug
- 某些类型的bug,靠系统测试是很难找到的。例如一些代码分支,平时99%的场景基本上都走不到,但一旦走到了,如果没有提前测试好,那么可能就是一个灾难。
提高代码质量
- 写单元测试的过程中,顺手把一些code重构了。
- 一些长得非常像的代码,如果每次都要写一堆测试代码去测同样的code,这样很让人抓狂。于是设法把待测试的code改得尽量的精简,重复代码减少,这样覆盖率上去了,测试也好测了,代码也简洁了。
2、基于django的单元测试
2.1、环境准备
测试数据库编码设置,增加配置:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'meiduo_mall_db',
'HOST': '127.0.0.1',
'USER': 'root',
'PASSWORD': 'mysql',
'PORT': 3306,
'TEST': {
'CHARSET': 'utf8',
'COLLATION': 'utf8_general_ci'
},
},
}
2.2、django单元测试
Django中有完善的单元测试,可以对开发的每一个功能进行单元测试,就可以测试功能是否正常。
Django 创建好项目后会在每个应用里自动创建一个tests.py 文件,可以直接在这个文件编写测试用例。
编写测试代码
- 单元测试通过类继承的方式,继承自
django.test.TestCase。 - 在类中定义
test开头的方法,可以定义多个,一个方法就是一个测试用例。 - TestCase 中的 setUp 方法会在每一个测试用例执行之前被调用,tearDown 方法会在每一个测试用例执行之后被调用,这两个方法的名字是固定的。
# 导入 django.test.TestCase
from django.test import TestCase
class MyClassTestCase(TestCase):
def setUp(self):
"""前置准备"""
pass
def tearDown(self):
"""后置处理"""
pass
def test_my_func(self):
"""测试功能"""
passdjango单元测试运行命令
# 执行项目中全部的测试用例 # 由于 美多 项目的目录结构和 django 项目结构不一致,所以这里需要加上 "-t ." 参数 python manage.py test # 运行某个名为users的app下面的所有的测试用例,users是应用的名字(APP_name),应用中所有以test开头的py文件都会执行 python manage.py test meiduo_mall.apps.users # 运行某个app下面的tests.py文件 python manage.py test meiduo_mall.apps.users.tests # 运行某个app下面的tests.py文件中指定的class类 python manage.py test meiduo_mall.apps.users.tests.MyClassTestCase # 执行ModeTest类下的某个测试方法 python manage.py test meiduo_mall.apps.users.tests.MyClassTestCase.test_my_func
注意:
- django 在测试过程中会建立一个用于测试的数据库
2.3、TestCase类
Django的TestCase继承了Python的unittest.TestCase, 所以这两个功能大部分一致(Python很多框架都集成了unittest)。
- django.test.TestCase类主要由
前、后置处理方法和test开头的方法组成test开头的方法是编写了测试逻辑的用例setUp方法 (名字固定)在每一个测试方法执行之前被调用tearDown方法(名字固定) 在每一个测试方法执行之前被调用setUpClass类方法(名字固定)在整个类运行前执行只执行一次tearDownClass类方法(名字固定)在调用整个类测试方法后执行一次
前后置方法运行特点
在django示例项目的 meiduo_demo/users/中新建的test_code文件夹,在其中新建test_1_run.py文件,完成如下代码:
from django.test import TestCase
class MyClassTestCase(TestCase):
# 在所有测试用例执行之前调用
@classmethod
def setUpClass(cls):
print('setUpClass在所有测试用例执行之前调用')
# 在所有测试用例执行之后调用
@classmethod
def tearDownClass(cls):
print('tearDownClass在所有测试用例执行之后调用')
# 在每一个测试方法执行之前被调用
def setUp(self):
print('setUp在每一个测试方法执行之前被调用')
# 在每一个测试方法执行之后被调用
def tearDown(self):
print('tearDown在每一个测试方法执行之前被调用')
# 测试方法以 test 开头
def test_1(self):
print('test 1')
def test_2(self):
print('test 2')运行单元测试python manage.py test meiduo_mall.apps.users.test_code
setUpClass 和 tearDownClass应用场景
当类中的测试用例需要共享一些数据,比如,测试:获取用户信息、获取用户浏览器记录、获取用户地址列表时,需要先进行用户登录,测试完成之后进行用户退出。
如果通过 setUp 和 teardown 方法,那么就需要多次进行登录、退出,这样是重复的代码,通常是不必要的。
可以通过 setUpClass 和 tearDownClass 做类级别的前置处理(例如,用户登录)和后置处理(例如,退出登录),这两个方法每个类中只会执行一次。
# 定义 setUpClass: 用户登录
# 定义 tearDownClass: 用户退出
# 定义测试方法:获取用户信息、获取用户浏览器记录、获取用户地址列表
from django.test import TestCase
from requests import Session
class MyClassTestCase(TestCase):
# 类属性
session = None
# 在所有测试用例执行之前调用
@classmethod
def setUpClass(cls):
print('setUpClass')
# 构造账号密码
info = {
"username": "mike123",
"password": "chuanzhi12345",
"remembered": True
}
# 实例化session对象,此为类属性
cls.session = Session()
# 登录
resp = cls.session.post(
'http://127.0.0.1:8000/login/',
json=info
)
# 获取 json 响应
result = resp.json()
# 检查是否登录成功
# 检查是否登录成功
if result['code'] != 0:
print('登录失败')
# 在所有测试用例执行之后调用
@classmethod
def tearDownClass(cls):
print('tearDownClass')
# 登出
cls.session.delete('http://127.0.0.1:8000/logout/')
# 获取登陆用户
def test_1_info(self):
# 查看用户状态
resp = self.session.get('http://127.0.0.1:8000/info/')
print(resp.json())
# 获取用户浏览历史
def test_2_history(self):
resp = self.session.get('http://127.0.0.1:8000/browse_histories/')
print(resp.json())
def test_3_addresses(self):
# 调用查看地址列表接口
url = 'http://127.0.0.1:8000/addresses/'
resp = self.session.get(url)
print(resp.json())2.4、Client类
Client类似于requests,可以充当HTTP请求客户端。提供了get,post等方法用于模拟请求
Client和requests和区别:
- client的实例对象能够实现状态保持,类似于requests的session对象
- 使用client测试时,不需要启动服务,requests必须要启动服务才能测试
- client只能在django的测试代码中使用,使用方式和requests中的session类似
- client的get、post等请求方法,不需要携带”schema://domain”,只需要路径即可,例如”/login/”
常用方法
- get(path, data={}, follow=False, **extra)
功能:发起 get 请求返回 response 对象。
参数说明:
- path 请求的 url, 在 urlconf 中配置的 url
- data 请求时的参数, 转换后为查询字符串
- post(path, data={}, content_type=MULTIPART_CONTENT, follow=False, **extra)
功能:发起一个POST请求并返回response对象
参数说明:
- path 请求的url, 在urlconf中配置的url
- data 传递的参数
- content_type 设置传递参数的类型, 默认是
multipart/form-data
- 如果data传递json格式数据,content_type需要赋值为
application/json
案例
from django.test import TestCase, Client from django_redis import get_redis_connection class MyClassTestCase(TestCase): def setUp(self): """前置准备""" print('setUp') self.client = Client() def tearDown(self): """后置处理""" print('tearDown') def test_api(self): self.do_register() # 注册 self.do_login() # 登陆 self.do_info() # 查看用户信息 self.do_logout() # 登出 self.do_info() # 查看用户信息 def do_register(self): # 注册 # 1. 获取验证码 _uuid = '0fdb75a8-d60e-4598-86bc-c3e7991aadf9' self.client.get(f'/image_codes/{_uuid}/') # 发请求 # 2. 从redis中获取验证码 conn = get_redis_connection('verify_code') ret = conn.get("img_%s" % _uuid) print(ret.decode()) # 3. 发送短信验证码 mobile = '18666667777' self.client.get(f'/sms_codes/{mobile}/?image_code={ret.decode()}&image_code_id={_uuid}') # 4. 注册 user_info = { "username": "mike234", "password": "chuanzhi12345", "password2": "chuanzhi12345", "allow": True, "mobile": mobile, "sms_code": "123456", } resp = self.client.post('/register/', data=user_info, content_type='application/json') print(resp.json()) def do_login(self): # 登陆 user_info = { "username": "mike234", "password": "chuanzhi12345", "remembered": True, } resp = self.client.post('/login/', data=user_info, content_type='application/json') print(resp.json()) def do_info(self): # 查询登陆用户信息 resp = self.client.get('/info/') print(resp.json()) def do_logout(self): # 登出 self.client.delete('/logout/')
浙公网安备 33010602011771号