Using Test Driven Development in a Computer Science Classroom:
A First Experience
Jeffrey Elkner
February 7, 2003
Background
In the five years that I have been teaching computer programming to high school students I have always had one goal in mind: to demystify computers and to make their workings and capabilities understandable to as broad a cross section of our student population as possible. Given the ever increasing importance of the electronic digital computer to almost every aspect of our lives, I see this understanding as a prerequisite to full democratic participation in the creation of our collective future.
Toward achieving this goal I have embarked on three seperate projects:
- Switching to the Linux operating system in the lab in which I teach and embracing the use of free software.
- Adopting Python as the programming language in our introductory courses.
- Incorporating principals from eXtreme Programming in the teaching of computer programming.
The Linux lab was set up durning the summer of 1997 and has been in use in the Yorktown High School computer science program for five and a half years now. We began using Python in Fall of 2000, and we are mid way through the third year of learning programming with Python. The XP project is just getting off the ground now.
The common thread that runs through each of these projects is the ability of collaboration with computer professionals using the Internet for communication to greatly enhance learning opportunities for high school computer science students and their teachers.
Relying only on the good will and support of the free software, Python, and now XP communities, I have been able to transform our computer science curriculum. While I have often recieved encouragement and support, both material and moral, from Yorktown parents and from my principal, I have embarked on this curriculum development project on my own initiative, without any official instruction from the school system to do so.
Why XP?
My desire to introduce extreme programming concepts into the classroom stems from the goal that drives me in the first place: to be able to bring students with diverse backgrounds, skills, and interests to a firm understanding of what computers are and what they can do, and to use the practice of computer programming to help students develop their logical thinking.
The "how" of the computer programming process is sorely lacking in computer programming texts. These books focus extensively on the syntax and symantics of the programming language in question, but say almost nothing on how to go about writing a program.
For strong students naturally drawn to programming this is not a problem. These students are able to write programs on their own after seeing a few examples of programs written by others. I do not understand how they are able to do this, but they somehow grasp the rules of the language just from seeing it. They are then able to make original programs because they know the rules of the game just from watching it played.
For students who don't "get it", on the other hand, there is little help available. Often the attitude seems similar to that expressed toward the learning of mathematics. You are either a programmer or you are not, and if you are not, you should find something else to do. I was desperately seeking an effective way out of this situation. I wanted to be able to bring an understanding of computer programming to a much broader audience then I was currently reaching.
I first became aware of extreme programming at the annual Python conferences I attended. XP is big in the Python community, and there were active XPers at each of the conferences. I was attracted immediately to XP because it represented an analysis of the programming process. Understanding this process from the point of view of the programmer was clearly the key to effectly teaching more students to be successful at programming.
The more I learned about XP, the more I was attracted to it. The four XP principles of communication, simplicity, feedback, and courage seemed tailor made for the classroom. While I did not see direct relevance to a learning environment in all of the XP practices, such practices as small releases, metaphor, simple design, testing, refactoring, pair programming, collective ownership, and coding standards all offered value to my students.
Along Came A Mentor
Last year I rearranged the room and put students together, two to a computer. This was not pair programming at this point, but it was my first, awkward step toward incorporating something of what I had learned about XP into my classroom. What I was really hoping for was a mentor, someone who was interested in seeing XP used in an educational setting who would take the time to guide me and some of my students into using it.
My hope was answered in the person of Steve Howell. Steve is a professional programmer who had been mentoring two of my top students on a project to create a Python implementation of Karel the Robot. That project has come along nicely, and is available at http://pykarel.sourceforge.net. Steve lived in Seattle, so all this mentoring was going on through the net.
That changed last summer when Steve first visited, and then moved into the Washington, DC area. We began to schedule code fests at the school where Steve visited Yorktown and worked with the students directly on the project. Steve is a master teacher, and watching him work with my students taught me two important things: I learned how pair programming was supposed to work, and I was introduced to a simple testing framework that I felt I could use with my classes to do test first programming.
This year my students have been working in a way that could legitimately be called pair programming. We have discussed the goals and concepts behind having two people actively programming together, with one "driving" while the other "navigates". I keep watch over the process and ask pairs to let the other partner drive when I see things are not balanced. The students keep daily journals and I have read many encouraging entries reflecting on the process.
A Testing Framework
Ever since I had decided to try out test driven development in my classroom, the biggest problem I faced was finding a testing framework suitable for our environment. All the unit test frameworks I had seen were way to complicated for use in our introductory class. We are still doing simple, proceedural programming and the goal is to teach students to write effective functions. A testing framework which required the creation of a class would add more confusion than clarity, defeating the goal of the project.
Steve came to the rescue here as well. He used a simple
assertEquals
function in his testing and told me he thought that
was all that was needed. It was a revelation for me, because it allowed me to
get a handle on the test first process without being frustrated trying to
figure out how to write and run the tests.
Below is the assertEquals
that I am now using in class. It is a
modified version of the one I watched Steve using:
import sys def assertEquals(a, b, msg = ''): if a != b: print "\nValues not equal for test %s\nThing1: %s\nThing2: %s\n" % (msg, a, b) sys.exit(1) else: print ".",
This function has been put on our Linux terminal server in the directory:
/usr/lib/python2.2/site-packages/TEST
Students can now import it from anywhere on the system.
Setting the Stage
In preperation for our first test driven development project I gave students the following assignment:
Research Test Driven Development (or Test First Design) and write up a description of what it is and why some programmers are using it. Your discussion should include who Kent Beck and Ward Cunningham are and what they did, as well as a brief mention of some of the other principles of eXtreme Programming (XP). Be sure to describe the four core values and the twelve practices of XP.
I was encouraged by the student's reaction to this assignment. Almost all of them found good background information and put it on their websites. They seemed genuinely interested in ideas of XP. I overheard one student telling his partner, with apparent enthusiasm, "there is a philosophy behind this..."
On Your Mark, Get Set, Test!
In introducing test driven design I decided to use as a programming problem the Pig Latin translator problem written by fellow Python teacher, Tim Wilson. The basic idea of the problem is to create an interactive translator that prompts the user for an English sentence or phrase and then converts the input into Pig Latin.
The sample session looks like this:
--> Stop Opstay --> No littering Onay itteringlay --> No shirts, no shoes, no service Onay irtsshay, onay eosshay, onay ervicesay --> No persons under 14 admitted Onay ersonspay underay 14 admitteday --> Hey buddy, get away from my car! Eyhay uddybay, etgay awayay omfray ymay arcay! --> ^D
This problem is particularly easy to use with test driven development. To
get things started, I demonstrated using test driven development on the
sub problem of writing a function pigPrefix(word)
which takes
an English word as an argument and returns the Pig Latin prefix of that
word.
The demonstration went something like this:
- Open up the three windows on your desktop: a unix shell and two
text editor windows.
- At the unix prompt, create a directory named PLT to hold the
files for this project. Then change to the new directory.
- In the first editor window, open a file in the PLT directory
named pigLatinTrans.py. We are going to begin with the function
pigPrefix
, and we want to start with a failing test, but we do not want it to fail to import, so let's create a do nothing function:def pigPrefix(word): pass
- Now in the other text editor window create a file in the PLT
directory named TESTpigLatinTrans.py that will hold our test suite. Put the following import statements into the file:
from TEST import assertEquals from pigLatinTrans import pigPrefix
- We are now ready to create our first test, which looks like this:
assertEquals(pigPrefix('pig'), 'p', 'pigPrefix - pig')
Now that we have our first test in place, run it at the command line:$ python TESTpigLatinTrans.py
Which will produce the following output:Values not equal for test pigPrefix - pig Thing1: None Thing2: p
A failing test, just like we wanted. I explained here that the process is to always begin with a failing test. If you don't have a failing test, then there is nothing that needs to be done in test driven development. I also took this opportunity to discuss the None type, which we had not previously discussed.
I then stated that the thing to do now was fix the function so that it passes the test, remembering the XP principle of doing the simplest thing first. In each of the two classes who took part in this lesson, a student volunteered what I was hoping for, and the function became:def pigPrefix(word): return 'p'
I told the class that my friend Steve Howell refers to this kind of a solution as "sliming". I added that I had checked with the XP community and found out this is a Steve Howellism, and not a general XP term, but that I liked the expression and would continue to use it in class.
With the modified function saved, we then ran the test again, and this time it passed:$ python TESTpigLatinTrans.py .
The dot that indicated a successful test appeared.
- Now it was time to add another test, and the test suite became:
assertEquals(pigPrefix('pig'), 'p', 'pigPrefix - pig') assertEquals(pigPrefix('computer'), 'c', 'pigPrefix - computer')
The discussion about how to pass this new test was very interesting. One student suggested that we could use anif...else
statement, which would be a continuation of the sliming approach we used before. We could, I responded, but would that really be the simplest thing to do? After thinking about it for a few seconds another student suggested the following:def pigPrefix(word): return word[0]
This solution is interesting on a number of levels. It is definitely the simplest thing that will work, but it is also surprisingly powerful, because our little function will now work for the majority of words that exist.
- A few more iterations and we had the following test suite:
assertEquals(pigPrefix('pig'), 'p', 'pigPrefix') assertEquals(pigPrefix('computer'), 'c', 'pigPrefix') assertEquals(pigPrefix('spam'), 'sp', 'pigPrefix') assertEquals(pigPrefix('through'), 'thr', 'pigPrefix') assertEquals(pigPrefix('oink'), '', 'pigPrefix') assertEquals(pigPrefix('Yorktown'), '', 'pigPrefix') assertEquals(pigPrefix('I'), '', 'pigPrefix') assertEquals(pigPrefix('NFL'), 'NFL', 'pigPrefix') assertEquals(pigPrefix('123'), '123', 'pigPrefix')
And the following solution to the problem:def pigPrefix(word): vowels = 'aeiouy' index = 0 while index < len(word) and word[index].lower() not in vowels: index = index + 1 return word[:index]
Now It's Your Turn
The next time class met I told them it was their turn to use what I had
shown them to write two new function on their own: pigStem(word)
,
which returns the part of the word after the prefix, and
makePigWord(word)
, which takes an English word and returns a
Pig Latin translation of it, with a capitalized first letter or trailing
punctuation mark preserved.
I had scheduled this part of the project to be on our double period block days so that students would have the time they needed to get the process down. My first period class would have a double period on Wednesday, and my second period would have a double period on Thursday. This gave me the added advantage of time to make adjustments before Thursday if things didn't go well on Wednesday.
As it turned out, I did not need to make any adjustments. Each of these classes was the kind of class that teachers dream of. All the students where actively engaged in programming for the entire ninty minutes of class. Several pairs kept working after the bell rang, working into their break. I joked with one student who told me she had cried earlier on, but now she felt happy because she had made her function pass the tests. "You laughed, you cried...", I said to her, "now in what other class can you have such an experience?!"
A New Way To Communicate
In reflecting afterwards on what made the classes so succesful I realized that test driven development had proven useful in a way that I had not anticipated. I had started this experiment with the premise that it would help students focus on what they were supposed to do, and give them a better understanding of the problem they were trying to solve. In frequent discussions with students in class who were having difficulty getting started solving a problem, I noticed that they often did not have a real grasp of what they were supposed to do. By having to write tests before they began they would be forced to think about the expected input and output of the function they were writing, helping them to bridge this very important gap in their understanding.
Indeed, this happened. More students were able to successfully complete the task than usual, and the lost expressions accompanied by "I don't know what to do", that were so common on earlier projects practically disappeared.
Yet what was even more exciting was an unforeseen benefit to test driven
development. During the second day as students were writing their tests for
the makePigWord
function they would call me over when they thought
they were finished. I would say, "let me see your tests", and then, looking
over the test suite, I would sit down, add a test they had not thought of, and
walk away with a smile. Rising to the challenge they would then fix their
code to pass this new test and call me again, and the process repeated itself
several times.
What I found novel about this experience was that a new and very effective process of communication was developing between me and my students that was as precise as it was succinct. By just typing in a one line test I communicated more effectively what I wanted them to do then several paragraphs of explaination. Judging by the atmosphere in the classroom they were as excited by the power of this new form of communication as I was.
The experience was fun and exciting to the students because it clarified for them what the programming process was all about. They could see clearly what they were supposed to do and they received immediate feedback from their tests as to whether or not they had been successful. This gave the whole process the atmosphere of a game and made it a rewarding and empowering experience.
Conclusion
After only two days of using test driven development it is certainly too early to draw any definitive conclusions, but even at this very early stage the results have been successful enough to make me glad I embarked on the experiment. If the results of this past week are any indication, using test driven development in my introductory programming class may prove to be just the key I was looking for to help all my students become more successful at programming.