• Django 单元测试


    单元测试框架unittest

    unittest是python自带的一个单元测试框架,无需安装,使用简单方便。

    unittest中最核心的部分是:TestFixture、TestCase、TestSuite、TestRunner

    unittest case的运行流程:

    1. 写好一个完整的TestCase
    2. 多个TestCase 由TestLoder被加载到TestSuite里面, TestSuite也可以嵌套TestSuite
    3. 由TextTestRunner来执行TestSuite,测试的结果保存在TextTestResult中
    4. TestFixture指的是环境准备和恢复

    TestFixture

    用于测试环境的准备和恢复还原, 一般用到下面几个函数:

    1. setUp():每个测试case运行之前运行
    2. tearDown():每个测试case运行完之后执行
    3. setUpClass():必须使用@classmethod 装饰器, 所有case运行之前只运行一次
    4. tearDownClass():必须使用@classmethod装饰器, 所有case运行完之后只运行一次

    TestCase

    • 执行结果:成功是 .,失败是 F,出错是 E,跳过是 S
    • 测试用例的执行跟方法的顺序没有关系, 默认按字母顺序
    • 每个测试方法均以 test 开头

    测试model文件:

    # -*- coding: utf-8 -*-
    import unittest
    
    
    class TestMy(unittest.TestCase):
        def setUp(self):
            # 每个测试用例运行之前执行
            print 'setUp'
    
        def tearDown(self):
            # 每个测试用例运行之后执行
            print 'tearDown'
    
        @classmethod
        def setUpClass(cls):
            # 所有测试用例运行之前执行
            print 'setUpClass'
    
        @classmethod
        def tearDownClass(cls):
            # 所有测试用例运行之后执行
            print 'tearDownClass'
    
        def run_test(self):
            # 不是以test开头,不是测试用例
            print 'run'
    
        def test_run_a(self):
            # 测试用例
            self.assertEqual(1, 1)
    
        def test_run_b(self):
            # 测试用例
            self.assertEqual(1, 2)
    
    

    在单元测试运行完之后,成功会打印一个".",失败会显示断言失败的地方。

    Django单元测试

    django的单元测试模块是基于unittest编写。我们使用的类名为django.test.TestCase,继承于python的unittest.TestCase。所以它的整个流程跟unittest是完全一致的。

    • 不同点:在Django.test模块中定义了Client类用于在测试工程中模拟多种请求的发送。
    from django.test import TestCase
    import json
    
    class TestAlarm(TestCase):
    
          def test_nic_type(self):
            # 测试获取NIC类型接口
            r = self.client.get('/api/home/nic_type/')
            self.assertEqual(r.status_code, 200)
    
            content = json.loads(r.content)
            self.assertEqual(content['result'], True)
    
    
    

    Django支持的请求类型

    请求类型 调用方法
    Post请求 post(path, data=None, content_type=MULTIPART_CONTENT, follow=False, secure=False, **extra)
    Get请求 get(path, data=None, follow=False, secure=False, **extra)
    Put请求 put(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra)
    Patch请求 patch(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra)
    Delete请求 delete(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra)

    常用参数含义:

    参数名 含义解释
    path 发送请求使用url
    data 发送请求时携带的数据
    content_type 携带数据的格式
    secure 客户端将模拟HTTPS请求
    follow 客户端将遵循任何重定向

    其中:post、put、patch、delete请求方法中content_type为application/json时,如果data为dict,list或tuple类型,则需要使用json.dumps()进行序列化。

    请求数据的接收与处理

    通过Client对象发出的请求在经过被测试单元的一系列操作之后,返回的数据会以返回值的形式返回。一般情况下,后端返回的数据形式为json格式,在接收数据后需要调用json方法进行解析。

    import json
    
     def test_nic_type(self):
           # 测试获取NIC类型接口
           response = self.client.get('/api/home/nic_type/')
           self.assertEqual(response.status_code, 200)
           content = json.loads(response.content)
    
    

    其中,response的常用属性有:

    • status_code:http状态码
    • content: reponse的body,json格式,用json方式进行解析

    结果验证

    • self.assertEqual(1, 2): 断言1==2
    • self.assertTrue(isinstance(content, list)): 断言content的类型是否是list
    • self.assertContains(response, text, count=None, status_code=200, msg_prefix='', html=False): 断言response是否与status_code和text内容相应。将html设为True会将text作为html处理。
    • self.assertJSONEqual(raw, expected_data, msg=None):断言Json片段raw和expected_data是否相当。

    mock数据

    mock 是辅助单元测试的模块,用于测试不方便调用的别人的接口。

    使用:

    在python2.x中,mock是一个独立的模块,使用时需要先安装

    pip install mock
    

    在python3.3之后,mock已经合并到unittest模块中了,是unittest单元测试的一部分,直接导入过来就行

    from unittest import mock
    

    使用举例

    需要测试的方法:

        def get_os_account(cls, bk_biz_id):
            params = {
                'bk_biz_id': bk_biz_id
            }
            accounts = JobApi.get_os_account(params=params)
            return accounts
    

    其中JobApi.get_os_account为外部接口,单元测试时无法调用,所以需要使用mock数据,使用方法如下:

    import mock
    
    def test_get_os_account(self):
        """
        测试方法get_os_account
        :return:
        """
        JobApi.get_os_account = mock.MagicMock(return_value=Mock_Os)
        data = Rules.get_os_account(
            bk_biz_id=2
        )
        self.assertTrue(isinstance(data, dict))
    

    其中Mock_Os为:

    Mock_Os = {
        "result": True,
        "code": 0,
        "message": "success",
        "data": [
            {
                "id": 2,
                "account": "Administrator",
                "creator": "system",
                "os": "Windows",
                "alias": "Administrator",
                "bk_biz_id": 2,
                "create_time": "2018-03-22 15:36:31"
            }
        ]
    }
    

    数据库

    测试是需要数据库的,django会为测试单独生成数据库。不管你的测试是否通过,当你所有的测试都执行过后,这个测试数据库就会被销毁。

    • 注意要点: 在执行单元测试的过程中,会Django会生成临时的数据库, 因此,连接数据库的用户需要有创建和删除数据库的权限。
    • 默认情况下,测试数据库的名字是test_DATABASE_NAME,DATABASE_NAME是你在settings.py里配置的数据库名;
    • 如果你需要给测试数据库一个其他的名字,可以在settings.py中指定TEST_DATABASE_NAME的值

    配置测试数据库,在setting.py文件DATABASES中增加TEST字段

    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',  # 默认用mysql
            'NAME': 'hn_collector',                        # 数据库名 (默认与APP_ID相同)
            'USER': 'root',                        # 你的数据库user
            'PASSWORD': '123456',                    # 你的数据库password
            'HOST': '127.0.0.1',                   # 开发的时候,使用localhost
            'PORT': '3306',                        # 默认3306
            'TEST': {
                'NAME': 'data_collector_test',
                'CHARSET': 'utf8',
                'COLLATION': 'utf8_general_ci'
            },
            'OPTIONS': {'charset': 'utf8'},
        },
    }
    

    推荐使用配置数据库,这个可以修改测试数据库的相关配置。否则会比较容易出问题:例如编码问题:中文无法入库等

    keepdb

    在编写单元测试时,每次重新创建MySQL数据库表都花费很长时间,于是在表结果不变的情况下,想使用keepdb参数

    python manage.py test --keepdb
    
    

    测试数据:fixtures

    有一些项目是基于数据库来开发的,如果数据库里没有数据,那么对于一个基于数据库的网站来说,test case并无多大的用处.为了给测试数据库加入测试数据更方便,django提供了载入fixtures的方法.

    fixture是一系列的数据集合,django知道如何将它导入数据库。

    使用步骤
    • 在myapp下创建fixtures文件夹,在文件夹下创建myapp.json文件
    • 在json文件中配置
    [{
        "fields": {
            "subnets": "1",
            "update_time": "2020-03-13T16:48:15",
            "name": "test",
            "ch_operator": null,
            "scan_time": "2020-03-13T18:31:30",
            "edit_user": null,
            "is_delete": 0,
            "create_user": "wangying",
            "create_time": "2020-03-13T16:48:15"
        },
        "model": "tasks.tasksconfig",
        "pk": 3
    }, {
        "fields": {
            "status": 1,
            "edit_time": "2020-03-13T16:48:15",
            "is_delete": 0,
            "create_user": "wangying",
            "create_time": "2020-03-13T16:48:15",
            "frequency": 60,
            "time": "02:00:00",
            "day": 5,
            "tasksconfig": 3
        },
        "model": "tasks.cycle",
        "pk": 3
    }]
    
    
    • 如果实际数据库中有数据,可以将数据直接拷贝到myapp.json文件中,拷贝命令如下
    python manage.py dumpdata myapp >myapp/fixtures/myapp.json
    
    • setting.py中加入:FIXTURE_DIRS = ('/path/to/api/fixtures/',)
    • 在测试文件中使用:
    class TestHome(BaseTestCase):
        fixtures = ['ip_address.json']
    
        def setUp(self):
            super(TestHome, self).setUp()
    

    登陆验证

    直接将登陆验证mock掉,可以绕过中间件鉴权(不推荐)

    @patch('account.middlewares.LoginMiddleware',return_value={'user': 'wy'})
        def test_all_sub(self, user):
    

    直接cookie赋值

        def setUp(self):
            self.client.cookies['bk_token'] = 'bk_token'
    

    Celery 单元测试

    异步任务单元测试

    简单例子:

    @task()
    def add():
        print '33333'
    
        def task_celery(self, request):
            type = request.GET.get('type')
            if int(type) == 1:
                add.delay()
                data = {
                    'status': 1
                }
            else:
                data = {
                   'status': 2
                }
            return Response(data)
    

    单元测试方法:使用CELERY_ALWAYS_EAGER=True

    • 装饰器override_settings
    from django.test.utils import override_settings
    
        @override_settings(CELERY_ALWAYS_EAGER=True)
        def test_add(self):
            param = {
                'type': 1
            }
            r = self.client.get('/api/task/task_celery/', param, content_type='application/json')
            self.assertEqual(r.status_code, 200)
    
            content = json.loads(r.content)
            self.assertEqual(content['result'], True)
    
    • 直接修改settings
    from django.conf import settings
    
        def setUp(self):
            settings.CELERY_ALWAYS_EAGER = True
    

    如果设置CELERY_ALWAYS_EAGER=True,则下面两行代码没有区别

    add.delay()
    add()
    

    文件上传:

    只需提供文件字段名称作为键,并提供要上传的文件的文件句柄作为值。

        def test_upload_file(self):
            """
            测试方法 upload_file
            :return:
            """
            with open('apps/test_handlers/mock_data/upload_file/ydauto_tomcat.zip') as fb:
                r = self.client.post('/api/scripts/', data={'file': fb})
                content = json.loads(r.content)
                self.assertEqual(r.status_code, 200)
    

    file只是一个键值,可以自己定义

    运行单元测试

    • 测试整个工程python manage.py test
    • 测试某个应用python manage.py test app_name
    • 只测试一个Casepython manage.py test MyTestCase
    • 只测试一个方法python manage.py test test_func

    测试通过:

  • 相关阅读:
    【转】Android listview与adapter用法
    【转】 Android Fragment 真正的完全解析(下)
    Jupyter Notebook 基本使用
    斯坦福CS231n学习--初识
    MatConvNet 练习使用CNN
    数据库系统学习(四)- 关系模型之关系代数
    操作系统学习(一)--概述启动过程
    数据库系统学习(三)- 关系模型之基本概念
    数据库系统学习(二)- 基础模型
    数据库系统学习(一)-入门篇
  • 原文地址:https://www.cnblogs.com/wangyingblock/p/12478512.html
Copyright © 2020-2023  润新知