使用Django构建REST API - 测试驱动的方法:第1部分

2017-08-31

使用Django构建REST API - 测试驱动的方法:第1部分

Code without tests is broken as designed. — Jacob Kaplan-Moss

在软件开发中,测试至关重要。那我为什么要这样做呢?

  • 测试有一个很短的反馈循环,使您和您的团队学习更快,并进行调整
  • 调试时间缩短,让您花更多的时间编写代码
  • 测试作为您的代码的文档!
  • 它们在减少错误的同时提高代码质量
  • 重构代码后,您的测试将告诉您更改是否已经破坏了以前的工作代码,等等
  • 测试可以防止你脱发!

执行代码测试的最好方法是使用测试驱动开发(TDD)。

这是它的工作原理:

  • 写一个测试 - 测试将在您的应用程序中强化一些功能
  • 然后,运行测试 - 测试应该失败,因为没有代码让它通过。
  • 写代码 - 让测试通过
  • 运行测试 - 如果通过,您确信您编写的代码符合测试要求
  • 重构代码 - 删除重复,修剪大对象,使代码更易读。每次重构代码时重新运行测试
  • 重复 - 就是这样!

作为最佳实践的粉丝,我们将使用TDD创建一个bucketlist API。API将具有CRUD(创建,读取,更新,删除)和身份验证功能。让我们来吧!


Bucketlist

本文的目的是帮助您在创建新事物时学习真棒的东西。我们将创建一个桶列表API。如果您没有听说“ 清单 ”一词,那么您将要实现的所有目标列表,想要实现的梦想,以及您在死亡之前想要体验的生活体验(或者打桶)。因此,这个API应该可以帮助我们创建和管理我们的分类列表。

要在同一页面上,API应该具有以下能力:

  • 创建一个Bucketlist
  • 检索一个Bucketlist
  • 更新它
  • 而且,删除一个Bucketlist

其他后续的补充功能将是:

  • API用户认证
  • 搜索Bucketlists
  • 将bucketlist items添加到bucketlist中
  • 分页

假如你是 Python 或者 Django 的初学者。 请查看构建您的第一个django应用程序。这是初学者的优秀资源。

现在我们知道一个桶单,这里有一些关于我们用来创建它的应用程序的工具。


Django Rest Framework

Django Rest Framework

Django Rest Framework(或简称为DRF)是构建Web API的强大模块。构建具有身份验证策略并可浏览的模型支持的API非常简单。

为什么使用DRF?

  • 认证 - 从基本和基于会话的认证到基于令牌和Oauth2功能,DRF是王者。
  • 序列化 - 它支持ORM和非ORM数据源,并且与您的数据库无缝集成。
  • 丰富的文档 - 如果你被困在某个地方,你可以参考它的庞大的在线文档和伟大的社区支持
  • Heroku,Mozilla,Red Hat和Eventbrite是在其API中使用DRF的顶级公司之一。

要求

要使DRF工作,您必须具备:

  • Python
  • Django

创建并cd进入您的项目文件夹。您可以将文件夹称为任何您喜欢的内容。

$ mkdir projects && $_

然后,创建一个虚拟环境,将我们的代码与系统的其余部分隔离开来。

$ virtualenv -p /usr/local/bin/python3 venv

-p开关告诉virtualenv要使用的python版本的路径。确保在-p开关后放置正确的python安装路径。venv是你的环境 即使你可以为自己的环境命名任何东西,它被认为是最好的做法,简单的命名它venvenv

通过这样做激活你的虚拟环境:

$ source venv/bin/activate

您将看到带有环境名称的提示符。即**(venv)**。这意味着环境现在是活跃的。现在我们已经准备好在我们的环境中安装我们的需求。

在project文件夹中,使用pip安装Django

$ pip install Django 

如果您的系统中没有安装 pip,可以先执行以下操作:

$ sudo easy_install pip

由于,需要追踪 python 包的安装情况,我们将创建一个 requirements.txt 文件

$ touch requirements.txt

通过使用 pip freeze 把需要安装的 python 包写到 requirements.txt 文件中

$ pip freeze > requirements.txt

最后,创建一个django项目。

$ django-admin startproject djangorest

我们现在应该有一个名称djangorest创建的文件夹。随意给任何其他名字。文件夹结构应如下所示:

djangorest
├─djangorest
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── manage.py

集成DRF

使用pip,安装DRF

$ pip install djangorestframework

对于我们的应用程序使用DRF,我们必须添加rest_framework到我们的settings.py。让我们走在前面,做到这一点。

# /djangorest/djangorest/settings.py
...

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles', # Ensure a comma ends this line
    'rest_framework', # Add this line
]

创建Rest API应用程序

在Django中,我们可以创建多个应用集成以形成一个应用程序。django中的一个应用程序只是一个包含init.py文件的一堆文件的python包。

首先cd进入终端上的djangorest目录。我们这样做,以便我们可以访问该manage.py文件。然后创建应用程序如下:

$ python3 manage.py startapp api

