1.单元测试
单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作,是测试最小独立的单元模块
如果有需要,可以给项目进行单元测试,主要目的有消灭低级错误、减少和快速定位BUG、提高代码质量
2.django单元测试
我们在创建好一个APP的时候,它会在自动在APP目录创建一个tests.py文件,这个就是拿来做单元测试的
apps/users/tests.py
from django.test import TestCase
class MyTestCase(TestCase):
@classmethod
def setUpClass(cls):
print('setUpClass')
@classmethod
def tearDownClass(cls):
print('tearDownClass')
def setUp(self):
print("setUp")
def tearDown(self):
print("tearDown")
def test_2_func(self):
print("test_2_func")
def test_1_func(self):
print("test_1_func")
终端命令,有多种执行方法
python3 manage.py test -t . # 执行所有测试
python manage.py test meiduo_mall.apps.users # 执行某个APP的所有test文件
python manage.py test meiduo_mall.apps.users.tests # 执行某个APP的某个tests文件
执行结果
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
setUpClass
setUp
test_1_func
tearDown
.setUp
test_2_func
tearDown
.tearDownClass
----------------------------------------------------------------------
Ran 2 tests in 0.004s
OK
Destroying test database for alias 'default'...
从执行过程打印的信息我们可以知道
(1).测试类要继承TestCase类
(2).执行前会自动创建一个mysql数据库,执行后自动销毁
(3).setUpClass和tearDownClass这两个静态方法分别会在测试所有用例执行前后各执行一次
(4).setUp和tearDown这两个方法会在每个测试用例前后都各执行一次
(5).setUp和tearDown、def setUpClass和tearDownClass成对出现
(6).用例方法必须以test开头,并非自身往下执行,而是按照方法名字符按照ASCII码排序,顺序是0-9、A-Z、a-z
(7).测试用例执行成功返回一个小数点,失败则返回F
另外,在执行过程可能会遇到django.db.utils.OperationalError: (1366, "Incorrect string value: '\\xE7\\x94\\xA8\\xE6\\x88\\xB7' for column 'name' at row 5")
的问题
这个时候,打开Django的配置文件,在DATABASES配置项里添加一个TEST指定单元测试的字符编码
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 数据库引擎
...
"TEST":{
"CHARSET":"utf8",
"COLLATION":"utf8_general_ci"
},
},
}
根据以上信息,我们可以写一个登录、查询、登出的测试例子
from django.test import TestCase
from requests import Session
class MyTestCase(TestCase):
session = None
@classmethod
def setUpClass(cls):
info = {
"username": "13978414080",
"password": "123456789",
"remembered": True
}
cls.session = Session()
resp = cls.session.post("http://127.0.0.1:8000/login/", json=info)
result = resp.json()
if result["code"] == 0:
print("登录成功")
@classmethod
def tearDownClass(cls):
resp = cls.session.delete('http://127.0.0.1:8000/logout/')
result = resp.json()
if result["code"] == 0:
print("已退出登录")
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):
resp = self.session.get("http://127.0.0.1:8000/addresses/")
print(resp.json())
3.Client
3.1 Client与Session的区别
我们在上面的模拟测试中使用了requests.Session()来状态保持,其实也可以使用Client类,它还是与Session有些区别的
(1).Client不需要启动服务器就能测试Django项目,也只能在Django中测试
(2).Client的请求url不需要带协议和域名端口,只需要路径部分
3.2 GET和POST
(1)get (path,data={},follow=False,**extra)
(2)post (path, data={}, content_type=MULTIPART_CONTENT, follow=False, **extra)
content_type:默认是multipart/form-data,可以指定为application/json
这次我们使用Client实现注册、登录、查询、登出,登出之后再查询一下信息
from django.test import TestCase
from django_redis import get_redis_connection
from requests import Session
class MyTestCase(TestCase):
def setUp(self):
print("setUp")
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):
_uuid = '0fdb75a8-d60e-4598-86bc-c3e7991aadf9'
self.client.get("/image_codes/%s/" % _uuid)
conn = get_redis_connection('verify_code')
ret = conn.get("img_%s" % _uuid)
mobile = "13977778888"
self.client.get("/sms_codes/%s/?image_code=%s&image_code_id=%s" % (mobile,ret.decode(),_uuid))
user_info = {
"username": "13978414088",
"password": "123456789",
"password2": "123456789",
"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": "13978414088",
"password": "123456789",
"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/')