《Web接口开发与自动化测试基于Python语言》--第6章

《Web接口开发与自动化测试基于Python语言》–读书笔记

第6章 Django测试

这章来到本书的正题了。

Web应用的难点在于: HTTP层面的请求处理、表单验证和处理、模板渲染;

Django框架的测试模块解决的问题: 模拟请求、插入测试数据、检查应用输出。


6.1 unittest单元测试框架

6.1.1 单元测试框架

误区:

不用单元测试框架一样可以编写单元测试,单元测试本质上就是通过一段代码去测试另一段代码;

单元测试框架不仅可以用于程序单元级别的测试,同样可以用于UI自动化测试、接口自动化测试,以及移动APP自动化测试。

单元测试框架:

提供用例编写规范与执行: 单元测试框架提供了统一的用例编写规范,灵活指定不同级别的测试,如针对一个测试方法、一个测试类、一个测试文件,或者一个测试目录等不同级别的测试。

提供专业的比较方法: 测试用例最关键的步骤,实际测试结果与预期结果的比较,单元测试将这个比较过程命名为“断言”,单元测试框架提供了丰富的断言方法,eg:相等/不相等,包含/不包含,True/False等。

提供丰富的测试日志: 单元测试框架提供了丰富的执行日志,当测试用例执行失败的时候会抛出明确的失败信息,测试完成后提供结果信息,失败用例数、成功用例数、执行时间等。

单元测试框架可帮助我们完成不同级别测试的自动化:

  • 单元测试:unittest

  • HTTP接口自动化测试:unittest+Requests

  • Web UI自动化测试:unittest+Selenium

  • 移动自动化测试:unittest+Appium

6.1.2 编写单元测试用例

简单示例:

对两个整数的简单计算module.py:

#! /usr/bin python
# -*- coding:utf-8 -*-

class Calculator():
    """实现两个数的加、减、乘、除"""

    def __init__(self, a, b):
        self.a = int(a)
        self.b = int(b)

    # 加法
    def add(self):
        return self.a + self.b

    # 减法
    def sub(self):
        return self.a - self.b

    # 乘法
    def mul(self):
        return self.a * self.b

    # 除法
    def div(self):
        return self.a / self.b

编写对应的测试文件test.py:

#! /usr/bin python
# -*- coding:utf-8 -*-

import unittest
from module import Calculator

class ModuleTest(unittest.TestCase):

    def setUp(self):
        self.cal = Calculator(8, 4)

    def tearDown(self):
        pass

    def test_add(self):
        result = self.cal.add()
        self.assertEqual(result, 12)

    def test_sub(self):
        result = self.cal.sub()
        self.assertEqual(result, 4)

    def test_mul(self):
        result = self.cal.mul()
        self.assertEqual(result, 32)

    def test_div(self):
        result = self.cal.div()
        self.assertEqual(result, 2)

if __name__ == "__main__":
    # unittest.main()
    # 构造测试集
    suite = unittest.TestSuite()
    suite.addTest(ModuleTest("test_add"))
    suite.addTest(ModuleTest("test_mul"))
    suite.addTest(ModuleTest("test_sub"))
    suite.addTest(ModuleTest("test_div"))
    # 执行测试
    runner = unittest.TextTestRunner()
    runner.run(suite)

通过unittest单元测试框架编写的测试用例,更加规范和整洁。

对代码进行解释:

  1. 首先,import导入unittest单元测试框架;

  2. 其次,创建ModuleTest类继承unittest.TestCase类;

  3. setUp()方法,用于测试用例执行前的初始化工作,eg:初始化变量、生成数据库测试数据、打开浏览器等;

  4. tearDown()方法,用于测试用例执行之后的善后工作,eg:清除数据库测试数据、关闭文件、关闭浏览器等;

  5. 然后,创建具体的测试用例,包含被测试数据、预期测试结果;

  6. 接下来,调用unittest.TestSuite()类的addTest()方法,向测试套件中添加测试用例,所谓测试套件可以理解为测试用例的集合;

  7. 最后,通过unittest.TextTestRunner()类的run()方法运行测试套件中的测试用例。

注意:

  1. 根据unittest单元测试框架的要求,测试用例必须以“test”开头,eg:test_add、test_mul;

  2. 如果想默认运行当前测试文件中的所有测试用例,可以使用:unittest.main()方法。