startapp命令创建一个新的应用程序。我们的应用程序被称为api。它将持有我们的API逻辑。到目前为止,你应该有一个命名的文件夹api旁边的djangorest应用程序。

要将我们的api应用程序与djangorest主应用程序集成,我们必须将其添加到我们的djangorest中settings.py。让我们走在前面,做到这一点。

...

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'api', # Add this line
]

开始 Coding

首先,写测试!

我们首先要创建模型。但是我们没有写出测试。因此,我们将在我们的api应用程序的tests.py文件夹中编写一些测试。

# /api/tests.py

from django.test import TestCase
from .models import Bucketlist

class ModelTestCase(TestCase):
    """This class defines the test suite for the bucketlist model."""

    def setUp(self):
        """Define the test client and other test variables."""
        self.bucketlist_name = "Write world class code"
        self.bucketlist = Bucketlist(name=self.bucketlist_name)

    def test_model_can_create_a_bucketlist(self):
        """Test the bucketlist model can create a bucketlist."""
        old_count = Bucketlist.objects.count()
        self.bucketlist.save()
        new_count = Bucketlist.objects.count()
        self.assertNotEqual(old_count, new_count)

上面的代码从django.test导入测试用例。测试用例有一个测试,用于测试该模型是否可以创建一个名称的桶单。

然后,我们定义我们的模型

我们需要创建一个空白模型类。这是在我们的models.py

# /api/models.py

from django.db import models

class Bucketlist(models.Model):
    pass

使用 django 运行测试非常简单,我们将使用test命令:

$ python3 manage.py test

你应该在屏幕上看到一堆错误。别担心 这是因为我们还没有编写我们的模型字段并更新了我们的数据库。Django使用SQlite作为默认数据库,因此现在我们将使用它。此外,在创建模型时,我们不必编写单个SQL语句。Django为我们处理所有这些。在models.py我们定义将代表我们的数据库表字段的字段。

# api/models.py

from django.db import models

class Bucketlist(models.Model):
    """This class represents the bucketlist model."""
    name = models.CharField(max_length=255, blank=False, unique=True)
    date_created = models.DateTimeField(auto_now_add=True)
    date_modified = models.DateTimeField(auto_now=True)

    def __str__(self):
        """Return a human readable representation of the model instance."""
        return "{}".format(self.name)

运行 migrate!

Migrations 是Django将您对模型所做的更改(如添加字段,删除模型等)的方式传播到数据库模式中。现在我们有一个丰富的模型,我们需要告诉数据库创建相关的模式。在你的控制台中运行:

$ python3 manage.py makemigrations

这将根据我们对我们的模型所做的更改创建一个新的migration。然后,通过执行以下操作将迁移应用到您的数据库:

$ python3 manage.py migrate

运行测试时,您应该看到如下:

django test

测试通过了!这意味着我们可以继续为我们的应用编写序列化程序


Serializers

Serializers 能够序列化和反序列化数据。因此,你会问序列化是什么?序列化是将数据从数据库中的复杂查询数据更改为可以理解的数据形式,如JSON或XML。在验证我们要保存到数据库的数据后,反序列化将恢复此过程。

ModelSerializer 很棒!

ModelSerializer类可以自动创建一个序列化器类与对应型号字段的字段。这大大减少了我们的代码行。在api目录中创建一个名为serializers.py文件。让我们写一些代码:

# api/serializers.py

from rest_framework import serializers
from .models import Bucketlist

class BucketlistSerializer(serializers.ModelSerializer):
    """Serializer to map the Model instance into JSON format."""

    class Meta:
        """Meta class to map serializer's fields with the model fields."""
        model = Bucketlist
        fields = ('id', 'name', 'date_created', 'date_modified')
        read_only_fields = ('date_created', 'date_modified')

Views

我们先写视图的测试。起初测试似乎很难过。但是,当你知道要执行什么时,很容易知道要测试什么。在我们的情况下,我们想要创建将处理以下内容的视图:

  • 创建一个桶单 - 处理POST请求
  • 读取桶单 - 处理GET请求
  • 更新一个bucketlist - 处理PUT请求
  • 删除桶列表 - 处理DELETE请求

基于上述功能,我们知道要测试什么。我们会用它们作为指导。我们先来看一下。如果我们想测试API是否会成功创建一个存储区列表,我们将在以下代码中编写以下代码tests.py

# api/tests.py

# Add these imports at the top
from rest_framework.test import APIClient
from rest_framework import status
from django.core.urlresolvers import reverse

# Define this after the ModelTestCase
class ViewTestCase(TestCase):
    """Test suite for the api views."""

    def setUp(self):
        """Define the test client and other test variables."""
        self.client = APIClient()
        self.bucketlist_data = {'name': 'Go to Ibiza'}
        self.response = self.client.post(
            reverse('create'),
            self.bucketlist_data,
            format="json")

    def test_api_can_create_a_bucketlist(self):
        """Test the api has bucket creation capability."""
        self.assertEqual(self.response.status_code, status.HTTP_201_CREATED)

