概述:

1: 作用: 对测类,方法,函数进行测试。

2: 单元测试是测试的最小单位

3: 由于django的特殊性,通过接口测试单元。因为django中的每个单元都是在类视图中。

4: 单元测试的好处:


消除低级错误
减少Bug, 消除低级Bug
提高代码质量(测试后快速优化)


一: 基于Django的单元测试:

1: 概述:


Django环境
1: 配置文件mysql设置字符集编码为utf-8
2: 数据库的用户最好是用root,因为测试过程中会出现自动建临时库的操作。



每个应用自带test.py
每个类必须继承django.test.TestCase
每个类可以具备一个前置和后置方法
测试方法的名称必须以test开头


2: 样例

from django.test import TestCase

# Create your tests here.


class MyClassTestCase(TestCase):
def setUp(self):
"""前置准备"""
print('setUp')

def tearDown(self):
"""后置处理"""
print('tearDown')

def test_my_func(self):
"""测试功能"""
print('测试用例')
def test_my_func2(self):
"""测试功能"""
print('测试用例2')

注意: django执行测试用例时,每次都会先执行setUp方法,最后必然执行tearDown方法。比如上面的就会执行两次setUp和两次tearDown。

3: 单元测试命令运行:

1: 执行所有的Test文件:

cd manager.py所在的目录
python manager.py test

2: 运行某个app下面的所有的测试用例:

cd manager.py所在的目录
python manager.py test meiduo_mall.apps.users

3: 运行某个指定的测试用例:(最常用)

cd manager.py所在的目录
python manager.py test meiduo_mall.apps.users.tests

4: 运行某个指定测试用例的某个类:

cd manager.py所在的目录
python manager.py test meiduo_mall.apps.users.tests.MyClassTestCase

5: 运行某个指定测试用例的某个类中的方法:

cd manager.py所在的目录
python manager.py test meiduo_mall.apps.users.tests.MyClassTestCase.test_my_func

二:TestCase类

1: Django的TestCase继承了Python的unittest.TestCase, 所以这两个功能大部分一致(Python很多框架都集成了unittest)。


django.test.TestCase类主要由前、后置处理方法 和test开头的方法组成
test开头的方法 是编写了测试逻辑的用例
setUp方法 (名字固定)在每一个测试方法执行之前被调用
tearDown方法(名字固定) 在每一个测试方法执行之前被调用
setUpClass类方法(名字固定)在整个类运行前执行只执行一次
tearDownClass类方法(名字固定)在调用整个类测试方法后执行一次


2: 样例

rom 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')

单元测试---TestCase,Client,断言_json

3: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())
$ python manage.py test meiduo_mall.apps.users.test_code.test_2_case
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
setUpClass
{'code': 0, 'errmsg': 'ok', 'info_data': {'username': 'mike123', 'mobile': '13344445555', 'email': '', 'email_active': False}}
.{'code': 0, 'errmsg': 'ok', 'skus': []}
.{'code': 0, 'errmsg': 'ok', 'default_address_id': None, 'addresses': []}
.tearDownClass

----------------------------------------------------------------------
Ran 3 tests in 0.383s

OK
Destroying test database for alias 'default'...

三: Client类:

1: Client的特点:


  • Client的实例对象可以进行状态保持
  • 使用Client进行测试时,不需要启动服务器
  • Client测试时Django特有的,只能测试Dijango代码
  • Client的get, post方法不需要携带协议,域名,端口,只需要携带路径
  • 如果发送的是json数据,需要指定数据格式content_type='application/json’

2:案例: 对美多商城进行注册,然后登录,然后查看用户信息,最后退出登录。

from django.test import TestCase
import requests
from django_redis import get_redis_connection

class MyTest(TestCase):

def setUp(self):
print("setUp")
# 创建一个Session实例对象
self.s = requests.Session()

def tearDown(self):
print("tearDown")
del self.s

def test_xxx(self):
# self.do_register()
self.do_login()
self.do_info()
self.do_logout()


# 注册
def do_register(self):
print("进行注册")
_uuid = "0fdb75a8-d60e-4598-86bc-c3e7991aadf9"
# 发送获取图片验证码请求
self.s.get("http://localhost:8000/image_codes/%s/" % _uuid)
# redis中获取图片验证码
conn = get_redis_connection("verify_code")
image_code_from_redis = conn.get("img_%s" % _uuid).decode()
print(image_code_from_redis)

