Django实战(10):单元测试

尽早进行单元测试(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

 

[python] view plaincopy
  1. from django.test import TestCase
  2. class SimpleTest(TestCase):
  3. def test_basic_addition(self):
  4. """
  5. Tests that 1 + 1 always equals 2.
  6. """
  7. 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代码如下:

 

  1. class ProductTest < ActiveSupport::TestCase
  2. test "product attributes must not be empty"do
  3. product = Product.new
  4. assert product.invalid?
  5. assert product.errors[:title].any?
  6. assert product.errors[:description].any?
  7. assert product.errors[:price].any?
  8. assert product.errors[:image_url].any?
  9. end
  10. test "product price must be positive"do
  11. product = Product.new(:title => "My Book Title",
  12. :description => "yyy",
  13. :image_url => "zzz.jpg")
  14. product.price = -1
  15. assert product.invalid?
  16. assert_equal "must be greater than or equal to 0.01",
  17. product.errors[:price].join('; ')
  18. product.price = 0
  19. assert product.invalid?
  20. assert_equal "must be greater than or equal to 0.01",
  21. product.errors[:price].join('; ')
  22. product.price = 1
  23. assert product.valid?
  24. end
  25. def new_product(image_url)
  26. Product.new(:title => "My Book Title",
  27. :description => "yyy",
  28. :price => 1,
  29. :image_url => image_url)
  30. end
  31. test "image url"do
  32. ok = %w{ fred.gif fred.jpg fred.png FRED.JPG FRED.Jpg
  33. http://a.b.c/x/y/z/fred.gif }
  34. bad = %w{ fred.doc fred.gif/more fred.gif.more }
  35. ok.eachdo |name|
  36. assert new_product(name).valid?, "#{name} shouldn't be invalid"
  37. end
  38. bad.eachdo |name|
  39. assert new_product(name).invalid?, "#{name} shouldn't be valid"
  40. end
  41. end
  42. test "product is not valid without a unique title"do
  43. product = Product.new(:title => products(:ruby).title,
  44. :description => "yyy",
  45. :price => 1,
  46. :image_url => "fred.gif")
  47. assert !product.save
  48. assert_equal "has already been taken", product.errors[:title].join('; ')
  49. end
  50. test "product is not valid without a unique title - i18n"do
  51. product = Product.new(:title => products(:ruby).title,
  52. :description => "yyy",
  53. :price => 1,
  54. :image_url => "fred.gif")
  55. assert !product.save
  56. assert_equal I18n.translate('activerecord.errors.messages.taken'),
  57. product.errors[:title].join('; ')
  58. end
  59. 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

 

[python] view plaincopy
  1. #/usr/bin/python
  2. #coding: utf8
  3. """
  4. This file demonstrates writing tests using the unittest module. These will pass
  5. when you run "manage.py test".
  6. Replace this with more appropriate tests for your application.
  7. """
  8. from django.test import TestCase
  9. from forms import ProductForm
  10. class SimpleTest(TestCase):
  11. def test_basic_addition(self):
  12. """
  13. Tests that 1 + 1 always equals 2.
  14. """
  15. self.assertEqual(1 + 1, 2)
  16. class ProductTest(TestCase):
  17. def setUp(self):
  18. self.product = {
  19. 'title':'My Book Title',
  20. 'description':'yyy',
  21. 'image_url':'http://google.com/logo.png',
  22. 'price':1
  23. }
  24. f = ProductForm(self.product)
  25. f.save()
  26. self.product['title'] = 'My Another Book Title'
  27. ####  title,description,price,image_url不能为空
  28. def test_attrs_cannot_empty(self):
  29. f = ProductForm({})
  30. self.assertFalse(f.is_valid())
  31. self.assertTrue(f['title'].errors)
  32. self.assertTrue(f['description'].errors)
  33. self.assertTrue(f['price'].errors)
  34. self.assertTrue(f['image_url'].errors)
  35. ####   price必须大于零
  36. def test_price_positive(self):
  37. f = ProductForm(self.product)
  38. self.assertTrue(f.is_valid())
  39. self.product['price'] = 0
  40. f = ProductForm(self.product)
  41. self.assertFalse(f.is_valid())
  42. self.product['price'] = -1
  43. f = ProductForm(self.product)
  44. self.assertFalse(f.is_valid())
  45. self.product['price'] = 1
  46. ####   image_url必须以jpg,png,jpg结尾,并且对大小写不敏感;
  47. def test_imgae_url_endwiths(self):
  48. url_base = 'http://google.com/'
  49. oks = ('fred.gif', 'fred.jpg', 'fred.png', 'FRED.JPG', 'FRED.Jpg')
  50. bads = ('fred.doc', 'fred.gif/more', 'fred.gif.more')
  51. for endwith in oks:
  52. self.product['image_url'] = url_base+endwith
  53. f = ProductForm(self.product)
  54. self.assertTrue(f.is_valid(),msg='error when image_url endwith '+endwith)
  55. for endwith in bads:
  56. self.product['image_url'] = url_base+endwith
  57. f = ProductForm(self.product)
  58. self.assertFalse(f.is_valid(),msg='error when image_url endwith '+endwith)
  59. self.product['image_url'] = 'http://google.com/logo.png'
  60. ###   titile必须唯一
  61. def test_title_unique(self):
  62. self.product['title'] = 'My Book Title'
  63. f = ProductForm(self.product)
  64. self.assertFalse(f.is_valid())
  65. 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的相关部分如下:

 

[python] view plaincopy
  1. def clean_image_url(self):
  2. url = self.cleaned_data['image_url']
  3. ifnot endsWith(url.lower(), '.jpg', '.png', '.gif'):
  4. raise forms.ValidationError('图片格式必须为jpg、png或gif')
  5. 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。

posted @ 2012-02-19 22:42  心内求法  阅读(10121)  评论(4编辑  收藏  举报