我们运行这个测试时会失败。这样做是因为我们尚未实现用于处理POST请求的视图和URL。

让我们继续吧!打开views.py,写下面的代码:

# api/views.py

from rest_framework import generics
from .serializers import BucketlistSerializer
from .models import Bucketlist

class CreateView(generics.ListCreateAPIView):
    """This class defines the create behavior of our rest api."""
    queryset = Bucketlist.objects.all()
    serializer_class = BucketlistSerializer

    def perform_create(self, serializer):
        """Save the post data when creating a new bucketlist."""
        serializer.save()

ListCreateAPIView是一个通用视图,它提供GET(列出所有)和POST方法处理程序

注意:

我们指定queryset和serializer_class属性。我们还声明一种perform_create方法,一旦发布就有助于保存新的桶列表。


处理 URLS

为了完成,我们将指定URL作为消耗我们的API的端点。将URL视为外界的界面。如果有人想要与我们的网络API进行交互,那么他们必须使用我们的URL。

在api目录下创建一个urls.py文件。这是我们定义我们的网址模式的地方。

# api/urls.py

from django.conf.urls import url, include
from rest_framework.urlpatterns import format_suffix_patterns
from .views import CreateView

urlpatterns = {
    url(r'^bucketlists/$', CreateView.as_view(), name="create"),
}

urlpatterns = format_suffix_patterns(urlpatterns)

format_suffix_pattern允许时,我们使用的网址我们指定的数据格式(原始JSON甚至HTML)。它将格式用于模式中的每个URL。

最后,我们向主应用程序的urls.py文件添加一个URL,以便它指向我们的API应用程序。我们必须将api.urls我们刚才声明的内容包括在主应用程序中urlpatterns。转到djangorest文件夹并将以下内容添加到urls.py

# djangorest/urls.py
# This is the main urls.py. It should'nt be mistaken for the urls.py in the api directory

from django.conf.urls import url, include

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^', include('api.urls')) # Add this line
]

让我们运行起来吧!

我们将使用runserver命令运行我们的服务器django方式:

$ python3 manage.py runserver

你应该在你的控制台上看到这个输出

django running

这意味着一切顺利运行。

在浏览器中输入服务器指定的URL(http://127.0.0.1:8000/bucketlists)。

DRF

继续, 写一个bucketlist并点击post按钮来确认我们的API是否有效。你应该看到这样的东西:

DRF POST


读取,更新和删除

写测试

我们将写三篇更多的测试,以满足GETPUTDELETE请求。我们将包装如下:

# api/tests.py

    def test_api_can_get_a_bucketlist(self):
        """Test the api can get a given bucketlist."""
        bucketlist = Bucketlist.objects.get()
        response = self.client.get(
            reverse('details'),
            kwargs={'pk': bucketlist.id}, format="json")

        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertContains(response, bucketlist)

    def test_api_can_update_bucketlist(self):
        """Test the api can update a given bucketlist."""
        change_bucketlist = {'name': 'Something new'}
        res = self.client.put(
            reverse('details', kwargs={'pk': bucketlist.id}),
            change_bucketlist, format='json'
        )
        self.assertEqual(res.status_code, status.HTTP_200_OK)

    def test_api_can_delete_bucketlist(self):
        """Test the api can delete a bucketlist."""
        bucketlist = Bucketlist.objects.get()
        response = self.client.delete(
            reverse('details', kwargs={'pk': bucketlist.id}),
            format='json',
            follow=True)

        self.assertEquals(response.status_code, status.HTTP_204_NO_CONTENT)

如果我们运行这些测试,他们应该失败。我们来解决这个问题。现在是用一个PUT和DELETE方法处理程序完成api的时候了。我们将为此定义一个视图类。在该views.py文件中,添加以下代码:

# api/views.py

class DetailsView(generics.RetrieveUpdateDestroyAPIView):
    """This class handles the http GET, PUT and DELETE requests."""

    queryset = Bucketlist.objects.all()
    serializer_class = BucketlistSerializer

RetrieveUpdateDestroyAPIView是提供一个通用的视图GET(之一), PUT,PATCH和DELETE方法处理程序。

最后,我们创建这个新的URL与我们的DetailsView相关联。

# api/urls.py

from .views import DetailsView 

url(r'^bucketlists/(?P<pk>[0-9]+)/$',
        DetailsView.as_view(), name="details"),

结束工作

在浏览器中输入指定的URL(http://127.0.0.1:8000/bucketlists/1/)。您现在可以编辑现有的桶单。

DRF Detail view


结论

恭喜这篇文章结束了 - 你真棒!

在本系列的第2部分中,我们将深入添加用户,集成授权和身份验证,记录API并添加更精细的测试。想深入挖掘?随时阅读DRF的官方文档

如果你刚刚到Django,会发现Django for Beginners Excellent。

https://scotch.io/tutorials/build-a-rest-api-with-django-a-test-driven-approach-part-1