# 发送短信验证码
mobile = "13377778888"
r = self.s.get('http://localhost:8000/sms_codes/%s/?image_code=%s&image_code_id=%s' % (mobile, image_code_from_redis, _uuid))
print('短信验证码结果 = ', r.json())

# 进行注册
info = {
"username": "mikeabc",
"password": "chuanzhi12345",
"password2": "chuanzhi12345",
"allow": True,
"mobile": mobile,
"sms_code": "123456"
}

r = self.s.post('http://localhost:8000/register/', json=info)
print('注册结果 = ', r.json())

# 登陆
def do_login(self):
info = {
"username": "mikeabc",
"password": "chuanzhi12345",
"remembered": True,
}

r = self.s.post('http://localhost:8000/login/', json=info)
print('登陆结果 = ', r.json())

# 获取用户信息
def do_info(self):
r = self.s.get('http://localhost:8000/info/')
print('用户信息结果 = ', r.json())

# 退出登陆
def do_logout(self):
r = self.s.delete('http://localhost:8000/logout/')
print('登出结果 = ', r.json())

将上面代码转换成使用Client类来操作:

from django.test import TestCase, Client
import requests
from django_redis import get_redis_connection


class MyTest(TestCase):
def setUp(self) -> None:
print('setUp')
# 创建一个Client()对象,实例属性
self.s = Client()


def tearDown(self) -> None:
print('tearDown')
del self.s # 删除变量,不是必须,这里只是为了匹配

def test_xxx(self):
self.do_register()
self.do_login()
self.do_info()
self.do_logout()
self.do_info()

def do_register(self):
"""注册"""
print('注册')
_uuid = '0fdb75a8-d60e-4598-86bc-c3e7991aadf9'

# a.发送图片验证玛
# http://域名或ip:端口/image_codes/0fdb75a8-d60e-4598-86bc-c3e7991aadf9/
self.s.get('/image_codes/%s/' % _uuid)


# b.发送短信验证码,需要上一步图片验证码(直接读redis)
# 从redis读取图片验证码
# 1.2 读取redis中的图形验证码
conn = get_redis_connection('verify_code')
# redis返回的数据,如果没有则None,如果有返回的是字符串的字节形式b'CFGR' --> b'CFGR'.decode() --> 'CFGR'
image_code_from_redis = conn.get('img_%s' % _uuid)
image_code = image_code_from_redis.decode()
print(image_code)

mobile = "13377778888"
# http://域名或ip:端口/sms_codes/任意手机号/?image_code=上一步获取的验证码&image_code_id=0fdb75a8-d60e-4598-86bc-c3e7991aadf9
r = self.s.get('/sms_codes/%s/?image_code=%s&image_code_id=%s' % (mobile, image_code, _uuid))
print('短信验证码结果 = ', r.json())

# c.注册(手机验证为上一步,验证码固定为123456)
info = {
"username":"mikemike",
"password":"chuanzhi12345",
"password2":"chuanzhi12345",
"allow":True,
"mobile":mobile,
"sms_code":"123456"
}

# client.post只有data关键字参数,需要通过content_type='application/json'指定请求体为json格式
r = self.s.post('/register/', data=info, content_type='application/json')
print('注册结果 = ', r.json())

def do_login(self):
info = {
"username":"mikemike",
"password":"chuanzhi12345",
"remembered":True,
}

r = self.s.post('/login/', data=info, content_type='application/json')
print('登陆结果 = ', r.json())

def do_info(self):
r = self.s.get('/info/')
print('用户信息结果 = ', r.json())

def do_logout(self):
r = self.s.delete('/logout/')
print('登出结果 = ', r.json())

测试结果:

单元测试---TestCase,Client,断言_redis_02

四:断言

  • Django 当中提供了多种断言方法,这些方法帮助我们进行断言,msg 参数是可选的描述信息,当断言方法失败时会抛出 AssertionError。

常见断言方法如下:

单元测试---TestCase,Client,断言_django_03

# 断言:预期结果和实际结果是否符合
# 如果ret['code']和0相等,说明符合预期,测试通过,断言成功,msg的信息不会显示
# 如果ret['code']和0不相等,说明不符合预期,测试失败,断言失败,msg的信息会显示
# 断言失败,标志为F,抛出异常
self.assertEqual(ret['code'], 0, msg=ret['errmsg'])