REST架构中,后端如何写接口及单元测试

后端举例,接口测试在实际应用中编写最容易,编写速度最快的就是整合测试。
1)整合测试测试接口逻辑与数据库ORM等多个功能,可以用到web开发框架(如Django、Flask等)自带的测试客户端和测试类(如Django的TestCase)。
2)不管是接口测试用例还是单元测试用例,其主要测试方向都是集中测试指定的接口或函数功能上”做什么”。如,对于REST型接口而言,主要测试的方面包括:传入有效和无效参数、接口返回的预期的状态码、返回与前端约定的数据格式,数据库数据异常情况下的接口数据处理等。

代码举例如下

import json
from django.test import TestCase

from lists.models import List, Item
from lists.forms import DUPLICATE_ITEM_ERROR, EMPTY_ITEM_ERROR


class ListAPITest(TestCase):
    base_url = "/api/lists/{}/"

    def test_get_returns_json_200(self):
        ''' 测试get接口返回了200的json '''
        list_ = List.objects.create()
        response = self.client.get(self.base_url.format(list_.id))
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response["content-type"], "application/json")

    def test_get_returns_items_for_correct_list(self):
        ''' 测试api返回指定清单的所有待办事项 '''
        other_list = List.objects.create()
        Item.objects.create(list=other_list, text="待办事项1")
        our_list = List.objects.create()
        item1 = Item.objects.create(list=our_list, text="待办事项1")
        item2 = Item.objects.create(list=our_list, text="待办事项2")
        response = self.client.get(self.base_url.format(our_list.id))
        self.assertEqual(
            json.loads(response.content.decode("utf8")),
            [
                {"id": item1.id, "text": item1.text},
                {"id": item2.id, "text": item2.text}
            ]
        )

    def test_POSTing_a_new_item(self):
        ''' 测试通过post请求创建一个新的待办事项 '''
        list_ = List.objects.create()
        response = self.client.post(
            self.base_url.format(list_.id),
            {"text": "新的待办事项"},
        )
        self.assertEqual(response.status_code, 201)
        new_item = list_.item_set.get()
        self.assertEqual(new_item.text, "新的待办事项")

    def post_empty_input(self):
        ''' post方法发送空数据 '''
        list_ = List.objects.create()
        return self.client.post(
            self.base_url.format(list_.id),
            data={"text": ""}
        )

    def test_for_invalid_input_nothing_saved_to_db(self):
        ''' 测试无效输入不会被保存 '''
        self.post_empty_input()
        self.assertEqual(Item.objects.count(), 0)

    def test_for_invalid_input_return_error_code(self):
        ''' 测试无效输入返回错误状态码 '''
        response = self.post_empty_input()
        self.assertEqual(response.status_code, 400)
        self.assertEqual(
            json.loads(response.content.decode("utf8")),
            {"error": EMPTY_ITEM_ERROR}
        )

    def test_duplicate_items_error(self):
        ''' 测试提交重复事项会返回错误码 '''
        list_ = List.objects.create()
        self.client.post(
            self.base_url.format(list_.id), data={"text": "thing"}
        )
        response = self.client.post(
            self.base_url.format(list_.id), data={"text": "thing"}
        )
        self.assertEqual(response.status_code, 400)
        self.assertEqual(
            json.loads(response.content.decode("utf8")),
            {"error": DUPLICATE_ITEM_ERROR}
        )

后端单元测试,纯粹的单元测试与整合测试不同,只测函数本身的功能。在实际应用中更多的出现在视图层。需要用到模拟技术库(如Python的unittest.mock模块),用模拟技术库模拟所有依赖。模拟技术一般分为三种,如spy(侦件)、fake(伪件)、stub(桩件)。单元测试一般难以编写,需要对框架足够了解,持续的编写练习,并进入”神赐的心流状态”才可能写出不错的单元测试。而且耗费时间较长,但采用TDD驱动开发的情况下,编写单元测试往往可以写出简洁优雅的代码、导向系统的更优设计、易于BUG定位、且有单元测试保证的情况下,方便代码重构。
单元测试的主要方面包括:针对ORM的数据模型层测试,针对视图层的函数行为测试。
数据模型层测试主要测试的是:对于数据库操作的辅助函数,功能上是否满足应用的功能需求。比如登录功能,ORM层就应该可以校验空数据(当然,前端也可以校验)、重复数据、删改查、以及关联关系,唯一性约束等。
举例代码如下:

class ItemModelTest(TestCase):

    def test_default_text(self):
        ''' 测试待办事项的默认字符串 '''
        item = Item()
        self.assertEqual(item.text, "")

    def test_item_is_related_to_list(self):
        ''' 测试待办事项和列表的相关性 '''
        list_ = List.objects.create()
        item = Item()
        item.list = list_
        item.save()
        self.assertIn(item, list_.item_set.all())

    def test_cannot_save_empty_list_items(self):
        ''' 测试数据库约束不能保存空的待办事项 '''
        list_ = List.objects.create()
        item = Item(list=list_, text="")
        with self.assertRaises(ValidationError):
            item.save()
            item.full_clean()

    def test_string_representation(self):
        ''' 测试待办事项的字符串呈现 '''
        item = Item(text="some text")
        self.assertEqual(str(item), "some text")

视图层测试主要测试的是:页面重定向,页面渲染、数据完整性及合法性校验、请求参数验证、错误提示返回、cookie创建等。
举例代码如下:

@patch("lists.views.NewListForm")
class NewListViewUnitTest(unittest.TestCase):
    ''' 新的列表视图测试类 '''

    def setUp(self):
        self.request = HttpRequest()
        self.request.POST["text"] = "新的待办事项"
        self.request.user = Mock()

    def test_passes_POST_data_to_NewListForm(self, mockNewListForm):
        ''' 测试列表视图传递POST数据到新列表表单 '''
        new_list(self.request)
        mockNewListForm.assert_called_once_with(data=self.request.POST)

    def test_saves_form_with_owner_if_form_valid(self, mockNewListForm):
        ''' 测试表单有效时对所有者的保存功能 '''
        mock_form = mockNewListForm.return_value
        mock_form.is_valid.return_value = True
        new_list(self.request)
        mock_form.save.assert_called_once_with(owner=self.request.user)

    @patch("lists.views.redirect")
    def test_redirects_to_form_returned_object_if_form_valid(
        self, mock_redirect, mockNewListForm
    ):
        ''' 测试表单有效时视图会重定向到一个显示刚刚提交的表单的页面 '''
        mock_form = mockNewListForm.return_value
        mock_form.is_valid.return_value = True

        response = new_list(self.request)

        self.assertEqual(response, mock_redirect.return_value)
        mock_redirect.assert_called_once_with(
            str(mock_form.save().get_absolute_url.return_value))

    @patch("lists.views.render")
    def test_renders_home_template_with_form_if_form_invalid(
        self, mock_render, mockNewListForm
    ):
        ''' 测试如果表单无效,那么返回主页模版 '''
        mock_form = mockNewListForm.return_value
        mock_form.is_valid.return_value = False

        response = new_list(self.request)

        self.assertEqual(response, mock_render.return_value)
        mock_render.assert_called_once_with(
            self.request, "home.html", {"form": mock_form}
        )

    def test_does_not_save_if_form_invalid(self, mockNewListForm):
        ''' 测试form内容无效时不应该保存 '''
        mock_form = mockNewListForm.return_value
        mock_form.is_valid.return_value = False
        new_list(self.request)
        self.assertFalse(mock_form.save.called)

猜你喜欢

转载自blog.csdn.net/weixin_41845533/article/details/81772841