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 plain copy
  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代碼如下:

 

[ruby] view plain copy
  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 plain copy
  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 plain copy
  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。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM