系列文章介绍
本系列文章将详细介绍将Django官方引导教程中的投票项目改写为RESTful网络服务。Django官方教程地址https://docs.djangoproject.com/zh-hans/2.1/intro/tutorial01/
前置步骤
- Step-1:RESTful与Django
- Step-2:创建项目和应用
- Step-3:使用原生Django编写API
- Step-4:序列化与反序列化
- Step-5:基于DRF的视图类的视图
- Step-6:基于DRF的视图集的视图
- Step-7:用户接入控制
- Step-8:生成API文档
前置步骤文章见文末列表。
Step-9 测试
测试的重要性是不言而喻的。关于测试驱动开发,推荐一本不错的书:《Python测试驱动开发:使用Django、Selenium和JavaScript进行Web编程》,也是就传说中的山羊书。以前这本书还不叫这个名字,很多人都不知道这是一本Django主题的书。这本书详细讲解了如何用Django开发一个项目,如何从头开始就测试介入、测试驱动开发,如何使用Selenium,如何进行前端测试,甚至包含如何进行Git代码管理。现在Django主题的书已经汗牛充栋了,国人主写的Django书籍质量也非常高,而且更适合国内读者阅读。在这种大环境下这本书的阅读必要性也就没那么强了。
有人问我关于Django有什么书籍推荐,我认为,现在国人主笔的Django书籍都是经得起检验的,在书市上好好挑一本阅读,必有收获。如果要推荐一本外国人写的Django书,也许《轻量级Django》值得一读。这也是经起检验的一本不错的书,其中也介绍了Django REST开发相关内容。想了解这本书的同学可以私信向我借阅电子版(本来想用公众号自动发的,怕有引流嫌疑(⊙﹏⊙)b)。
DRF中的测试工具
本节中我们为API添加测试。 DRF为API测试提供了一些有用的类,使得测试更简单。本节使用如下几个测试相关的类:
- APIRequestFactory:类似于Django的RequestFactory。支持对视图发送各种请求,对比各种响应。
- APIClient:类似于Django的Client。支持对url发起GET和POST、测试响应。
- APITestCase:类似于Django的TestCase。各种测试类的父类。
创建测试请求
Django的RequestFactory可以创建一个请求实例,对视图函数发起请求。DjangoRESTFramework的APIRequestFactory类拓展了Django的RequestFactory;它支持几乎所有的HTTP动词。语法如下:
factory = APIRequestFactory()
request = factory.post(uri, post_data)
我们在polls/tests.py中创建第一个测试:
# in polls/tests.py
# from django.test import TestCase
# Create your tests here.
from rest_framework.test import APITestCase
from rest_framework.test import APIRequestFactory
from polls import apiview, apiviewsets
class TestPoll(APITestCase):
def setUp(self):
# 设置初始化的值
self.factory = APIRequestFactory()
self.view = apiviewsets.PollViewSet.as_view({'get': 'list'})
self.uri = '/polls/'
def test_list(self):
request = self.factory.get(self.uri)
response = self.view(request)
self.assertEqual(
response.status_code,
200,
'预期200状态的响应,但响应码为{}.'.format(response.status_code)
)
运行测试:
$ python manage.py test
可以看到我们的测试失败了。这是因我访问这个视图需要认证,而测试没有认证。 我们来编写一个带认证的测试:
# in polls/tests.py
# from django.test import TestCase
# Create your tests here.
from django.contrib.auth import get_user_model
from rest_framework.authtoken.models import Token
from rest_framework.test import APITestCase
from rest_framework.test import APIRequestFactory
from polls import apiview, apiviewsets
class TestPoll(APITestCase):
def setUp(self):
self.factory = APIRequestFactory()
self.view = apiviewsets.PollViewSet.as_view({'get': 'list'})
self.uri = '/polls/'
self.user = self.setup_user()
self.token = Token.objects.create(user=self.user)
self.token.save()
@staticmethod
def setup_user():
User = get_user_model()
return User.objects.create_user(
'test',
email='testuser@test.com',
password='823w74ytrh3948gh!'
)
def test_list(self):
request = self.factory.get(
self.uri,
HTTP_AUTHORIZATION='Token {}'.format(self.token.key)
)
request.user = self.user
response = self.view(request)
self.assertEqual(
response.status_code,
200,
'预期200状态的响应,但响应码为{}.'.format(response.status_code)
)
再来运行测试,结果如图:
使用APIClient
使用APIClient不需要先创建request,可以直接使用GET和POST。
# in polls/test.py
# from django.test import TestCase
# Create your tests here.
from django.contrib.auth import get_user_model
from rest_framework.authtoken.models import Token
from rest_framework.test import APITestCase
from rest_framework.test import APIRequestFactory
from rest_framework.test import APIClient
from polls import apiview, apiviewsets
class TestPoll(APITestCase):
def setUp(self):
self.factory = APIRequestFactory()
self.client = APIClient()
self.view = apiviewsets.PollViewSet.as_view({'get': 'list'})
self.uri = '/polls/'
self.user = self.setup_user()
self.token = Token.objects.create(user=self.user)
self.token.save()
@staticmethod
def setup_user():
User = get_user_model()
return User.objects.create_user(
'test',
email='testuser@test.com',
password='823w74ytrh3948gh!'
)
def test_list(self):
request = self.factory.get(
self.uri,
HTTP_AUTHORIZATION='Token {}'.format(self.token.key)
)
request.user = self.user
response = self.view(request)
self.assertEqual(
response.status_code,
200,
'预期200状态的响应,但响应码为{}.'.format(response.status_code)
)
def test_list2(self):
response = self.client.get('/api-polls/polls/')
self.assertEqual(
response.status_code,
200,
'预期200状态的响应,但响应码为{}.'.format(response.status_code)
)
如果现在运行测试,会因为没有认证而不能通过。我们可以使用APIClient.login进行认证。
# in polls/tests.py
class TestPoll(APITestCase):
... ...
def test_list2(self):
# self.client.login(username='test', password='823w74ytrh3948gh!')
self.client.force_authenticate(user=self.user, token=self.token)
response = self.client.get('/api-polls/polls/') # 注意这里使用的不是self.uri
self.assertEqual(
response.status_code,
200,
'预期200状态的响应,但响应码为{}.'.format(response.status_code)
)
APIClient提供了
- client.login(username='lauren', password='secret'),使用账号密码
- client.credentials(HTTP_AUTHORIZATION='Token ' + token.key),使用token
- client.force_authenticate(user=user),强制认证
三种方式进行认证,即使用账号密码,使用token,使用强制认证等。此处使用的是强制认证。 值得注意的是,APIClient模拟的是客户端,所有self.client.get(uri)中的uri,要使用不含主机名的完整链接。
官方文档见:Testing - Django REST framework 运行测试,查看结果:
POST测试
之前都是进行GET测试,现在开始进行POST测试。
class TestPoll(APITestCase):
... ...
def test_create(self):
self.client.login(username='test', password='823w74ytrh3948gh!') # 这一次,我们使用了用户名、密码验证
params = {
'question': '如何看待第一届杨超越杯编程大赛?',
'created_by': self.user.id
}
response = self.client.post('/api-polls' + self.uri, params)
self.assertEqual(
response.status_code,
201,
'预期201状态的响应,但响应码为{}.'.format(response.status_code)
) # 注意post成功的状态码不是200,而是201
运行测试,不出意外的话测试成功。
Step-last:后记
系列文章风格
系列文章会以低零基础、手把手、逐行解释、连续完整的风格进行写作。
- 低零基础:降低文章阅读门槛,使接触Python Web开发时间较短的读者也能有所收获。本人本职是从事数据开发与数据挖掘,所以对低零基础深有体会。
- 手把手:一些基础操作,也会说明。如本文中,包括安装库等操作也会进行说明。
- 逐行解释:对代码进行解释,以白居易写诗风格为目标(传说白居易会把自己的诗解释给街头妇人,直到连不懂文化的妇人也能明白,完成创作)。
- 连续完整:连续是指,文章是成系列的,上文下文之间是有着联系的,项目是连续的。代码托管也体现了这一点,不同的文章,对应不同的git标签或分支,也体现了不同的进度。完整是指,项目是完整的,文章也是完整的。文章可以当作博文来读,也可以当作教程来读。
文章列表
- Vue+Django构建前后端分离项目(选读): https://zhuanlan.zhihu.com/p/54776124
- Python构建RESTful网络服务[Django篇:基于函数视图的API]:https://zhuanlan.zhihu.com/p/55562891
- Python构建RESTful网络服务[Django篇:使用PostgreSQL替代SQLite](选读):https://zhuanlan.zhihu.com/p/55903530
- Python构建RESTful网络服务[Django篇:基于类视图的API]:https://zhuanlan.zhihu.com/p/57024322
- Python构建RESTful网络服务[Django篇:基于视图集的API]:https://zhuanlan.zhihu.com/p/57791697
- Python构建RESTful网络服务[Django篇:用户接入控制,认证与权限]:https://zhuanlan.zhihu.com/p/58426061
参考文献
Hillar G C. Building RESTful Python Web Services[J]. Birmingham, UK: Packt Publishing Ltd, 2016.
重构Djagno官方教程为RESTful网络应用.