5. Lineamientos para la implementación de la NTP-ISO 2451
5.1. Tabla para el cumplimiento de las especificaciones y requisitos
5.1.4. Otros requisitos de calidad
If we try to run our tests we will get a bunch of errors because the tests also need to be con-
verted to Python 3. Running 2to3 on our tests directory - 2to3 tests - will give us the
following errors.
1 RefactoringTool: Skipping implicit fixer: buffer 2 RefactoringTool: Skipping implicit fixer: idioms 3 RefactoringTool: Skipping implicit fixer: set_literal 4 RefactoringTool: Skipping implicit fixer: ws_comma
5 RefactoringTool: No changes to tests/contact/testContactModels.py 6 RefactoringTool: No changes to tests/main/testMainPageView.py 7 RefactoringTool: No changes to tests/payments/testCustomer.py 8 RefactoringTool: Refactored tests/payments/testForms.py
9 --- tests/payments/testForms.py (original) 10 +++ tests/payments/testForms.py (refactored)
11 @@ -29,9 +29,9 @@
12 def test_signin_form_data_validation_for_invalid_data(self): 13 invalid_data_list = [
14 {'data': {'email': '[email protected]'},
15 - 'error': ('password', [u'This field is required.'])}, 16 + 'error': ('password', ['This field is required.'])}, 17 {'data': {'password': '1234'},
18 - 'error': ('email', [u'This field is required.'])} 19 + 'error': ('email', ['This field is required.'])}
20 ]
21
22 for invalid_data in invalid_data_list: 23 @@ -78,14 +78,14 @@
24 'data': {'last_4_digits': '123'},
25 'error': (
26 'last_4_digits',
27 - [u'Ensure this value has at least 4 characters (it has 3).']
28 + ['Ensure this value has at least 4 characters (it has 3).'] 29 ) 30 }, 31 { 32 'data': {'last_4_digits': '12345'}, 33 'error': ( 34 'last_4_digits',
35 - [u'Ensure this value has at most 4 characters (it has 5).']
36 + ['Ensure this value has at most 4 characters (it has 5).']
37 )
38 }
39 ]
40 RefactoringTool: No changes to tests/payments/testUserModel.py 41 RefactoringTool: Refactored tests/payments/testviews.py
42 --- tests/payments/testviews.py (original) 43 +++ tests/payments/testviews.py (refactored) 44 @@ -80,11 +80,11 @@ 45 'register.html', 46 { 47 'form': UserForm(), 48 - 'months': range(1, 12), 49 + 'months': list(range(1, 12)), 50 'publishable': settings.STRIPE_PUBLISHABLE, 51 'soon': soon(), 52 'user': None, 53 - 'years': range(2011, 2036), 54 + 'years': list(range(2011, 2036)), 55 } 56 ) 57 ViewTesterMixin.setupViewTester(
58 RefactoringTool: Files that need to be modified: 59 RefactoringTool: tests/contact/testContactModels.py
60 RefactoringTool: tests/main/testMainPageView.py 61 RefactoringTool: tests/payments/testCustomer.py 62 RefactoringTool: tests/payments/testForms.py 63 RefactoringTool: tests/payments/testUserModel.py 64 RefactoringTool: tests/payments/testviews.py
Looking through the output we can see the same basic errors that we had in our main code:
1. Includeubefore each string literal,
2. Convert the generator returned byrange()to alist, and
3. Callprintas a function.
Applying similar fixes as we did above will fix these, and then we should be able to run our tests and have them pass. To make sure after you have completed all your changes, type the following from the terminal:
1 $ cd django_ecommerce
2 $ ./manage.py test ../tests
Whoa! Errors all over the place.
1 Creating test database for alias 'default'... 2 F...F... 3 . 4 ...F. 5 ====================================================================== 6 FAIL: test_contactform_str_returns_email (tests.contact.testContactModels.UserModelTest) 7 --- 8 Traceback (most recent call last):
9 File "../testContactModels.py", line 23, in test_contactform_str_returns_email
10 self.assertEquals("[email protected]", str(self.firstUser)) 11 AssertionError: '[email protected]' != 'ContactForm object' 12 - [email protected] 13 + ContactForm object 14 15 16 ====================================================================== 17 FAIL: test_registering_new_user_returns_succesfully (tests.payments.testviews.RegisterPageTests)
18 --- 19 Traceback (most recent call last):
20 File "../py3/lib/python3.4/site-packages/mock.py", line 1201, in patched
21 return func(*args, **keywargs) 22 File "../testviews.py", line 137, in
test_registering_new_user_returns_succesfully 23 self.assertEquals(resp.content, "") 24 AssertionError: b'' != '' 25 26 ====================================================================== 27 FAIL: test_returns_correct_html (tests.payments.testviews.SignOutPageTests) 28 --- 29 Traceback (most recent call last):
30 File "../testviews.py", line 36, in test_returns_correct_html 31 self.assertEquals(resp.content, self.expected_html) 32 AssertionError: b'' != '' 33 34 --- 35 Ran 29 tests in 0.297s 36 37 FAILED (failures=3)
38 Destroying test database for alias 'default'...
As it turns out, 2to3 can’t catch everything, so we need to go in and fix the issues. Let’s deal with them one at a time.
First error
1 ====================================================================== 2 FAIL: test_contactform_str_returns_email
(tests.contact.testContactModels.UserModelTest)
3 --- 4 Traceback (most recent call last):
5 File
"/Users/michaelherman/Documents/repos/realpython/book3-exercises/_chapters/chp11/tests/contact/testContactModels.py", line 23, in test_contactform_str_returns_email
6 self.assertEquals("[email protected]", str(self.firstUser)) 7 AssertionError: '[email protected]' != 'ContactForm object'
8 - [email protected] 9 + ContactForm object
It appears that callingstrand passing ourContactFormno longer returns the user’s name.
If we look atcontact/models.py/ContactFormwe can see the following function:
1 def __unicode__(self): 2 return self.email
In Python 3 the unicode() built-in function has gone away and str() always returns a
unicode string. Therefore the__unicode__()function is just ignored.
Change the name of the function to fix this issue:
1 def __str__(self): 2 return self.email
This and various other Python 3-related errata for Django can be foundhere.
Run the tests again. A few left, but lucky for us they all have the same solution
Unicode vs Bytestring errors
They basically all look like the following:
1 FAIL: test_registering_new_user_returns_succesfully (tests.payments.testviews.RegisterPageTests)
2 --- 3 Traceback (most recent call last):
4 File "/py3/lib/python3.4/site-packages/mock.py", line 1201, in patched
5 return func(*args, **keywargs) 6 File "../testviews.py", line 137, in
test_registering_new_user_returns_succesfully 7 self.assertEquals(resp.content, "")
8 AssertionError: b'' != ''
In Python 2 a bytestring (b'some_string' ) is effectively the same as a unicode string (
u'some_string') as long as ‘some_string’ only contains ASCII data. However in Python 3 bytestrings should be used only for binary data or when you actually want to get at the bits
and bytes. In Python 3b'some_string'!=u’some_string’‘.
BUT, and the big but here, is that according toPEP 3333, input and output streams are
alwaysbyteobjects. What isresponse.content? It’s a stream. Thus, it should be a byte
Django’s recommendedsolutionfor this is to useassertContains()orassertNotContains().
UnfortunatelyassertContainsdoesn’t handle redirects - e.g., a status_code of 302. And
from our errors we know it’s the redirects that are causing the problems. The solu-
tion then is to change the setUpClass method for the classes that test for redirects in
test/payments/testViewsin ourSignOutPageTestclass:
1 class SignOutPageTests(TestCase, ViewTesterMixin): 2 3 @classmethod 4 def setUpClass(cls): 5 ViewTesterMixin.setupViewTester( 6 '/sign_out', 7 sign_out,
8 b"", # a redirect will return an empty bytestring
9 status_code=302,
10 session={"user": "dummy"},
11 )
Here, we just changed the third argument from "" to b"", because response.context
is now a bytestring, so our expected_html must also be a bytestring. The last error in
tests/payments/testviews/in thetest_registering_new_user_returns_succesfully
test is the same type of bytestring error, with the same type of fix: Update:
python self.assertEqual(resp.content, b)
To:
1 self.assertEqual(resp.content, b"")
Same story as before: We need to make sure our string types match. And with that, we can rerun our tests and they all pass.
1 Creating test database for alias 'default'... 2 ... 3 . 4 ... 5 --- 6 Ran 29 tests in 0.430s 7 8 OK
Success!
So now our upgrade to Python 3 is complete. We can commit our changes, and merge back into the master branch. We are now ready to move forward with Django 1.7 and Python 3. Awesome.
Python 3 Changes Things Slightly
Before moving on with the rest of the chapter, have a look around the directory tree for the
project. Notice anything different? You should now have a bunch of__pycache__directo-
ries. Let’s look at the contents of one of them:
1 $ ls -al django_ecommerce/django_ecommerce/__pycache__ 2 total 24
3 drwxr-xr-x 5 michaelherman staff 170 Jul 25 19:12 . 4 drwxr-xr-x 11 michaelherman staff 374 Jul 25 19:12 .. 5 -rw-r--r-- 1 michaelherman staff 208 Jul 25 19:12
__init__.cpython-34.pyc
6 -rw-r--r-- 1 michaelherman staff 2690 Jul 25 19:12 settings.cpython-34.pyc
7 -rw-r--r-- 1 michaelherman staff 852 Jul 25 19:12 urls.cpython-34.pyc
There are a few things to note about the directory structure:
1. Each of these files is a .pyc. No longer are pyc files littered throughout your project;
with Python 3 they are all kept in the appropriate__pycache__directory. This is nice
as it cleans things up a bit and un-clutters your code directory.
2. Further,.pycfiles are now in the format<file_name>.<vm_name>-<python_version>.pyc.
This allow for storing multiple.pycfiles; if you’re testing against Python 3.4 and 2.7,
for example, you don’t have to regenerate your.pycfiles each time you test against a
different environment. Also each VM (jython, pypy, etc.) can store its own.pycfiles
so you can run against multiple VMs without regenerating.pycfiles as well. This will
come in handy later when we look at running multiple test configurations with Travis CI.
All in all the__pycache__directory provides a cleaner, more efficient way of handling mul-
tiple versions of Python and multiple VMs for projects that need to do that. For projects that
don’t… well, at least it gets the.pycfiles out of your code directories.
As said before, there are a ton of new features in Python 3 and we will see several of them as we progress throughout the course. We’ve seen a few of the features already during our upgrade. Some may seem strange at first; most of them can be thought of as cleaning up the Python API and making things clearer. While it may take a bit to get used to the subtle changes in Python 3, it’s worth it. So stick with it.