单元测试——django单元测试

1、介绍

单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。

1.1、单元测试和接口测试的区别

  • 单元测试注重代码逻辑,接口测试注重业务逻辑

  • 单元测试的粒度最小,是测试最小独立的单元模块(不依赖其他模块);接口测试不是,会覆盖很多

  • 单元测试关注的是代码的实现和逻辑,测试范围较小,保证实现逻辑通过就行;接口测试因为关注业务,所以测试范围较广,会用更多的测试数据去测试

1.2、单元测试的优点

  • 消灭低级错误

    • 基本的单元测试,可以在系统测试之前,把大部分比较低级的错误都消灭掉,减少系统测试过程中的问题,这样也就减少了系统测试中定位和解决问题的时间成本。
  • 减少bug,快速定位bug

    • 某些类型的bug,靠系统测试是很难找到的。例如一些代码分支,平时99%的场景基本上都走不到,但一旦走到了,如果没有提前测试好,那么可能就是一个灾难。
  • 提高代码质量

    • 写单元测试的过程中,顺手把一些code重构了。
    • 一些长得非常像的代码,如果每次都要写一堆测试代码去测同样的code,这样很让人抓狂。于是设法把待测试的code改得尽量的精简,重复代码减少,这样覆盖率上去了,测试也好测了,代码也简洁了。

2、基于django的单元测试

2.1、环境准备

测试数据库编码设置,增加配置:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'meiduo_mall_db',
        'HOST': '127.0.0.1',
        'USER': 'root',
        'PASSWORD': 'mysql',
        'PORT': 3306,
        'TEST': {
            'CHARSET': 'utf8',
            'COLLATION': 'utf8_general_ci'
        },
    },
}
View Code

2.2、django单元测试

  • Django中有完善的单元测试,可以对开发的每一个功能进行单元测试,就可以测试功能是否正常。

  • Django 创建好项目后会在每个应用里自动创建一个tests.py 文件,可以直接在这个文件编写测试用例。

编写测试代码

  • 单元测试通过类继承的方式,继承自 django.test.TestCase
  • 在类中定义 test 开头的方法,可以定义多个,一个方法就是一个测试用例。
  • TestCase 中的 setUp 方法会在每一个测试用例执行之前被调用,tearDown 方法会在每一个测试用例执行之后被调用,这两个方法的名字是固定的。
# 导入 django.test.TestCase
from django.test import TestCase


class MyClassTestCase(TestCase):
    def setUp(self):
        """前置准备"""
        pass

    def tearDown(self):
        """后置处理"""
        pass

    def test_my_func(self):
        """测试功能"""
        pass

django单元测试运行命令

# 执行项目中全部的测试用例
# 由于 美多 项目的目录结构和 django 项目结构不一致,所以这里需要加上 "-t ." 参数
python manage.py test

# 运行某个名为users的app下面的所有的测试用例,users是应用的名字(APP_name),应用中所有以test开头的py文件都会执行
python manage.py test meiduo_mall.apps.users

# 运行某个app下面的tests.py文件
python manage.py test meiduo_mall.apps.users.tests 

# 运行某个app下面的tests.py文件中指定的class类
python manage.py test meiduo_mall.apps.users.tests.MyClassTestCase

# 执行ModeTest类下的某个测试方法
python manage.py test meiduo_mall.apps.users.tests.MyClassTestCase.test_my_func

注意

  • django 在测试过程中会建立一个用于测试的数据库

2.3、TestCase类

Django的TestCase继承了Python的unittest.TestCase, 所以这两个功能大部分一致(Python很多框架都集成了unittest)。

  • django.test.TestCase类主要由前、后置处理方法test开头的方法组成
    • test开头的方法 是编写了测试逻辑的用例
    • setUp方法 (名字固定)在每一个测试方法执行之前被调用
    • tearDown方法(名字固定) 在每一个测试方法执行之前被调用
    • setUpClass类方法(名字固定)在整个类运行前执行只执行一次
    • tearDownClass类方法(名字固定)在调用整个类测试方法后执行一次

前后置方法运行特点

在django示例项目的 meiduo_demo/users/中新建的test_code文件夹,在其中新建test_1_run.py文件,完成如下代码:

from django.test import TestCase


class MyClassTestCase(TestCase):
    # 在所有测试用例执行之前调用
    @classmethod
    def setUpClass(cls):
        print('setUpClass在所有测试用例执行之前调用')

    # 在所有测试用例执行之后调用
    @classmethod
    def tearDownClass(cls):
        print('tearDownClass在所有测试用例执行之后调用')

    # 在每一个测试方法执行之前被调用
    def setUp(self):
        print('setUp在每一个测试方法执行之前被调用')

    # 在每一个测试方法执行之后被调用
    def tearDown(self):
        print('tearDown在每一个测试方法执行之前被调用')

    # 测试方法以 test 开头
    def test_1(self):
        print('test 1')

    def test_2(self):
        print('test 2')
运行单元测试
python manage.py test meiduo_mall.apps.users.test_code

