对Python的Django框架中的项目进行单元测试的方法


Posted in Python onApril 11, 2016

 Python中的单元测试

我们先来回顾一下Python中的单元测试方法。
下面是一个 Python的单元测试简单的例子:

假如我们开发一个除法的功能,有的同学可能觉得很简单,代码是这样的:

def division_funtion(x, y):
  return x / y

但是这样写究竟对还是不对呢,有些同学可以在代码下面这样测试:

def division_funtion(x, y):
  return x / y
 
 
if __name__ == '__main__':
  print division_funtion(2, 1)
  print division_funtion(2, 4)
  print division_funtion(8, 3)

但是这样运行后得到的结果,自己每次都得算一下去核对一遍,很不方便,Python中有 unittest 模块,可以很方便地进行测试,详情可以文章最后的链接,看官网文档的详细介绍。

下面是一个简单的示例:

import unittest
 
 
def division_funtion(x, y):
  return x / y
 
 
class TestDivision(unittest.TestCase):
  def test_int(self):
    self.assertEqual(division_funtion(9, 3), 3)
 
  def test_int2(self):
    self.assertEqual(division_funtion(9, 4), 2.25)
 
  def test_float(self):
    self.assertEqual(division_funtion(4.2, 3), 1.4)
 
 
if __name__ == '__main__':
  unittest.main()

我简单地写了三个测试示例(不一定全面,只是示范,比如没有考虑除数是0的情况),运行后发现:

F.F
======================================================================
FAIL: test_float (__main__.TestDivision)
----------------------------------------------------------------------
Traceback (most recent call last):
 File "/Users/tu/YunPan/mydivision.py", line 16, in test_float
  self.assertEqual(division_funtion(4.2, 3), 1.4)
AssertionError: 1.4000000000000001 != 1.4
 
======================================================================
FAIL: test_int2 (__main__.TestDivision)
----------------------------------------------------------------------
Traceback (most recent call last):
 File "/Users/tu/YunPan/1.py", line 13, in test_int2
  self.assertEqual(division_funtion(9, 4), 2.25)
AssertionError: 2 != 2.25
 
----------------------------------------------------------------------
Ran 3 tests in 0.001s
 
FAILED (failures=2)

汗!发现了没,竟然两个都失败了,测试发现:

4.2除以3 等于 1.4000000000000001 不等于期望值 1.4

9除以4等于2,不等于期望的 2.25

下面我们就是要修复这些问题,再次运行测试,直到运行不报错为止。

譬如根据实际情况,假设我们只需要保留到小数点后6位,可以这样改:

def division_funtion(x, y):
  return round(float(x) / y, 6)

再次运行就不报错了:

...
----------------------------------------------------------------------
Ran 3 tests in 0.000s

 
OK

Django中的单元测试

尽早进行单元测试(UnitTest)是比较好的做法,极端的情况甚至强调“测试先行”。现在我们已经有了第一个model类和Form类,是时候开始写测试代码了。

Django支持python的单元测试(unit test)和文本测试(doc test),我们这里主要讨论单元测试的方式。这里不对单元测试的理论做过多的阐述,假设你已经熟悉了下列概念:test suite, test case, test/test action,  test data, assert等等。

在单元测试方面,Django继承python的unittest.TestCase实现了自己的django.test.TestCase,编写测试用 例通常从这里开始。测试代码通常位于app的tests.py文件中(也可以在models.py中编写,但是我不建议这样做)。在Django生成的 depotapp中,已经包含了这个文件,并且其中包含了一个测试用例的样例:

depot/depotapp/tests.py

from django.test import TestCase
class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.assertEqual(1 + 1, 2)

你可以有几种方式运行单元测试:

  • python manage.py test:执行所有的测试用例
  • python manage.py test app_name, 执行该app的所有测试用例
  • python manage.py test app_name.case_name: 执行指定的测试用例

用第三种方式执行上面提供的样例,结果如下:

$ python manage.py test depotapp.SimpleTest
Creating test database for alias 'default'...
.
----------------------------------------------------------------------
Ran 1 test in 0.012s

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

你可能会主要到,输出信息中包括了创建和删除数据库的操作。为了避免测试数据造成的影响,测试过程会使用一个单独的数据库,关于如何指定测试数据库 的细节,请查阅Django文档。在我们的例子中,由于使用sqlite数据库,Django将默认采用内存数据库来进行测试。

下面就让我们来编写测试用例。在《Agile Web Development with Rails 4th》中,7.2节,最终实现的ProductTest代码如下:

class ProductTest < ActiveSupport::TestCase
test "product attributes must not be empty"do
product = Product.new
assert product.invalid?
assert product.errors[:title].any?
assert product.errors[:description].any?
assert product.errors[:price].any?
assert product.errors[:image_url].any?
end
test "product price must be positive"do
product = Product.new(:title => "My Book Title",
:description => "yyy",
:image_url => "zzz.jpg")
product.price = -1
assert product.invalid?
assert_equal "must be greater than or equal to 0.01",
product.errors[:price].join('; ')
product.price = 0
assert product.invalid?
assert_equal "must be greater than or equal to 0.01",
product.errors[:price].join('; ')
product.price = 1
assert product.valid?
end
def new_product(image_url)
Product.new(:title => "My Book Title",
:description => "yyy",
:price => 1,
:image_url => image_url)
end
test "image url"do
ok = %w{ fred.gif fred.jpg fred.png FRED.JPG FRED.Jpg
http://a.b.c/x/y/z/fred.gif }
bad = %w{ fred.doc fred.gif/more fred.gif.more }
ok.eachdo |name|
assert new_product(name).valid?, "#{name} shouldn't be invalid"
end
bad.eachdo |name|
assert new_product(name).invalid?, "#{name} shouldn't be valid"
end
end
test "product is not valid without a unique title"do
product = Product.new(:title => products(:ruby).title,
:description => "yyy",
:price => 1,
:image_url => "fred.gif")
assert !product.save
assert_equal "has already been taken", product.errors[:title].join('; ')
end
test "product is not valid without a unique title - i18n"do
product = Product.new(:title => products(:ruby).title,
:description => "yyy",
:price => 1,
:image_url => "fred.gif")
assert !product.save
assert_equal I18n.translate('activerecord.errors.messages.taken'),
product.errors[:title].join('; ')
end
end

对Product测试的内容包括:

1.title,description,price,image_url不能为空;

2. price必须大于零;

3. image_url必须以jpg,png,jpg结尾,并且对大小写不敏感;

4. titile必须唯一;

让我们在Django中进行这些测试。由于ProductForm包含了模型校验和表单校验规则,使用ProductForm可以很容易的实现上述测试:

depot/depotapp/tests.py

#/usr/bin/python
#coding: utf8
"""
This file demonstrates writing tests using the unittest module. These will pass
when you run "manage.py test".
Replace this with more appropriate tests for your application.
"""
from django.test import TestCase
from forms import ProductForm
class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.assertEqual(1 + 1, 2)
class ProductTest(TestCase):
def setUp(self):
self.product = {
'title':'My Book Title',
'description':'yyy',
'image_url':'http://google.com/logo.png',
'price':1
}
f = ProductForm(self.product)
f.save()
self.product['title'] = 'My Another Book Title'
#### title,description,price,image_url不能为空
def test_attrs_cannot_empty(self):
f = ProductForm({})
self.assertFalse(f.is_valid())
self.assertTrue(f['title'].errors)
self.assertTrue(f['description'].errors)
self.assertTrue(f['price'].errors)
self.assertTrue(f['image_url'].errors)
####  price必须大于零
def test_price_positive(self):
f = ProductForm(self.product)
self.assertTrue(f.is_valid())
self.product['price'] = 0
f = ProductForm(self.product)
self.assertFalse(f.is_valid())
self.product['price'] = -1
f = ProductForm(self.product)
self.assertFalse(f.is_valid())
self.product['price'] = 1
####  image_url必须以jpg,png,jpg结尾,并且对大小写不敏感;
def test_imgae_url_endwiths(self):
url_base = 'http://google.com/'
oks = ('fred.gif', 'fred.jpg', 'fred.png', 'FRED.JPG', 'FRED.Jpg')
bads = ('fred.doc', 'fred.gif/more', 'fred.gif.more')
for endwith in oks:
self.product['image_url'] = url_base+endwith
f = ProductForm(self.product)
self.assertTrue(f.is_valid(),msg='error when image_url endwith '+endwith)
for endwith in bads:
self.product['image_url'] = url_base+endwith
f = ProductForm(self.product)
self.assertFalse(f.is_valid(),msg='error when image_url endwith '+endwith)
self.product['image_url'] = 'http://google.com/logo.png'
###  titile必须唯一
def test_title_unique(self):
self.product['title'] = 'My Book Title'
f = ProductForm(self.product)
self.assertFalse(f.is_valid())
self.product['title'] = 'My Another Book Title'

然后运行 python manage.py test depotapp.ProductTest。如同预想的那样,测试没有通过:

Creating test database for alias 'default'...
.F..
======================================================================
FAIL: test_imgae_url_endwiths (depot.depotapp.tests.ProductTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/holbrook/Documents/Dropbox/depot/../depot/depotapp/tests.py", line 65, in test_imgae_url_endwiths
self.assertTrue(f.is_valid(),msg='error when image_url endwith '+endwith)
AssertionError: False is not True : error when image_url endwith FRED.JPG

----------------------------------------------------------------------
Ran 4 tests in 0.055s

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

因为我们之前并没有考虑到image_url的图片扩展名可能会大写。修改ProductForm的相关部分如下:

def clean_image_url(self):
url = self.cleaned_data['image_url']
ifnot endsWith(url.lower(), '.jpg', '.png', '.gif'):
raise forms.ValidationError('图片格式必须为jpg、png或gif')
return url

然后再运行测试:

$ python manage.py test depotapp.ProductTest
Creating test database for alias 'default'...
....
----------------------------------------------------------------------
Ran 4 tests in 0.060s

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

测试通过,并且通过单元测试,我们发现并解决了一个bug。

 

Python 相关文章推荐
python实现代码行数统计示例分享
Feb 10 Python
Python读写Json涉及到中文的处理方法
Sep 12 Python
Python利用Beautiful Soup模块修改内容方法示例
Mar 27 Python
Python实现简单文本字符串处理的方法
Jan 22 Python
python保存网页图片到本地的方法
Jul 24 Python
对Python3使运行暂停的方法详解
Feb 18 Python
Python+Pyqt实现简单GUI电子时钟
Feb 22 Python
Python 脚本的三种执行方式小结
Dec 21 Python
python实现录音功能(可随时停止录音)
Oct 26 Python
python 爬虫网页登陆的简单实现
Nov 30 Python
python matplotlib工具栏源码探析三之添加、删除自定义工具项的案例详解
Feb 25 Python
Python之matplotlib绘制饼图
Apr 13 Python
使用Python的Django框架结合jQuery实现AJAX购物车页面
Apr 11 #Python
Python的Django REST框架中的序列化及请求和返回
Apr 11 #Python
python中的错误处理
Apr 10 #Python
python脚本实现xls(xlsx)转成csv
Apr 10 #Python
Python使用gensim计算文档相似性
Apr 10 #Python
Python调用SQLPlus来操作和解析Oracle数据库的方法
Apr 09 #Python
python调用fortran模块
Apr 08 #Python
You might like
PHP简单实现数字分页功能示例
2016/08/24 PHP
Ubuntu VPS中wordpress网站打开时提示”建立数据库连接错误”的解决办法
2016/11/03 PHP
php+js实现的无刷新下载文件功能示例
2019/08/23 PHP
表单内同名元素的控制
2006/11/22 Javascript
Dojo 学习笔记入门篇 First Dojo Example
2009/11/15 Javascript
使用JavaScript动态设置样式实现代码(2)
2013/01/25 Javascript
jqGrid增加时--判断开始日期与结束日期(实例解析)
2013/11/08 Javascript
Nodejs Post请求报socket hang up错误的解决办法
2014/09/25 NodeJs
JavaScript实现页面跳转的几种常用方式
2015/11/28 Javascript
jQuery实现简单隔行变色的方法
2016/02/20 Javascript
jquery拖动层效果插件用法实例分析(附demo源码)
2016/04/28 Javascript
ion content 滚动到底部会遮住一部分视图的快速解决方法
2016/09/06 Javascript
纯JS代码实现隔行变色鼠标移入高亮
2016/11/23 Javascript
DOM 事件的深入浅出(一)
2016/12/05 Javascript
bootstrapvalidator之API学习教程
2017/06/29 Javascript
浅谈Vue.js中的v-on(事件处理)
2017/09/05 Javascript
vue开发移动端底部导航条功能
2020/04/08 Javascript
python代码检查工具pylint 让你的python更规范
2012/09/05 Python
Python抓取框架 Scrapy的架构
2016/08/12 Python
一步步教你用Python实现2048小游戏
2017/01/19 Python
Python列表推导式与生成器用法分析
2018/08/02 Python
pytorch+lstm实现的pos示例
2020/01/14 Python
基于tf.shape(tensor)和tensor.shape()的区别说明
2020/06/30 Python
Python使用lambda抛出异常实现方法解析
2020/08/20 Python
美国女性卫生用品公司:Thinx
2017/06/30 全球购物
一个C/C++编程面试题
2013/11/10 面试题
小学生考试获奖感言
2014/01/30 职场文书
幼儿教师寄语集锦
2014/04/03 职场文书
班级光棍节联谊会策划书
2014/10/10 职场文书
2015年个人实习工作总结
2014/12/12 职场文书
后进生评语大全
2015/01/04 职场文书
2019年手机市场的调研报告2篇
2019/10/10 职场文书
Python上下文管理器Content Manager
2021/06/26 Python
使用goaccess分析nginx日志的详细方法
2021/07/09 Servers
Python爬虫入门案例之回车桌面壁纸网美女图片采集
2021/10/16 Python
html网页引入svg图片的4种方式
2022/08/05 HTML / CSS