测试结果如下:

root@TEST:~# python test.py 
....
----------------------------------------------------------------------
Ran 4 tests in 0.001s

OK
root@TEST:~# 

执行结果中的“.”代表一条运行通过的用例。


6.2 Django测试

django.test.TestCase从unittest.TestCase继承而来。

6.2.1 一个简单的例子

Django创建应用时,默认创建了test.py文件,查看/guest/sign/tests.py。

针对模型编写测试用例:

from django.test import TestCase
from sign.models import Guest, Event

# Create your tests here.
class ModelTest(TestCase):

    def setUp(self):
        Event.objects.create(id=1, name="oneplus 3 event", status=True, limit=2000, address="shenzhen", start_time="2016-08-31 02:18:22")
        Guest.objects.create(id=1, event_id=1, realname="alen", phone='13711001101',email="alen@mail.com", sign=False)

    def test_event_models(self):
        result = Event.objects.get(name="oneplus 3 event")
        self.assertEqual(result.address, "shenzhen")
        self.assertTrue(result.status)

    def test_guest_models(self):
        result = Guest.objects.get(phone="13711001101")
        self.assertEqual(result.realname, "alen")
        self.assertFalse(result.sign)

对上述代码进行分析:

  1. 首先,还是创建ModelTest类继承django.test.TestCase测试类;

  2. 然后,setUp()方法,初始化针对发布会表和嘉宾表的测试数据;

  3. 最后,通过test_event_models()、test_guest_models()测试方法,分别查询创建的数据,并对返回结果进行断言是否符合预期;

注意:

千万不要单独执行tests.py文件,Django专门提供了test命令来运行测试,效果如下:

root@TEST:/home/test/guest# python manage.py test
Creating test database for alias 'default'...
/usr/local/lib/python2.7/dist-packages/pymysql/cursors.py:166: Warning: (3135, u"'NO_ZERO_DATE', 'NO_ZERO_IN_DATE' and 'ERROR_FOR_DIVISION_BY_ZERO' sql modes should be used with strict mode. They will be merged with strict mode in a future release.")
  result = self._query(query)
/usr/local/lib/python2.7/dist-packages/pymysql/cursors.py:166: Warning: (3090, u"Changing sql mode 'NO_AUTO_CREATE_USER' is deprecated. It will be removed in a future release.")
  result = self._query(query)
..
----------------------------------------------------------------------
Ran 2 tests in 0.025s

OK
Destroying test database for alias 'default'...

Ps:这里的两个警告,也困扰了自己很久,本来是写了一个详细的排查和解决过程的,但是一不小心没保存,结果全没了,也懒得再重写了,这里就简单给大家介绍一下如何去掉这两个警告吧!

  • 第一个警告,需要修改Django的配置文件settings.py,将数据库的配置里设置SQL_MODES的地方注释掉:
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'HOST': '127.0.0.1',
        'PORT': '3306',
        'NAME': 'guest',
        'USER': 'root',
        'PASSWORD': 'nsfocus',
        #'OPTIONS': {
        #    'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
        #},
    }
}

然后修改MySQL数据库的配置文件:/etc/mysql/mysql.conf.d/mysqld.cnf,增加配置:

sql_mode = ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION

  • 第二个警告就简单了,只需要将提示的内容直接从配置里去掉即可。

6.2.2 运行测试用例

test命令,提供了可以控制测试用例执行的级别。

运行sign应用下的所有测试用例:

root@TEST:/home/test/guest# python manage.py test sign
Creating test database for alias 'default'...
..
----------------------------------------------------------------------
Ran 2 tests in 0.041s

OK
Destroying test database for alias 'default'...

运行sign应用下的tests.py测试文件:

root@TEST:/home/test/guest# python manage.py test sign.tests
Creating test database for alias 'default'...
..
----------------------------------------------------------------------
Ran 2 tests in 0.040s

OK
Destroying test database for alias 'default'...

运行sign应用tests.py测试文件下的ModelTest测试类:

root@TEST:/home/test/guest# python manage.py test sign.tests.ModelTest
Creating test database for alias 'default'...
..
----------------------------------------------------------------------
Ran 2 tests in 0.027s

