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.
To use unittest
,
you write a separate Python file that defines a series of classes,
where each class is derived from the unittest.TestCase
class,
and has a test()
method that uses assert
to verify something.
Then you call unittest.main()
.
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 samesetUp()
method without you having to write the setup code twice. - There are many more ways to use
unittest
if you want to do fancier things. For now, we're sticking to the straightforward way.
Q1.
Find your partner for the last homework assignment.
Pick just one or two tests from your test suite,
and convert them over to use unittest
.
The result should be a script that imports both unittest
and the appropriate date module,
then defines a class or two derived from unittest.TestCase
,
and finally calls unittest.main()
.
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 unittest
style
when you submit the tests for your projects.
2. XML
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 xml
.
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.
The xml
package contains a small implementation
of the "XML Document Object Model" (the "DOM") in the module
xml.dom.minidom
.
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[0] >>> n.childNodes [<DOM Text node "ick">, <DOM Element: eggs at 136658772>] >>> n.childNodes[0] <DOM Text node "ick"> >>> n.childNodes[0].data u'ick' >>> n.childNodes[1] <DOM Element: eggs at 136658772> >>> n.childNodes[1].childNodes [<DOM Text node "yum">] >>> n.childNodes[1].childNodes[0].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
using the getElementsByTagName()
method,
which searches an entire subtree.
Text can be extracted from text nodes using the data
attribute.
For nodes representing tags, the tag name is available
in the tagName
attribute.
The tag attributes can be obtained by looking at the node's
attributes
attribute,
which retrieves something similar to a dictionary:
>>> enode = _[0] >>> 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' >>>
The little 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.
The parseString()
routine
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
by urllib.urlopen
) using xml.dom.minidom.parse()
.
That's probably what you will want to use for the following exercise.
Q2.
The URL http://slashdot.org/slashdot.rss
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.
Using urllib
and xml.dom.minidom
together,
parse the Slashdot XML into a document object.
Then use getElementsByTagName
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.
3. Tkinter
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
ssh crit.org
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
the config()
method.
b.config(text='Wuggeda wuggeda wuggeda doo-wop wah.') b.config(foreground='yellow') b.config(background='blue')
Calling 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. b.config(borderwidth=10)
),
or as keyword arguments to the Button()
constructor.
Some of them require a little explanation:
- The value of
state
may 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
relief
may 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.
Try it:
def pressed(): print 'Eek!' b.config(command=pressed)
You might have noticed that the button didn't appear
until you called its pack()
method.
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 side
argument:
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 Frame
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.
f.config(borderwidth=2) f.config(relief='ridge')
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 __init__
constructor
of TicBoard
.
If you have any questions about these exercises or the assignment, feel free to send me e-mail at bczestyca.