Django偏爱使用Python标准库中内置的unittest模块来编写测试,当然在Django中进行web测试也可以使用另一个python测试框架,Django提供了用于这种集成的API和工具。
Django中创建应用(mylists)的测试写在应用目录(mylists/)下的tests.py文件中。测试系统会自动在所有以tests开头的文件里寻找并执行测试代码。
Django中创建的测试类都继承自django.test.TestCase,这个类又继承自unittest.TestCase,编写好测试类及对应的以tests开头的测试用例后,可以使用以下命令执行:
python manage.py test mylists
执行命令,测试主要经过了以下一些过程:
1)命令将会寻找mylists应用里(mylists目录下)的测试代码;
2)找到django.test.TestCase的子类;
3)创建一个特殊的数据库供测试使用;
4)在类中寻找测试方法–以tests开头的方法
5)执行方法中的内容(创建变量、实例、断言等)
注意⚠️:如果测试用例依赖数据库连接,比如创建或查询模型,那么编写的测试类需继承自django.test.TestCase,而不是直接继承unittest.TestCase,而如果不依赖数据库,直接继承unittest.TestCase就好,因为使用unittest.TestCase可以避免在交互中运行每个测试用例及刷新数据库的成本。
当然,测试时创建的数据库不是开发时实际的数据库,而是专门为测试创建的空白数据库,当所有的测试都执行完成后,测试的数据库就会销毁。
可以使用命令:
python manage.py test --keepdb
保留测试数据库。
如果在测试用例执行过程中,使用了Ctrl+C中断了测试的执行,用于测试的数据库没有被自动销毁,当运行下一次测试时,会被询问是否reuse or destory the database。使用命令:
python manage.py test --noinput
会强制禁止显示该提示并且会自动销毁之前的测试数据库。
Django提供了一个供测试使用的Client来模拟用户和视图层代码的交互。
Client测试视图函数以及可以和基于django的应用编程交互。
Client测试工具可以完成下列事情:
1)模拟Get和Post请求,观察响应结果–从HTTP到页面内容;
2)查看重定向链以及在每一步确认URL和状态码;
3)用一个包括特定值的模板内容来测试一个被Django模板渲染的请求。
注意⚠️:django的client测试工具不是替代selenium或其他网页测试工具,client有自己的关注点,简要的说有以下两点:
1)使用django的client建立正确的模板,并且向模板传递正确的内容;
2)使用selenium这类web自动化测试框架来测试渲染的HTML以及网页行为,django也提供了对这些框架的支持,如LiveServerTestCase。
使用Client的一个简单例子:
from django.test import Client
c = Client()
response = c.post('/login/', {'username' : 'banana', 'password' : '123456'})
response.status_code
response = c.get('/user/details/')
response.content
注意⚠️:
测试工具Client不要求网页服务器在运行着,这是因为client避免了HTTP的开销直接处理Django框架,这对快速运行单元测试是有帮助的;
另外,当需要检索页面时,只需要给定URL的路径,不需要整个域名,比如:
c.get('/login/')
正确,但以下这种方法就是错误的
c.get('https://www.mysite.com/login/')
当然,client测试工具不能检索哪些不是由django项目启动的web页面,如果需要检索这些页面,可以使用python标准库urllib或urllib2。
为解析URL,client会使用由你ROOT_URLCONF配置中的URLconf。
默认情况下,client将禁用任何由你的站点执行的CSRF检查,若想要client测试执行CSRF检查,可以在创建client时添加参数:
csrf_client = Client(enforce_csrf_checks = True)
Client类原型是
class Client(enforce_csrf_checks=False, json_encoder=DjangoJSONEncoder, **defaults)源代码
一般情况下,创建client实例只需使用默认参数即可。
c = Client()
可调用的方法
创建了一个实例之后,可以使用以下方法:
1)get方法
get(path, data=None, follow=False, secure=False, **extra)源代码
通过给定的路径发送一个GET请求,返回一个响应对象。
若使用下面的代码使用get方法(直接使用data参数形式):
c.get('/mysite/details/', {'username' : 'banana', 'password' : '123456'})
此时对get的请求就等同于/mysite/details/?username=banana&password=123456,这也就是URL的编码形式,若已经知道了这一编码形式,也可以使用下面的方法来获取相同的页面:
c.get('/mysite/details/?username=banana&password=123456')
若两种形式都给定的话,client会优先选择data参数的形式。
如果将follow参数置为True,client将follow任意重定向,并且可以通过
response = c.get('/redirect/',follow = True)
response.redirect_chain
方法来查看redirect_chain属性,返回的值是intermediate urls和状态码组成的元组。
若将参数secure设置为True,client将模拟一个HTTPS请求。
2)post方法
post(path, data=None, content_type=MULTIPART_CONTENT, follow=False, secure=False, **extra)源代码
通过一个给定路径,发送一个POST请求,返回一个Response对象。
采用字典的形式提交给POSTdata参数:
c = Client()
c.post('/login/', {'username' : 'banana', 'password' : '123456'})
以上c.post方法就是使用POST数据:username=banana&password=123456去请求网页/login/。
如果提供content_type参数(例如text/xml),数据会被作为报头中Content-Type的类型进行POST上传,若不提供,数据会被作为multipart/form-data类型上传。
如果给定的data中一个key对应多个value值,比如数据的值choices键值对应三个选择的值(‘a’,‘b’,‘c’),可以这样编写data参数:
{'choices':('a','b','c')}
如果POST数据是一个文件,data参数的key值就是文件字段名称,需要上传的文件句柄就是对应的value值。
c = Client()
with open('wishlist.doc') as fp:
c.post('/mysite/wishes/',{'name' : 'banana', 'attachment' : fp})
这里的attachment可以换成任何你想要的文件处理代码。
当然,也可以提供file-like对象(如:StringIO 、BytesIO)作为文件句柄。
⚠️如果你希望多次使用post方法提交同一个文件,你需要在各次post之间手动的重置文件指针,最简单的方法就是当一次post完成后,手动关闭文件。
若使用POST方法时,URL已经包含了编码参数,而这些参数在request.GET
方法中是可用的:
c.post('/login/?visitor=true',{'name' : 'banana', 'password' : '123456'})
处理此请求的视图查询request.POST去检索用户名和密码,同时使用request.GET来确定当前是否是visitor。
当参数follow设为True时,client与使用GET方法一致。
参数secure也与使用GET方法一致。
3)head方法
head(path, data=None, follow=False, secure=False, **extra)源代码
发送一个HEAD请求,这个方法与get方法方式类似,这个方法不返回消息主体。
4)options方法
options(path, data=’’, content_type=‘application/octet-stream’, follow=False, secure=False, **extra)源代码
发送一个OPTIONS请求,这对测试RESTful接口很有用。
5)put方法
put(path, data=’’, content_type=‘application/octet-stream’, follow=False, secure=False, **extra)源代码
发送一个PUT请求,对测试RESTful接口很有用
6)patch方法
patch(path, data=’’, content_type=‘application/octet-stream’, follow=False, secure=False, **extra)源代码
发送PATCH请求,对测试RESTful接口很有用
7)delete方法
delete(path, data=’’, content_type=‘application/octet-stream’, follow=False, secure=False, **extra)源代码
发送DELETE请求,对测试RESTful接口很有用
8)trace方法
trace(path, follow=False, secure=False, **extra)源代码
发送TRACE请求,用于模拟诊断探针。
9)login方法
login(**credentials)源代码
如果你的site使用的是Django的身份验证系统,并且你要处理用户登录,那么可以使用测试客户端的login方法来模拟用户登录site的效果。
当调用此方法后,client将拥有任何可能构成视图的的所有cookies和session数据,而这些数据是在登录时所需要的数据。
参数credentials的格式依赖于使用的身份验证系统,这个身份验证系统由AUTHENTICATION_BACKENDS配置文件进行配置,若使用的是默认的,那么验证信息就是用户的用户名和密码:
c = Client()
c.login(username = 'banana', password = '123456')
若用户名和密码都通过的话,login方法返回值为True,并且登录成功。
10)force_login方法
force_login(user, backend=None)源代码
这个方法可以模仿用户登录站点的效果,当一项测试需要用户登录但用户登录方式的详情不重要时可以使用这种方法,无需使用login方法(这种方法速度更快)。
使用这种方法跳过了身份验证环节,不活跃的用户也允许登录,并且不需要用户的验证信息。
11)logout方法
logout()源代码
模拟用户退出登录的效果,调用该方法后,client将所有cookies和session数据清空到默认值。
client类中的get()和post()方法都能够返回一个响应对象,这个响应对象与由django视图返回的HttpResponse对象不相同,测试响应对象有一些对测试代码验证的额外的数据,包含以下属性:
1)client
client属性用于发出请求,这个请求能够返回响应对象。
2)content
这是响应的主体,输出形式是字节串,由视图渲染的最终页面内容,或者是错误消息。
3) context
用于渲染模板的模板上下文实例,这个模板是由响应对象的content产生的。
若渲染页面使用了多个模板,那么context对象就是context 对象组成的列表,并且以渲染的的顺序作为下标编号。
检索context的内容可以使用如下方式:
response = client.get('/foo/')
response.context['name']
*4) json(*kwargs)
响应对象的主体以JSON格式进行解析,传入参数kwargs的内容传给json.loads(),如:
response = client.get('/foo/')
response.json()['name']
5) request
模拟响应的请求数据
6)wsgi_request
wsgirequest实例由测试句柄生成,而测试句柄由响应对象生成。
7) status_code
status_code属性是响应的HTTP状态,以整数的格式。查看所有状态码
8) templates
用来渲染最终内容的模板实例列表,按最终渲染顺序排列。列表中的每一个模板,如果模板是从一个文件中导入的,则可以使用template.name方法来获取模板的文件名称。
9) resolver_match
resolver_match属性是响应的一个ResolverMatch实例,可以使用func属性来验证提供响应的视图:
self.assertEqual(response.resolver_match.func,my_view)
self.assertEqual(response.resolver_match.func.__name__,my_view.as_view().__name__)
如果给定的URL无法请求到,使用这个属性将会产生一个Resolver404异常。
当然,也可以对响应对象使用字典语法来查询HTTP表头的任意设置的值,比如使用response[‘Content-Type’]来确定content type。
测试工具client是状态化的,如果测试响应返回一个cookie,这个cookie数据会存放在测试工具client中,若使用了get或post方法,client会将得到的cookie数据发送给这些请求方法。如果想要终止这个cookie,可以创建一个新的client实例或者手动删除。
client有以下两个属性存放在Presistent state信息中:
1)Client.cookies
这是一个python SimpleCookie对象,其中包含了所有的client cookie的当前值。
2) Client.session
这是一个字典类型的对象,包含了session信息。若要修改并保存session,必须先将其传入一个变量,这是因为每次访问此属性时都会创建一个新的SessionStore。如:
def test_session(self):
session = self.client.session
session['name'] = 'banana'
session.save()
在测试支持国际化和本地的应用程序时,需要为client request设置语言,设置语言的方法取决于LocaleMiddleware是否启用。
1)LocaleMiddleware启用了
此时设置语言可以通过创建一个cookie,这个cookie的key值为LANGUAGE_COOKIE_NAME,对应的value值为language code,如:
from django.conf import settings
def test_language_using_cookie(self):
self.client.cookies.load({settings.LANGUAGE_COOKIE_NAME : 'fr'})
response = self.client.get('/')
self.assertEqual(response.content,b'Bienvenue sur monsite.')
也可以通过在请求中包含 Accept-Language HTTP header:
def test_language_using_header(self):
response = self.client.get('/', HTTP_ACCEPT_LANGUAGE = 'fr')
self.assertEqual(response.content,b'Bienvenue sur monsite.')
How Django discovers language preference有更多详细的细节。
2)LocaleMiddleware未启用
此时可以使用translation.override()方法来设置语言:
from django.utils import translation
def test_language_using_override(self):
with translation.override('fr'):
response = self.client.get('/')
self.assertEqual(response.content,b'Bienvenue sur monsite.')
更多详情细节Explicitly setting the active language
使用django的client测试工具的一个简单单元测试实例
from django.test import Client
import unittest
class SimpleTest(unittest.TestCase):
'''测试之前的准备工作'''
def SetUp(self):
self.client = Client()
'''测试详情页面'''
def test_details(self):
#Make a GET request
response = self.client.get('/mysite/details/')
#check the status_code
self.assertEqual(response.status_code,200)
#check the rendered context contains 5 customers
self.assertEqual(len(response.context['customers']),5)