1. Unit Testing
Now that you've had a little experience writing tests,
we'll introduce you to a test harness called
unittest that make tests more convenient.
you write a separate Python file that defines a series of classes,
where each class is derived from the
and has a
test() method that uses
to verify something.
Then you call
This will automatically run all of your tests
and produce a report on any failures.
A simple example should help to make this more clear:
from unittest import TestCase, main class TestAddition(TestCase): def test(self): assert 1 + 1 == 2, "addition produced an incorrect result" class TestSubtraction(TestCase): def test(self): assert 5 - 4 == 2, "subtraction produced an incorrect result" class TestMultiplication(TestCase): def test(self): assert 2 * 2 == 4, "multiplication produced an incorrect result" class TestDivision(TestCase): def test(self): assert 3 / 0 == 1, "division produced an incorrect result" main()
Here's what you'll see if you run the above script:
.E.F ====================================================================== ERROR: test (__main__.TestDivision) ---------------------------------------------------------------------- Traceback (most recent call last): File "arith.py", line 17, in test assert 3 / 0 == 1, "division produced an incorrect result" ZeroDivisionError: integer division or modulo by zero ====================================================================== FAIL: test (__main__.TestSubtraction) ---------------------------------------------------------------------- Traceback (most recent call last): File "arith.py", line 9, in test assert 5 - 4 == 2, "subtraction produced an incorrect result" AssertionError: subtraction produced an incorrect result ---------------------------------------------------------------------- Ran 4 tests in 0.003s FAILED (failures=1, errors=1)
The first line of output is a summary of the tests that succeeded or failed. It shows a dot for success, then an E for error, then a dot for success, then an F for failure, corresponding to the four tests in alphabetical order (TestAddition, then TestDivision, then TestMultiplication, then TestSubtraction). Then there are more detailed reports on any tests that didn't succeed.
Notice the distinction between a failure (an assertion failed, which generally means that some expression produced an unexpected result) and an error (an error occurred while trying to perform the test).
A couple of notes about this:
- You can put as many assertions as you like in a single test method. The ability to group the tests under classes is provided just to help you organize your tests.
- If you want some preparations to take place
before the actual test, you can put the preparatory work
into a method called
setUp(). Then, you can derive test classes from other test classes, so they can share the same
setUp()method without you having to write the setup code twice.
- There are many more ways to use
unittestif you want to do fancier things. For now, we're sticking to the straightforward way.
Find your partner for the last homework assignment.
Pick just one or two tests from your test suite,
and convert them over to use
The result should be a script that imports both
and the appropriate date module,
then defines a class or two derived from
and finally calls
Call me over to show me your work.
Do not work on Q1 past 5:10 pm. If it's after 5:10 pm, move on.
I'll be asking you to use the
when you submit the tests for your projects.
Here we'll just do a brief introduction to the XML facilities in Python. Again, you can go much deeper into this if you want, but this might help you get started.
Python provides a package called
A package is a collection of modules.
We'll talk more about packages later,
but the main point is just that you can use dots to form a path
to a module within a package.
xml package contains a small implementation
of the "XML Document Object Model" (the "DOM") in the module
This module can parse XML into a corresponding tree-like data structure in memory. Here's an example:
>>> doc = '<spam>ick<eggs type="scrambled">yum</eggs></spam>' >>> top = xml.dom.minidom.parseString(doc) >>> top.childNodes [<DOM Element: spam at 136293436>] >>> n = top.childNodes >>> n.childNodes [<DOM Text node "ick">, <DOM Element: eggs at 136658772>] >>> n.childNodes <DOM Text node "ick"> >>> n.childNodes.data u'ick' >>> n.childNodes <DOM Element: eggs at 136658772> >>> n.childNodes.childNodes [<DOM Text node "yum">] >>> n.childNodes.childNodes.data u'yum' >>> n.getElementsByTagName('spam')  >>> top.getElementsByTagName('spam') [<DOM Element: spam at 136293436>] >>> top.getElementsByTagName('eggs') [<DOM Element: eggs at 136658772>] >>>
As you can see, each node in the tree has an attribute called
childNodes that lists its children.
You can walk your way down to the nodes a level at a time,
by asking successive nodes for their children,
or you can collect all the nodes for a particular tag
which searches an entire subtree.
Text can be extracted from text nodes using the
For nodes representing tags, the tag name is available
The tag attributes can be obtained by looking at the node's
which retrieves something similar to a dictionary:
>>> enode = _ >>> enode.attributes <xml.dom.minidom.NamedNodeMap instance at 0x8252e74> >>> enode.attributes.keys() [u'type'] >>> enode.attributes['type'] <xml.dom.minidom.Attr instance at 0x825421c> >>> enode.attributes['type'].value u'scrambled' >>>
u that appears
in front of the strings indicates that the strings use Unicode,
a standard for encoding strings containing characters
from all the world's languages.
XML is officially specified to use Unicode for all of its text.
is only good if you have an entire XML document in a string.
Alternatively, you can parse an open stream (such as the one produced
That's probably what you will want to use for the following exercise.
provides an XML document describing the current headlines at Slashdot.
Have a look at this document to see what tags are used to mark the headlines.
parse the Slashdot XML into a document object.
to extract the headline titles as a list of strings.
(I'm not asking you to write a program;
just play around in the interpreter
until you've extracted the titles, and send me a transcript.)
Do not work on Q2 past 5:30 pm. If it's after 5:30 pm, move on.
This is just a brief look at Tkinter, the GUI toolkit that comes bundled with Python. We'll look at this more in depth next week, but i wanted to give you a chance to play with it first.
The machines in EECS do not have Tkinter installed. So accounts have been created for you on another machine that does have Python with Tkinter. To get to that account, just type
You may be asked whether you want to accept a new host key. Say "yes" and you should be automatically logged in to the other account.
Try the following.
from Tkinter import * b = Button(text='Press me!') b.pack()
This should get you a nice little button.
You can configure many properties of the button using
b.config(text='Wuggeda wuggeda wuggeda doo-wop wah.') b.config(foreground='yellow') b.config(background='blue')
b.config() by itself produces a dictionary
of all the configuration options.
It's big. Here's a list of the keys:
>>> b.config().keys() ['bd', 'foreground', 'bg', 'highlightthickness', 'text', 'image', 'height', 'borderwidth', 'background', 'fg', 'pady', 'padx', 'bitmap', 'activeforeground', 'activebackground', 'highlightbackground', 'disabledforeground', 'wraplength', 'font', 'width', 'default', 'underline', 'cursor', 'textvariable', 'state', 'highlightcolor', 'command', 'relief', 'anchor', 'takefocus', 'justify'] >>>
All of these are acceptable both as keyword arguments to the
config() method (e.g.
or as keyword arguments to the
Some of them require a little explanation:
- The value of
statemay be 'active', 'disabled', or 'normal'. The 'disabled' state causes a button to be greyed out and prevents it from being pressed. The 'active' state highlights the button (this normally happens when the mouse pointer passes over the button).
- The value of
reliefmay be 'raised', 'sunken', 'ridge', 'groove', or 'flat'. Try these out to see what they do.
Experiment with the various options and see what you can do.
This can make some pretty buttons, but pressing the button
doesn't do anything yet.
That's what the
command option is for:
you can set the
command to any Python function,
and the function will get called when the button is pressed.
def pressed(): print 'Eek!' b.config(command=pressed)
You might have noticed that the button didn't appear
until you called its
That method invokes the packer,
which manages the layout of various items in the window.
The packer arranges things by placing them along one side of
the available space.
You can choose a side using the
b2 = Button(text='hello') b2.pack(side='left') b3 = Button(text='there') b3.pack(side='right')
By default, everything is packed along the top side, which means that items will appear in a column from top to bottom.
To achieve more interesting layouts,
you can pack widgets inside a
and then pack the
Frame inside the window.
f = Frame() b4 = Button(f, text='spam') b4.pack(side='bottom') b5 = Button(f, text='eggs') b5.pack(side='top') f.pack(side='right')
Frames have no border by default, but you can make the edge of a frame visible if you set its borderwidth to a positive number.
Play around with this for a bit to see how the layout manager works.
Here's the source to the tic-tac-toe program.
Do you see how it arranges the squares into a 3-by-3 grid?
Everything is arranged in the
If you have any questions about these exercises or the assignment, feel free to send me e-mail at bczestyca.