Using tests to get the code right

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.

Try it for yourself – writing and debugging code

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:

  1. Write code that ought to satisfy at least some of your tests.

    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
  2. If there are errors in the code you've already written, use the test output to help you locate and identity them. Once you understand the bugs, try to fix them and then go back to step 2.
  3. Once you've fixed all the errors in the code you've written, and if your project isn't complete, choose some new tests to concentrate on and go back to step 1.

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.141.27