setUpClass 和 tearDownClass应用场景

  • 当类中的测试用例需要共享一些数据,比如,测试:获取用户信息、获取用户浏览器记录、获取用户地址列表时,需要先进行用户登录,测试完成之后进行用户退出。

  • 如果通过 setUp 和 teardown 方法,那么就需要多次进行登录、退出,这样是重复的代码,通常是不必要的。

  • 可以通过 setUpClass 和 tearDownClass 做类级别的前置处理(例如,用户登录)和后置处理(例如,退出登录),这两个方法每个类中只会执行一次。

# 定义 setUpClass: 用户登录
# 定义 tearDownClass: 用户退出
# 定义测试方法:获取用户信息、获取用户浏览器记录、获取用户地址列表

from django.test import TestCase
from requests import Session


class MyClassTestCase(TestCase):
    # 类属性
    session = None

    # 在所有测试用例执行之前调用
    @classmethod
    def setUpClass(cls):
        print('setUpClass')
        # 构造账号密码
        info = {
            "username": "mike123",
            "password": "chuanzhi12345",
            "remembered": True
        }

        # 实例化session对象,此为类属性
        cls.session = Session()

        # 登录
        resp = cls.session.post(
            'http://127.0.0.1:8000/login/',
            json=info
        )

        # 获取 json 响应
        result = resp.json()

        # 检查是否登录成功
        # 检查是否登录成功
        if result['code'] != 0:
            print('登录失败')

    # 在所有测试用例执行之后调用
    @classmethod
    def tearDownClass(cls):
        print('tearDownClass')
        # 登出
        cls.session.delete('http://127.0.0.1:8000/logout/')

    # 获取登陆用户
    def test_1_info(self):
        # 查看用户状态
        resp = self.session.get('http://127.0.0.1:8000/info/')
        print(resp.json())

    # 获取用户浏览历史
    def test_2_history(self):
        resp = self.session.get('http://127.0.0.1:8000/browse_histories/')
        print(resp.json())

    def test_3_addresses(self):
        # 调用查看地址列表接口
        url = 'http://127.0.0.1:8000/addresses/'
        resp = self.session.get(url)
        print(resp.json())

2.4、Client类

Client类似于requests,可以充当HTTP请求客户端。提供了get,post等方法用于模拟请求

Client和requests和区别:

  • client的实例对象能够实现状态保持,类似于requests的session对象
  • 使用client测试时,不需要启动服务,requests必须要启动服务才能测试
  • client只能在django的测试代码中使用,使用方式和requests中的session类似
  • client的get、post等请求方法,不需要携带”schema://domain”,只需要路径即可,例如”/login/”

常用方法

  • get(path, data={}, follow=False, **extra)

功能:发起 get 请求返回 response 对象。

参数说明:
  • path 请求的 url, 在 urlconf 中配置的 url
  • data 请求时的参数, 转换后为查询字符串
  • post(path, data={}, content_type=MULTIPART_CONTENT, follow=False, **extra)

功能:发起一个POST请求并返回response对象

参数说明:

  • path 请求的url, 在urlconf中配置的url
  • data 传递的参数
  • content_type 设置传递参数的类型, 默认是 multipart/form-data
    • 如果data传递json格式数据,content_type需要赋值为application/json

案例

from django.test import TestCase, Client
from django_redis import get_redis_connection


class MyClassTestCase(TestCase):
    def setUp(self):
        """前置准备"""
        print('setUp')
        self.client = Client()

    def tearDown(self):
        """后置处理"""
        print('tearDown')

    def test_api(self):
        self.do_register()  # 注册
        self.do_login()  # 登陆
        self.do_info()  # 查看用户信息
        self.do_logout()  # 登出
        self.do_info()  # 查看用户信息

    def do_register(self):
        # 注册
        # 1. 获取验证码
        _uuid = '0fdb75a8-d60e-4598-86bc-c3e7991aadf9'

        self.client.get(f'/image_codes/{_uuid}/') # 发请求

        # 2. 从redis中获取验证码
        conn = get_redis_connection('verify_code')
        ret = conn.get("img_%s" % _uuid)
        print(ret.decode())

        # 3. 发送短信验证码
        mobile = '18666667777'
        self.client.get(f'/sms_codes/{mobile}/?image_code={ret.decode()}&image_code_id={_uuid}')

        # 4. 注册
        user_info = {
            "username": "mike234",
            "password": "chuanzhi12345",
            "password2": "chuanzhi12345",
            "allow": True,
            "mobile": mobile,
            "sms_code": "123456",
        }

        resp = self.client.post('/register/', data=user_info, content_type='application/json')
        print(resp.json())

    def do_login(self):
        # 登陆
        user_info = {
            "username": "mike234",
            "password": "chuanzhi12345",
            "remembered": True,
        }

        resp = self.client.post('/login/', data=user_info, content_type='application/json')
        print(resp.json())

    def do_info(self):
        # 查询登陆用户信息
        resp = self.client.get('/info/')
        print(resp.json())

    def do_logout(self):
        # 登出
        self.client.delete('/logout/')
View Code

posted on 2020-10-18 20:21  yycnblog  阅读(1093)  评论(1)    收藏  举报

导航