All right, so that code looks fairly good. Unfortunately, Nose tells us that there are a few problems. Actually, Nose reports quite a large number of problems, but a lot of them seem to be related to a few root causes.
First, let's address the problem that, though the Activity
and Status
classes don't seem to have the exclude
methods, some of our code tries to call that method. A typical report of this problem from the Nose output looks like a traceback followed by:
AttributeError: 'Activity' object has no attribute 'exclude'
Looking at our code, we see that it is properly called excludes
. The tracebacks included in the Nose error report tell us that the problem is on line 51 of planner/data.py
, and it looks like a quick fix.
We'll just change line 51 from the following:
if task.exclude(contained) or contained.exclude(task):
to:
if task.excludes(contained) or contained.excludes(task):
and run Nose again.
Similarly, several of our tests report the following output:
NameError: name 'containeed' is not defined
This is clearly another typo. That one's on line 52 of planner/data.py
. Oops!! We'll fix that one, too, and run Nose again to see what else is wrong.
Continuing our trend of picking the low-hanging fruit first, let's clear up the problem reported as the following:
SyntaxError: unexpected EOF while parsing
This is yet another typo, this time in docs/outline.txt
. This time, it's not a problem with the code being tested, but with the test itself. It still needs to be fixed.
The problem is that, when originally entering the tests, I apparently only typed in two dots at the beginning of several lines, instead of the three that tell doctest that an expression continues onto that line.
After fixing that, things are starting to get less obvious. Let's pick on this one next:
File "docs/outline.txt", line 36, in outline.txt Failed example: duplicate_activity in schedule Expected: True Got: False
Why isn't the activity being seen as a member of the schedule? The previous example passed, which shows that the in
operator works for the activity we actually added to the schedule. The failure shows up when we try to use an equivalent activity; once we realize that, we know what we need to fix. Either our __eq__
method isn't working, or (as is the actual case) we forgot to write it.
We can fix this bug by adding the __eq__
and __ne__
methods to Task
, which will be inherited by Activity
and Status
.
def __eq__(self, other): return (self.name == other.name and self.begins == other.begins and self.ends == other.ends) def __ne__(self, other): return (self.name != other.name or self.begins != other.begins or self.ends != other.ends)
Now, two tasks that have the same name, start time, and end time will compare as equivalent even if one is a Status
and the other is an Activity
. The last isn't necessarily right, but it doesn't cause any of our tests to fail, so we'll leave it for now. If it becomes a problem later, we'll write a test that checks it, and then fix it.
What's the deal with this one?
File "docs/outline.txt", line 61, in outline.txt Failed example: schedule.add(activity2) Expected: Traceback (most recent call last): ScheduleError: "test activity 2" overlaps with "test activity 1" Got: Traceback (most recent call last): File "/usr/lib64/python3.4/doctest.py", line 1324, in __run compileflags, 1), test.globs) File "<doctest outline.txt[20]>", line 1, in <module> schedule.add(activity2) File "planner/data.py", line 62, in add raise ScheduleError(task, contained) planner.data.ScheduleError: (<test activity 2 2014-06-01T10:15:00 2014-06-01T13:30:00>, <test activity 1 2014-06-01T09:05:00 2014-06-01T12:30:00>)
Well, it looks ugly but, if you look at it, you'll see that doctest
is just complaining that the raised exception doesn't print out as expected. It's even the right exception; it's just a question of formatting.
We can fix this on line 62 of planner/data.py
, by changing the line to read:
raise ScheduleError('"{}" overlaps with "{}"'.format(task.name, contained.name))
There's one more problem with this doctest example, which is that we wrote the name of the expected exception as ScheduleError
, and that was how Python 2 printed out exceptions. Python 3 prints out exceptions with a qualified name, though, so we need to change it to planner.data.ScheduleError
on line 63 of the doctest file.
Now, if you've been following along, all of the errors should be fixed, except for some of the acceptance tests in docs/outline.txt
. Basically, these failing tests tell us that we haven't written the persistence code yet, which is true.
The basic procedure, as we've discussed before, is to write some code, then run the tests to find problems with the code, and repeat. When you happen to come across an error that isn't covered by an existing test, you need to write a new test and continue the process. Perform the following steps:
Run your tests. If you've used the tools we talked about in the previous chapters, you should be able to run everything simply by executing:
$ python3 -m nose
Enough iterations on this procedure lead you to have a complete and tested project. Of course, the real task is more difficult than simply saying "it will work" but, in the end, it will work. You will produce a codebase that you can be confident in. It will also be an easier process than it would have been without the tests.
Your project might be done, but there's still more to do on the personal scheduler. At this stage of the chapter, I haven't finished going through the writing and debugging process. It's time to do that.
18.118.141.27