单元测试
单元测试是实际开发中很重要但也经常被忽视的部分,其主要原因是编写Web功能测试所耗费的时间可能会大于你开发此功能的时间,因此,对于需要快速开发、上线的业务来说,项目中关于单元测试的部分很少。但是对于需要长期维护的项目,还需要考虑增加单元测试。只是第一次编写时会比较耗费时间,一旦基础结构完成,后续跟着功能的增加来增加单元测试并不会耗费多少时间,但是收益却是十分明显的。
单元测试的主要目的是让代码更健壮,尤其是在进行重构或者业务增加的时候,跑通单元测试,就意味着新加入的代码或者修改的代码没有问题。在实际开发中,单元测试的覆盖率没有那么高,其主要原因也是写单元测试的成本过高,尤其是对于很复杂的业务。下面就Django内置的测试工具进行说明。
TestCase中几个方法的说明
在Django中运行测试用例时,如果我们使用的SQLite数据库,Django会帮助我们创建一个基于内存测试的数据库,用于测试。
但是对于MySQL数据库,Django会直接用配置的数据库用户和密码创建一个名为test_student_db的数据库,用于测试。因此需要保证有建表和建库的权限
当然,也可以自定义测试用的数据库名称,通过setting配置:
1DATABASE = { 2 'default': { 3 'ENGINE': 'django.db.backends.mysql', 4 'USER': 'mydatabaseuser', 5 'NAME': 'mydatabase', 6 'TEST': { 7 'NAME': 'mytestdatabase', # 这里配置 8 }, 9 }, 10}
Django提供了一个名为TestCase的基类,我们可以通过继承这个类来实现自己的测试逻辑。TestCase为我们系统了以下方法(需要用到的):
def setUp(self):用来初始化环境,包括创建初始化的数据,或者做一些其他准备工作。def test_xxxx(self):方法后面的xxxx可以是任意东西。以test_开头的方法,会被认为是需要测试的方法,跑测试时会被执行,每个需要被测试的方法是相互独立的。def tearDown(self):跟setUp相对,用来清理测试环境和测试数据。
样例代码:
Model层测试
主要保证数据的写入和查询是可用的,同时也需要保证我们在model所提供的方法是复合预期的。
比如在Model中增加了sex_show这样的属性,用来展示sex这个字段的中文显示,而不是1,2。当然,这个功能是Django已经提供给我们的。
1from django.db import models 2 3# Create your models here. 4 5class Student(models.Model): 6 SEX_ITEMS = [ 7 (1, '男'), 8 (2, '女'), 9 (0, '未知'), 10 ] 11 STATUS_ITEMS = [ 12 (0, '申请'), 13 (1, '通过'), 14 (2, '拒绝'), 15 ] 16 name = models.CharField(max_length=128, verbose_name='姓名') 17 sex = models.IntegerField(choices=SEX_ITEMS, verbose_name='性别') 18 profession = models.CharField(max_length=128, verbose_name='职业') 19 email = models.EmailField(verbose_name='Email') 20 qq = models.CharField(max_length=128, verbose_name='QQ') 21 phone = models.CharField(max_length=128, verbose_name='电话') 22 23 status = models.IntegerField(choices=STATUS_ITEMS, default=0, verbose_name='审核状态') 24 created_time = models.DateTimeField(auto_now_add=True, editable=False, verbose_name='创建时间') 25 26 def __str__(self): 27 return '<Student: ()>'.format(self.name) 28 29 class Meta: 30 verbose_name = verbose_name_plural = '学员信息' 31 32 @property 33 def sex_show(self): 34 return dict(self.SEX_ITEMS)[self.sex] 35 36 @classmethod 37 def get_all(cls): 38 return cls.objects.all()
views.py同级目录下的test.py,它是App初始化时Django默认帮我们创建的
1from django.test import TestCase, Client 2from .models import Student 3 4# Create your tests here. 5class StudentTestCase(TestCase): 6 def setUp(self) -> None: 7 Student.objects.create( 8 name='test', 9 sex=1, 10 email='test@test.com', 11 profession='test职业', 12 qq='123', 13 phone='123456', 14 ) 15 16 def test_create_and_sex_show(self): 17 student = Student.objects.create( 18 name='zhangsan', 19 sex=1, 20 email='zhangsan@test.com', 21 profession='程序员', 22 qq='123123', 23 phone='12121212', 24 ) 25 self.assertEqual(student.sex_show, '男', '性别字段内容更展示不一致') 26 27 def test_filter(self): 28 Student.objects.create( 29 name='lisi', 30 sex=1, 31 email='lisi@test.com', 32 profession='程序员', 33 qq='2124325', 34 phone='11111111' 35 ) 36 name='test' 37 students= Student.objects.filter(name=name) 38 self.assertEqual(students.count(), 1, '应该只存在一个名称为{}的记录'.format(name)) 39
在setUp中,创建了一条数据用于测试。test_create_and_sex_show用来测试数据创建以及sex字段的正确展示,test_filter测试查询是否可用。
需要说明的是,每一个以test_开头的函数都是独立运行的。因此,setUp和tearDown也会在每个函数运行时被执行。简单理解就是每个函数都处于独立的运行环境。
View层测试
test.py 添加如下代码
1 def test_get_index(self): 2 # 测试首页的可用性 3 client = Client() 4 response = client.get('/') 5 self.assertEqual(response.status_code, 200, 'status code must be 200!') 6 7 def test_post_student(self): 8 client = Client() 9 data = dict( 10 name='test_for_post', 11 sex=1, 12 email='333@test.com', 13 profession='程序员', 14 qq='333', 15 phone='222', 16 ) 17 response=client.post('/', data) 18 self.assertEqual(response.status_code, 302, 'status code must be 302!') 19 20 response = client.get('/') 21 self.assertEqual(b'test_for_post' in response.content, 'response content must contain `test_for_post`')