unit tests in Python
Let's review the unit test methods in Python.
Here is a simple example of a Python unit test:
If we develop a division function, some students may find it very simple, the code is this:
def division_funtion (x, y):
return x/y
But it is not right to write this way, some students can be tested under the code:
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)
But this run to get the results, each time you have to calculate to check again, very inconvenient, Python has unittest module, can be easily tested, details can be the last link to the article, crossing Web documents detailed introduction.
The following is a simple example:
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()
I have simply written three test examples (not necessarily comprehensive, just a demonstration, for example, without considering the divisor is a 0 case), after the run found:
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)
Sweat! Found no, unexpectedly two have failed, the test found:
4.2 divided by 3 equals 1.4000000000000001 is not equal to expected value 1.4
9 divided by 4 equals 2, not equal to the desired 2.25
Here's what we're going to do to fix these problems and run the tests again until the error is run.
For example, according to the actual situation, suppose we only need to retain to 6 decimal places, we can change this:
def division_funtion (x, y): return Round (float (x)/Y, 6)
Running again will not be the error:
...----------------------------------------------------------------------Ran 3 tests in 0.000s
Ok
Unit Tests in Django
Early unit Testing (unittest) is a good practice, and extreme situations even emphasize "test-first". Now that we have the first model class and the form class, it's time to start writing the test code.
Django supports Python's unit test and text test (Doc test), where we focus on unit testing. There is not much elaboration on the theory of unit testing, assuming you are familiar with the following concepts: test suite, test case, test/test action, test data, assert, and so on.
In unit testing, Django inherits Python's unittest.testcase to implement its own django.test.TestCase, and writing test cases usually starts here. The test code is usually located in the app's tests.py file (which can also be written in models.py, but I don't recommend it). In the Django-generated Depotapp, this file is already included and contains a sample of the test case:
depot/depotapp/tests.py
From django.test import Testcaseclass simpletest (TestCase):d ef test_basic_addition (self): "", Tests that 1 + 1 always equal s 2. "" " Self.assertequal (1 + 1, 2)
There are several ways you can run unit tests:
- Python manage.py Test: Execute all test Cases
- Python manage.py test app_name, execute all test cases for the app
- Python manage.py test app_name.case_name: Executing the specified test case
The example provided above is executed in the third way, with the following results:
$ 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'...
You may be primarily to the output information that includes operations to create and delete databases. To avoid the impact of the test data, the test process uses a separate database, and for details on how to specify the test database, consult the Django documentation. In our case, because of the SQLite database, Django will default to the in-memory database for testing.
Let's write the test case below. In "Agile Web development with Rails 4th", section 7.2, the final implementation of the Producttest code is as follows:
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
The content of the product test includes:
1.title,description,price,image_url cannot be empty;
2. Price must be greater than 0;
3. Image_url must end with jpg,png,jpg, and not sensitive to capitalization;
4. Titile must be unique;
Let's do these tests in Django. Because Productform contains model checksums and form validation rules, it is easy to implement these tests using 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'
Then run the Python manage.py test Depotapp. Producttest. As expected, the test did not pass:
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'...
Because we did not consider that the Image_url image extension might be capitalized. The relevant parts of modifying Productform are as follows:
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
Then run the test again:
$ 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'...
The test passed, and through the unit tests, we found and solved a bug.