OK
Destroying test database for alias 'default'...

运行sign应用tests.py测试文件下ModelTest测试类下面的test_event_models测试方法:

root@TEST:/home/test/guest# python manage.py test sign.tests.ModelTest.test_event_models
Creating test database for alias 'default'...
.
----------------------------------------------------------------------
Ran 1 test in 0.058s

OK
Destroying test database for alias 'default'... 

使用-p(或–pattern)参数模糊匹配测试文件:

root@TEST:/home/test/guest# python manage.py test -p test*.py
Creating test database for alias 'default'...
..
----------------------------------------------------------------------
Ran 2 tests in 0.029s

OK
Destroying test database for alias 'default'...


6.3 客户端测试

django.test.Client类,可以模拟一个虚拟的网络浏览器,可以测试视图views与Django的应用程序以编程方式交互:

  • 模拟“GET”和“POST”请求,观察响应结果,从HTTP(headers、status code)到页面内容;

  • 检查重定向链(如果有的话),再每一步检查URL和status code;

  • 用一个包括特定值得模板context来测试一个request被Django模板渲染。

示例:

root@TEST:/home/test/guest# python manage.py shell
Python 2.7.12 (default, Nov 19 2016, 06:48:10) 
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.test.utils import setup_test_environment    # 导入setup_test_environment方法
>>> setup_test_environment()                                # 用于测试前初始化测试环境
>>> from django.test import Client                          # 导入Client类
>>> c = Client()                                            
>>> response = c.get('/index/')                             # 通过get()请求/index/路径
>>> response.status_code                                    # 打印HTTP返回的状态码200代表请求成功
200
>>> 

6.3.1 测试首页

使用上面的方法对发布会首页进行测试,修改/guest/sign/tests.py:

#! /usr/bin python
# -*- coding:utf-8 -*-

from django.test import TestCase


# Create your tests here.
# 测试sign应用的视图
class IndexPageTest(TestCase):

    def test_index_page_renders_index_template(self):
        '''测试index视图'''
        response = self.client.get('/index/')             # 虽然没有导入django.test.Client类,但是self.client最终调用的依然是django.test.Client类的方法,请求/index/路径
        self.assertEqual(response.status_code, 200)       # status_code获取HTTP返回的状态码,使用assertEqual断言状态码是否为200
        self.assertTemplateUsed(response, 'index.html')   # 使用assertTemplateUsed()断言服务器是否使用的是index.html模板进行响应

6.3.2 测试登录动作

继续使用上面的方法对首页的登录动作进行测试,修改/guest/sign/tests.py:

class LoginActionTest(TestCase):
    '''测试登录动作'''

    def setUp(self):                                     # 初始化,调用User.objects.create_user创建登录用户数据
        User.objects.create_user('admin', 'admin@mail.com', 'admin123456')

    def test_add_admin(self):
        '''测试添加的用户数据是否正确'''
        user = User.objects.get(username='admin')
        self.assertEqual(user.username, 'admin')
        self.assertEqual(user.email, 'admin@mail.com')    # 注意这里书中有误,user表里的字段是email而不是mail,否则会报错

    def test_login_action_username_password_null(self):
        '''测试用户名密码为空'''
        test_data = {'username':'', 'password': ''}
        response = self.client.post('/login_action/', data=test_data)    # 通过post()方法请求'/login_aciton/'路径测试登录功能
        self.assertEqual(response.status_code, 200)
        self.assertIn(b'username or password error!', response.content)   # assertIn()方法断言返回的HTML页面中是否包含指定的提示字符串

    def test_login_action_username_password_error(self):
        '''测试用户名密码错误'''
        test_data = {'username':'abc', 'password':'123'}
        response = self.client.post('/login_action/', data=test_data)
        self.assertEqual(response.status_code, 200)
        self.assertIn(b'username or password error!', response.content)

    def test_login_action_success(self):
        '''测试登录成功'''
        test_data = {'username':'admin', 'password':'admin123456'}
        response = self.client.post('/login_action/', data=test_data)
        self.assertEqual(response.status_code, 302)    # 这里为什么断言的是302,是因为登录成功后,通过HttpResponseRedirect()跳转到了'/event_manage/'路径,这是一个重定向

6.3.3 测试发布会管理

继续使用上面的方法对发布会管理视图进行测试,修改/guest/sign/tests.py:

class EventManageTest(TestCase):
    """测试发布会管理"""

    def setUp(self):
        '''初始化测试数据,包括登录用户数据,发布会数据'''
        User.objects.create_user('admin', 'admin@mail.com', 'admin123456')
        Event.objects.create(name='xiaomi5', limit=2000, address='beijing', status=1, start_time='2017-08-10 12:30:00')
        self.login_user = {'username':'admin', 'password':'admin123456'}    # 定义登录变量

    def test_event_manage_success(self):
        '''测试发布会:xiaomi5'''
        response = self.client.post('/login_action/', data=self.login_user)
        response = self.client.post('/event_manage/')
        self.assertEqual(response.status_code, 200)
        self.assertIn(b'xiaomi5', response.content)
        self.assertIn(b'beijing', response.content)

    def test_event_manage_search_success(self):
        '''测试发布会搜索'''
        # 这里自己给自己挖了个坑,post登录请求的时候少写了一个/,当时写成了'/login_action',我擦一执行测试就返回302,排查了好半天才发现,哎,需要认真仔细啊
        response = self.client.post('/login_action/', data=self.login_user)
        response = self.client.post('/search_name/', {'name':'xiaomi5'})
        self.assertEqual(response.status_code, 200)
        self.assertIn(b'xiaomi5', response.content)
        self.assertIn(b'beijing', response.content)

注意:

由于发布会管理event_manage和发布会名称搜索search_name两个视图都被@login_required装饰器修饰,所以想测试这两个功能,必须要先登录成功,并且需要构造登录用户的数据。

6.3.4 测试嘉宾管理

继续使用上面的方法对嘉宾管理视图进行测试,修改/guest/sign/tests.py:

class GuestManageTest(TestCase):
    """测试嘉宾管理"""

    def setUp(self):
        '''还是使用setUp初始化一些测试数据'''
        User.objects.create_user('admin', 'admin@mail.com', 'admin123456')
        Event.objects.create(id=1, name='xiaomi5', limit=2000, address='beijing', status=1, start_time='2017-08-10 12:30:00')
        Guest.objects.create(realname='alen', phone=18611001100, email='alen@mail.com', sign=0, event_id=1)
        self.login_user = {'username':'admin', 'password':'admin123456'}

    def test_event_manage_success(self):
        '''测试嘉宾信息:alen'''
        response = self.client.post('/login_action/', data=self.login_user)
        response = self.client.post('/guest_manage/')
        self.assertEqual(response.status_code, 200)
        self.assertIn(b'alen', response.content)
        self.assertIn(b'18611001100', response.content)

    def test_guest_manage_search_success(self):
        '''测试嘉宾搜索功能'''
        response = self.client.post('/login_action/', data=self.login_user)
        # 这里就是坑了,我们根据书中描述一步一步来得话,我们在views.py里定义的搜索功能是根据名字来搜索的,而不是根据手机号,下面应该修改为('/search_realname/', {'realname':'alen'})
        # response = self.client.post('/search_phone/', {'phone':'18611001100'})
        response = self.client.post('/search_realname/', {'realname':'alen'})
        self.assertEqual(response.status_code, 200)
        self.assertIn(b'alen', response.content)
        self.assertIn(b'18611001100', response.content)

上面的代码,虫师给大家挖了很多坑,如果只编写了测试代码而未进行实际测试,是不会发现有问题的,我已经备注了,大家参见上面的备注吧。

其他知识点没有什么,基本和上面的类似,都是setUp初始化测试数据,然后分别对两个视图函数进行测试。

6.3.5 测试用户签到

继续使用上面的方法对签到管理视图进行测试,修改/guest/sign/tests.py:

class SignIndexActionTest(TestCase):
    """测试发布会签到"""

    def setUp(self):
        User.objects.create_user('admin', 'admin@mail.com', 'admin123456')
        Event.objects.create(id=1, name="xiaomi5", limit=2000, address='beijing', status=1, start_time='2017-8-10 12:30:00')
        Event.objects.create(id=2, name="oneplus4", limit=2000, address='shenzhen', status=1, start_time='2017-6-10 12:30:00')
        Guest.objects.create(realname="alen", phone=18611001100, email='alen@mail.com', sign=0, event_id=1)
        Guest.objects.create(realname="una", phone=18611011101, email='una@mail.com', sign=1, event_id=2)
        self.login_user = {'username':'admin', 'password':'admin123456'}

    def test_event_models(self):
        '''测试添加的发布会数据'''
        result1 = Event.objects.get(name='xiaomi5')
        self.assertEqual(result1.address, 'beijing')
        self.assertTrue(result1.status)
        result2 = Event.objects.get(name='oneplus4')
        self.assertEqual(result2.address, 'shenzhen')
        self.assertTrue(result2.status)

    def test_guest_models(self):
        '''测试添加的嘉宾数据'''
        result = Guest.objects.get(realname='alen')
        self.assertEqual(result.phone, '18611001100')
        self.assertEqual(result.event_id, 1)
        self.assertFalse(result.sign)

    def test_sign_index_action_phone_null(self):
        '''测试手机号为空'''
        response = self.client.post('/login_action/', data=self.login_user)
        response = self.client.post('/sign_index_action/1/', {"phone":""})
        self.assertEqual(response.status_code, 200)
        self.assertIn(b"phone error.", response.content)

    def test_sign_index_action_phone_or_event_id_error(self):
        '''测试手机号或发布会id错误'''
        response = self.client.post('/login_action/', data=self.login_user)
        response = self.client.post('/sign_index_action/2/', {"phone":"18611001100"})
        self.assertEqual(response.status_code, 200)
        self.assertIn(b"event id or phone error.", response.content)

    def test_sign_index_action_user_sign_has(self):
        '''测试嘉宾已签到'''
        response = self.client.post('/login_action/', data=self.login_user)
        response = self.client.post('/sign_index_action/2/', {"phone":"18611011101"})
        self.assertEqual(response.status_code, 200)
        self.assertIn(b"user has sign in.", response.content)

    def test_sign_index_action_sign_success(self):
        '''测试嘉宾签到成功'''
        response = self.client.post('/login_action/', data=self.login_user)
        response = self.client.post('/sign_index_action/1/', {"phone":"18611001100"})
        self.assertEqual(response.status_code, 200)
        self.assertIn(b"sign in success!", response.content)

测试嘉宾签到功能只是在数据初始化构造上内容多了,测试的点覆盖了签到功能全部分支,都比较好理解,只是在最终执行测试结果的时候,我崩溃了,6个测试用例中出现了2个失败,详细失败原因见下面:

root@TEST:/home/test/guest# python manage.py test sign.tests.SignIndexActionTest
Creating test database for alias 'default'...
..F18611001100
.F18611011101
.
======================================================================
FAIL: test_sign_index_action_phone_null (sign.tests.SignIndexActionTest)
测试手机号为空
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/test/guest/sign/tests.py", line 150, in test_sign_index_action_phone_null
    self.assertEqual(response.status_code, 200)
AssertionError: 404 != 200

======================================================================
FAIL: test_sign_index_action_sign_success (sign.tests.SignIndexActionTest)
测试嘉宾签到成功
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/test/guest/sign/tests.py", line 171, in test_sign_index_action_sign_success
    self.assertEqual(response.status_code, 200)
AssertionError: 404 != 200

----------------------------------------------------------------------
Ran 6 tests in 0.617s

FAILED (failures=2)
Destroying test database for alias 'default'...

我了个去的,为什么完全照搬书中代码还会出现404错误,感觉会不会是虫师又在哪给我们挖坑了呢?

错误提示的很清晰,就是“测试手机号为空”、“测试嘉宾签到成功”这两个用例,在判断返回页面的status_code的时候出错了,预期是返回200,但实际返回了404。

WHY?怎么会在这两个地方出现404错误,查看测试代码:

    def test_sign_index_action_phone_null(self):
        '''测试手机号为空'''
        response = self.client.post('/login_action/', data=self.login_user)
        response = self.client.post('/sign_index_action/1/', {"phone":""})
        self.assertEqual(response.status_code, 200)
        self.assertIn(b"phone error.", response.content)

    def test_sign_index_action_sign_success(self):
        '''测试嘉宾签到成功'''
        response = self.client.post('/login_action/', data=self.login_user)
        response = self.client.post('/sign_index_action/1/', {"phone":"18611001100"})
        self.assertEqual(response.status_code, 200)
        self.assertIn(b"sign in success!", response.content)

发现了一个共同点,这两个测试用例都是针对测试数据发布会id=1的,而与之关联的测试嘉宾的签到sign值在初始化的时候是sign=0,也就是未签到的状态。

将涉及到的两个值,分别做修改,如果我把这两个用例里的发布会id从1改为2,会发现再次执行的结果里“测试手机号为空”执行成功了,但是“测试嘉宾签到成功”依然还是失败的,捋一下,改成2,也就是对应的嘉宾已经签到了,所以“测试嘉宾签到成功”失败也是自然的。

如果我们把初始化数据里的嘉宾alen的sign改为1已签到,再次执行的时候,发现结果和上面一样,都是“测试手机号为空”能通过,但是“测试嘉宾签到成功”失败。

这里也真的是奇怪了,为什么会出现这种情况,我现在抛开测试数据,直接去看下真实的数据情况,因为之前为了验证嘉宾签到代码,已经全部都签到了,只能通过修改数据库的方式,将sign从1改为0,点击发布会页面的sign链接,发现的确会报404错误,奇怪了,为什么没嘉宾签到,就返回404错误呢?按理来说,即使没有嘉宾签到,从发布会点击签到页面,也应该展示签到页面,只不过显示的已签到数为0。

自己在这段时间里,跑偏了很久,想过是不是签到功能不完善,难道需要先将sign从初始化的0update为1,再执行测试用例?或者是提示404,找不到页面,那我就单独把sign_index_action的html从sign_index.html里独立出来?……

自己真的是跑偏了太久,休假前到休假后,中间隔了快一周时间,再次查看views.py里的签到功能代码才发现问题所在:

并不是虫师给大家挖坑,而是自己给自己挖了一个大坑!自己在做虫师的作业的时候,也就是在签到页面显示总的嘉宾数和已签到嘉宾数的时候,使用了一个擅自查资料使用的方法:get_list_or_404,一切的罪过都是由它而来,我们来看下面的代码就:

# 签到页面
@login_required
def sign_index(request, eid):
    username = request.session.get('user', '')
    event = get_object_or_404(Event, id=eid)
    guest_list = len(get_list_or_404(Guest, event_id=eid))
    guest_sign = len(get_list_or_404(Guest, event_id=eid, sign=1))
    return render(request, 'sign_index.html', {"user": username, "event": event, 'guest_list': guest_list, 'guest_sign': guest_sign})

这里使用的get_list_or_404()方法,当sign=1的时候并没有问题,因为始终都能获取到已经签到的嘉宾数量,但是一旦sign=0,就会导致get_list_or_404方法直接返回404错误,而不是返回0个已签到嘉宾,自己当时以为查找到了一个类似get_object_or_404相类似的好方法去获取嘉宾数量,但是没想到当获取不到嘉宾数量的时候应该怎样展示!聪明反被聪明误啊!

那么该如何去修改呢?其实很简单,只要正常去查询数据库,获取到嘉宾总数和已签到嘉宾数量就可以了,修改后的代码如下:

# 签到页面
@login_required
def sign_index(request, eid):
    username = request.session.get('user', '')
    event = get_object_or_404(Event, id=eid)
    guest_list = len(Guest.objects.filter(event_id=eid))
    guest_sign = len(Guest.objects.filter(event_id=eid, sign=1))
    return render(request, 'sign_index.html', {"user": username, "event": event, 'guest_list': guest_list, 'guest_sign': guest_sign})

修复后,再去执行测试用例,就全部通过了。


6.4 总结

至此,本章关于Django测试的内容就结束了,总结起来,就是如下几点:

  1. Django的test库,提供了丰富的单元测试方法;

  2. test库中的TestCase方法可以测试模型、视图;

  3. 每个测试用例必须以test命名开头;

  4. 一些断言关键字如:assertEqual判断是否相等、assertFalse判断为否、assertTrue判断为是、assertIn判断包含;

  5. 基本的测试套路就是构造数据,对指定测试内容传递数据进行测试,对返回结果进行判断。

更多关于Django测试方法技巧请参见官方文档:

Django测试部分官方文档

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值