Chapter 3 – Once is Not Enough

(Collects serial chapters #6–7 Once Is Not Enough and #8 Test Document.)

Yesterday left me drained. Jerry and I had solved the prime factors problem by sneaking up on it one tiny test case at a time. It was the strangest way to solve a problem that I have ever seen; but it worked better than my original solution.

I wandered aimlessly around the corridors thinking about it again and again. I don’t remember dinner, or even being in the galley. I fell asleep early and dreamed of sequences of tiny test cases.

When I reported to Jerry this morning he said:

“Good Morning Alphonse. Are you ready to start working on a real project?”

“You bet I am!” Farb, yes, I was ready! I was tired of these play examples.

“Good! We’ve got a program called SMC that compiles a finite state machine grammar into Java. Mr. C wants us to turn that program into a service delivered over the net.”

“What do you mean?” I asked.

Jerry turned to a sketch-wall and began to talk and draw at the same time.

smcr service diagram
Figure 1. SMCR service diagram

“We’re going to write two programs. One called SMCR Client, and the other called SMCR Server. The user who wants to compile a finite state machine will invoke SMCR Client with the name of the file to be compiled. SMCR Client will send that file to a special computer where SMCR Server is running. SMCR Server will run the SMC compiler and then send the resulting compiled files back to SMCR Client. SMCR Client will then write them in the user’s directory. As far as the user is concerned, it’ll be no different than using SMC directly.”

“OK, I think I understand.” I said. “It sounds pretty simple.”

“It is pretty simple.” Jerry said. “But working with sockets is always just a little interesting.”

So we sat down at a workstation and, as usual, got ready to write our first unit test. Jerry thought for a minute and then went back to the sketch-wall and drew the following diagram.

595
Figure 2. SMCR Server type hierarchy

“Here’s what I have in mind for SMCR Server.” He said. “We’ll put the socket management code in a class called SocketService. This class will catch, and manage, connections coming from the outside. When serve(port) is called, it’ll create the service socket with the given port number and start accepting connections. Whenever a connection comes in it will create a new thread and pass control to the serve(socket) method of the SocketServer interface. That way we separate socket management code from the code that performs the services we desire.”

Not knowing whether this was a good thing or not, I simply nodded. Clearly he had some reason for thinking the way he did. I just went along with it.

Next he wrote the following test.

Commit 1, TestSocketServer.java (partial)
public void testOneConnection() throws Exception {
  SocketService ss = new SocketService();
  ss.serve(999);
  connect(999);
  ss.close();
  assertEquals(1, ss.connections());
}

“What I’m doing here is called Intentional Programming.” Jerry said. I’m calling code that doesn’t yet exist. As I do so, I express my intent about what that code should look like, and how it should behave.”

“OK.” I responded. “You create the SocketService. Then you ask it to accept connections on port 999. Next it looks like you are connecting to the service you just created on port 999. Finally you close the SocketService and assert that it got one connection.”

“Right.” Jerry confirmed.

“But how do you know SocketService will need the connections method?”

“Oh, it probably doesn’t. I’m just putting it there so I can test it.”

“Isn’t that wasteful?” I queried.

Jerry looked me sternly in the eye and replied: “Nothing that makes a test easy is wasted, Alphonse. We often add methods to classes simply to make the classes easier to test.”

I didn’t like the connections() method, but I held my tongue for the moment.

We wrote just enough of the SocketService constructor, and the serve, close, and connect methods to get everything to compile. They were just empty functions. When we ran the test, it failed as expected.

Next Jerry wrote the connect method as part of the test case class.

Commit 2, TestSocketServer.java (partial)
private void connect(int port) {
  try {
    Socket s = new Socket("localhost", port);
    s.close();
  } catch (IOException e) {
    fail("could not connect");
  }
}

Running this produced the following error:

testOneConnection: could not connect

I said: “It’s failing because it can’t find port 999 anywhere, right?”

“Right!” said Jerry. “But that’s easy to fix. Here, why don’t you fix it?”

I’d never written socket code before, so I wasn’t sure what to do next. Jerry pointed me to the ServerSocket entry in the Javadocs. The examples there made it look pretty simple. So I fleshed in the SocketService methods as shown below.

Commit 3, SocketService.java
import java.net.ServerSocket;

public class SocketService {
  private ServerSocket serverSocket = null;

  public void serve(int port) throws Exception {
    serverSocket = new ServerSocket(port);
  }

  public void close() throws Exception {
    serverSocket.close();
  }

  public int connections() {
    return 0;
  }
}

Running this gave us:

testOneConnection: expected:<1> but was:<0>

“Aha!” I said. “It found port 999. Cool! Now we just need to count the connection!”

So I changed the SocketService class as follows:

Commit 4, SocketService.java
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class SocketService {
  private ServerSocket serverSocket = null;
  private int connections = 0;

  public void serve(int port) throws Exception {
    serverSocket = new ServerSocket(port);
    try {
      Socket s = serverSocket.accept();
      s.close();
      connections++;
    } catch (IOException e) {
    }
  }

  public void close() throws Exception {
    serverSocket.close();
  }

  public int connections() {
    return connections;
  }
}

But this didn’t work. It didn’t even fail. When I ran the test, the test hung.

“What’s going on?” I asked.

Jerry smiled. “See if you can figure it out, Alphonse. Trace it through.”

“OK, let’s see. The test program calls serve, which creates the socket and calls accept. Oh! accept doesn’t return until it gets a connection! And so serve never returns, and we never get to call connect.”

Jerry nodded. “So how are you going to fix this, Alphonse?”

I thought about this for a minute. I needed to call the connect function after calling accept; but when you call accept, it won’t return until you call connect. At first this sounded impossible.

“It’s not impossible, Alphonse.” said Jerry. “You just have to create a thread.”

I thought about this a little more. Yes, I could put the call to accept in a separate thread. Then I could invoke that thread and then call connect.

“I see what you mean about socket code being a little interesting.” I said. And then I made the following changes:

Commit 5, SocketService.java (partial)
private Thread serverThread = null;

public void serve(int port) throws Exception {
  serverSocket = new ServerSocket(port);
  serverThread = new Thread(new Runnable() {
    public void run() {
      try {
        Socket s = serverSocket.accept();
        s.close();
        connections++;
      } catch (IOException e) {
      }
    }
  });
  serverThread.start();
}

“Nice use of the anonymous inner class, Alphonse.” said Jerry.

“Thanks.” It felt good to get a compliment from him. “But it does make for a lot of monkey tails at the end of the function.”

“We’ll refactor that later. First, let’s run the test.”

The test ran just fine, but Jerry looked pensive, like he’d just been lied to.

“Run the test again, Alphonse.”

I happily hit the run button, and it worked again.

“Again.” he said.

I looked at him for a second to see if he was joking. Clearly he was not. His eyes were locked on the screen, as though he were hunting a dribin. So I hit the button again and saw:

testOneConnection: expected:<1> but was:<0>

“Now wait a minute!” I hollered. “That can’t be!”

“Oh, yes it can.” Jerry said. “I was expecting it.”

I repeatedly pushed the button. In ten attempts I saw three failures. Was I losing my mind? How can a program behave like that?

“How did you know, Jerry? Are you related to the oracle of Aldebran?”

“No, I’ve just written this kind of code before, and I know a little bit about what to expect. Can you work out what’s happening? Think it through carefully.”

My brain was already hurting, but I started piecing things together. I went to the sketch-wall and began to draw.

socketserver protocol sequence diagram
Figure 3. SocketServer protocol sequence diagram

When I had worked it out, I recited the scenario to Jerry. “TestSocketServer sent the serve(999) message to SocketService. SocketService created the ServerSocket and the serverThread and then returned. TestSocketServer then called connect which created the client socket. The two sockets must have found each other because we didn’t get a ‘could not connect’ error. The ServerSocket must have accepted the connection, but perhaps serverThread hadn’t had a chance to run yet. And while serverThread was blocked, the connect function closed the client socket. Then TestSocketServer sent the close message to the SocketService, which closed the serverSocket. By the time the serverThread got a chance to call the accept function, the server socket was closed.”

“I think you’re right.” Jerry said. “The two events — accept and close — are asynchronous, and the system is sensitive to their order. We call that a race condition. We have to make sure we always win the race.”

We decided to test my hypothesis by putting a print statement in the catch block after the accept call. Sure enough, three times in ten, we saw the message.

“So how can we get our unit test to run?” Jerry asked me.

“It seems to me that the test can’t just open the client socket and then immediately close it.” I replied. “It needs to wait for the accept.”

“We could just wait for 100ms real time before closing the client socket.” he said. “Yeah, that would probably work.” I replied. “But it’s a bit ugly.”

“Let’s see if we can get it to work, and then we’ll consider refactoring it.”

So I made the following changes to the connect method:

Commit 6, SocketService.java (partial)
private void connect(int port) {
  try {
    Socket s = new Socket("localhost", port);
    try {
      Thread.sleep(100);
    } catch (InterruptedException e) {
    }
    s.close();
  } catch (IOException e) {
    fail("could not connect");
  }
}

This made the tests pass 10 for 10.

“This is scary.” I said. “The fact that a test passes once doesn’t mean the code is working.”

“Yeah.” Jerry agreed. “When you are dealing with multiple threads, you have to keep an eye out for possible race conditions. Hitting the test button multiple times is a good habit to get into.”

“It’s a good thing we found this in our test cases.” I said. “Finding it after the system was running would have been a lot harder.”

Jerry just nodded.


The project so far:

Commit 7, TestSocketServer.java
import junit.framework.TestCase;
import junit.swingui.TestRunner;
import java.io.IOException;
import java.net.Socket;

public class TestSocketServer extends TestCase {
  public static void main(String[] args) {
    TestRunner.main(new String[]{"TestSocketServer"});
  }

  public TestSocketServer(String name) {
    super(name);
  }

  public void testOneConnection() throws Exception {
    SocketService ss = new SocketService();
    ss.serve(999);
    connect(999);
    ss.close();
    assertEquals(1, ss.connections());
  }

  private void connect(int port) {
    try {
      Socket s = new Socket("localhost", port);
      try {
        Thread.sleep(100);
      } catch (InterruptedException e) {
      }
      s.close();
    } catch (IOException e) {
      fail("could not connect");
    }
  }
}
Commit 7, SocketService.java
import java.io.IOException;
import java.net.*;

public class SocketService {
  private ServerSocket serverSocket = null;
  private int connections = 0;
  private Thread serverThread = null;

  public void serve(int port) throws Exception {
    serverSocket = new ServerSocket(port);
    serverThread = new Thread(new Runnable() {
      public void run() {
        try {
          Socket s = serverSocket.accept();
          s.close();
          connections++;
        } catch (IOException e) {
        }
      }
    });
    serverThread.start();
  }

  public void close() throws Exception {
    serverSocket.close();
  }

  public int connections() {
    return connections;
  }
}

We came back from our break, ready to continue the SocketService.

“We’ve proven that we can connect once. Let’s try to connect several times.” said Jerry.

“Sounds good,” I said. So I wrote the following test case:

Commit 8, TestSocketServer.java (partial)
public void testManyConnections() throws Exception {
  SocketService ss = new SocketService();
  ss.serve(999);
  for (int i = 0; i < 10; i++)
    connect(999);
  ss.close();
  assertEquals(10, ss.connections());
}

“OK, this fails,” I said.

“As it should.” Jerry replied. “The SocketService only calls accept once. We need to put that call into a loop.”

“When should the loop terminate?” I asked.

Jerry thought for a second and said: “When we call close on the SocketService.”

“Like this?” I said, making the following changes:

Commit 9, SocketService.java
import java.io.IOException;
import java.net.*;

public class SocketService {
  private ServerSocket serverSocket = null;
  private int connections = 0;
  private Thread serverThread = null;
  private boolean running = false;

  public void serve(int port) throws Exception {
    serverSocket = new ServerSocket(port);
    serverThread = new Thread(new Runnable() {
      public void run() {
        running = true;
        while (running) {
          try {
            Socket s = serverSocket.accept();
            s.close();
            connections++;
          } catch (IOException e) {
          }
        }
      }
    });
    serverThread.start();
  }

  public void close() throws Exception {
    running = false;
    serverSocket.close();
  }

  public int connections() {
    return connections;
  }
}

I ran the tests, and they both passed.

“Good.” I said. “Now we can connect as many times as we like. Unfortunately the SocketService doesn’t do very much when we connect to it. It just closes.”

“Yeah, let’s change that.” said Jerry. “Let’s have the SocketService send us a “Hello” message when we connect to it.”

I didn’t care for that. I said: “Why should we pollute SocketService with a “Hello” message just to satisfy our tests? It would be good to test that the SocketService can send a message, but we don’t want the message to be part of the SocketService code!”

“Right!” Jerry agreed. “We want the message to be specified by, and verified by, the test.”

“How do we do that?” I asked.

Jerry smiled and said: “We’ll use the Mock Object pattern. In short, we create an interface that the SocketService will execute after receiving a connection. We’ll have the test implement that interface to send the “Hello” message. Then we’ll have the test read the message from the client socket and verify that it was sent correctly.”

I didn’t know what the Mock Object pattern was, and his description of interfaces confused me. “Can you show me?” I asked.

So Jerry took the keyboard and began to type.

“First we’ll write the test.”

Commit 10, TestSocketServer.java (partial)
public void testSendMessage() throws Exception {
  SocketService ss = new SocketService();
  ss.serve(999, new HelloServer());
  Socket s = new Socket("localhost", 999);
  InputStream is = s.getInputStream();
  InputStreamReader isr = new InputStreamReader(is);
  BufferedReader br = new BufferedReader(isr);
  String answer = br.readLine();
  s.close();
  assertEquals("Hello", answer);
}

I examined this code carefully. “OK, so you’re creating something called a HelloServer and passing it into the serve method. That’s going to break all the other tests!”

“Good point!” Jerry exclaimed. “That means we need to refactor those other tests before we continue.”

“But the services in the other two tests don’t do anything.” I complained.

“Of course they do — they count connections! Remember how much you didn’t like that connections variable, and its accessor? Well now we’re going to get rid of them.”

“We are?”

“Just watch.” Jerry laughed. “First we’ll change the two tests, and add a connections variable to the test case.”

Commit 11, TestSocketServer.java (partial)
public void testOneConnection() throws Exception
{
  ss.serve(999, connectionCounter);
  connect(999);
  assertEquals(1, connections);
}

public void testManyConnections() throws Exception
{
  ss.serve(999, connectionCounter);
  for (int i=0; i<10; i++)
    connect(999);
  assertEquals(10, connections);
}

“Next we’ll make the interface.”

Commit 12, SocketServer.java
import java.net.Socket;

public interface SocketServer {
  public void serve(Socket s);
}

“Next we’ll create the connectionCounter variable and initialize it in the TestSocketServer constructor with an anonymous inner class that bumps the connections variable.

Commit 13, TestSocketServer.java (partial)
public class TestSocketServer extends TestCase {
  private int connections = 0;
  private SocketServer connectionCounter;

  // ...

  public TestSocketServer(String name) {
    super(name);
    connectionCounter = new SocketServer() {
      public void serve(Socket s) {
        connections++;
      }
    };
  }

  // ...
}

“Finally, we’ll make it all compile by adding the extra argument to the serve method of the SocketService, and commenting out the new test.

Commit 14, SocketService.java
public void serve(int port, SocketServer server) throws Exception {
  // ...
}

“OK, I see what you are doing.” I said. The two old tests should fail now because SocketService never invokes the serve method of its SocketServer argument.”

Sure enough, the tests failed for exactly that reason.

I knew what to do next. I grabbed the keyboard and made the following change:

Commit 15, SocketService.java (partial)
public class SocketService {
  private ServerSocket serverSocket = null;
  private int connections = 0;
  private Thread serverThread = null;
  private boolean running = false;
  private SocketServer itsServer;

  public void serve(int port, SocketServer server) throws Exception {
    itsServer = server;
    serverSocket = new ServerSocket(port);
    serverThread = new Thread(new Runnable() {
      public void run() {
        running = true;
        while (running) {
          try {
            Socket s = serverSocket.accept();
            itsServer.serve(s);
            s.close();
            connections++;
          } catch (IOException e) {
          }
        }
      }
    });
    serverThread.start();
  }

  // ...

This made all the tests run.

“Great!” Said Jerry. “Now we’ve got to make the new test work.”

So I uncommented the test and compiled it. It complained about HelloServer.

“Oh, right. We need to implement the HelloServer. It’s just going to spit the word “hello” out the socket, right?”

“Right,” Jerry confirmed.

So I added the following new class to the TestSocketServer.java file:

Commit 16, TestSocketServer.java (partial)
class HelloServer implements SocketServer {
  public void serve(Socket s) {
    try {
      PrintStream ps = new PrintStream(s.getOutputStream());
      ps.println("Hello");
    } catch (IOException e) {
    }
  }
}

The tests all passed.

“That was pretty easy,” said Jerry.

“Yeah. That Mock Object pattern is pretty useful. It let us keep all the test code in the test module. The SocketService doesn’t know anything about it.”

“It’s even more useful than that.” Jerry replied. “Real servers will also implement the SocketServer interface.”

“I can see that.” I said. “Interesting that the needs of a unit test drove us to create a design that would be generally useful."

“That happens all the time.” Said Jerry. “Tests are users too. The needs of the tests are often the same as the needs of the real users.”

“But why is it called Mock Object?”

“Think of it this way. The HelloServer is a substitute for, or a mock-up of, a real server. The pattern allows us to substitute test mock-ups into real application code.”

“I see.” I said. “Well, we should clean this code up now and get rid of that useless connections variable.”

“Agreed.”

So we did a little cleanup and took another break. The resulting SocketService is shown below.

Commit 17, SocketService.java
import java.io.IOException;
import java.net.*;

public class SocketService {
  private ServerSocket serverSocket = null;
  private Thread serverThread = null;
  private boolean running = false;
  private SocketServer itsServer;

  public void serve(int port, SocketServer server) throws Exception {
    itsServer = server;
    serverSocket = new ServerSocket(port);
    serverThread = makeServerThread();
    serverThread.start();
  }

  private Thread makeServerThread() {
    return new Thread(new Runnable() {
      public void run() {
        running = true;
        while (running) {
          acceptAndServeConnection();
        }
      }
    });
  }

  private void acceptAndServeConnection() {
    try {
      Socket s = serverSocket.accept();
      itsServer.serve(s);
      s.close();
    } catch (IOException e) {
    }
  }

  public void close() throws Exception {
    running = false;
    serverSocket.close();
  }
}

The turbo on level 17 was down again, so I had to use the ladder-shaft. While sliding down the ladder I got to thinking about how remarkable it was to use tests as a design tool. Lost in my thoughts I got a bit careless with the coriolis forces and bumped my elbow against the shaft. It was still stinging when I rejoined Jerry in the lab.

"Nicely done on the cleanup," said Jerry. "And with the HelloServer, we now know that we can send a message through the socket."

I knew Jerry was going to start thinking about refactoring now. I wanted to get the jump on him. So I looked carefully at the code, and I remembered what he told me about duplication. "There’s some duplicated code in the unit tests." I said. "In each of the three tests we create and close the SocketService. We should get rid of that."

"Good eye!" He replied. "Let’s move that into the Setup and Teardown functions." He grabbed the keyboard and made the following changes.

Commit 18, TestSocketServer.java (partial)
private SocketService ss;

public void setUp() throws Exception {
  ss = new SocketService();
}

public void tearDown() throws Exception {
  ss.close();
}

Then he removed all the ss = newSocketService(); and ss.close(); lines from the three tests.

"That’s a little better," I said. "So now let’s see if we can send a message the other direction."

"Exactly what I was thinking." Said Jerry. "And I’ve got just the way to do it."

Jerry grabbed the keyboard and began to type a new test case.

Commit 19, TestSocketServer.java (partial)
public void testReceiveMessage() throws Exception {
  ss.serve(999, new EchoService());
  Socket s = new Socket("localhost", 999);
  InputStream is = s.getInputStream();
  InputStreamReader isr = new InputStreamReader(is);
  BufferedReader br = new BufferedReader(isr);
  OutputStream os = s.getOutputStream();
  PrintStream ps = new PrintStream(os);
  ps.println("MyMessage");
  String answer = br.readLine();
  s.close();
  assertEquals("MyMessage", answer);
}

"Ouch! That’s pretty ugly." I said.

"Yeah, it is." Replied Jerry. "Let’s get it working and then we’ll clean it up. We don’t want a mess like that laying around for long! You see where I am going with this though, don’t you?"

"Yes, I thnk so." I said. "`EchoService` will recieve a message from the socket and just send it right back. So your test just sends 'MyMessage', then reads it back again."

"Right. Care to take a crack at writing EchoService?"

"Sure," I said, and I grabbed the keyboard.

Commit 20, TestSocketServer.java (partial)
class EchoService implements SocketServer {
  public void serve(Socket s) {
    try {
      InputStream is = s.getInputStream();
      InputStreamReader isr = new InputStreamReader(is);
      BufferedReader br = new BufferedReader(isr);
      OutputStream os = s.getOutputStream();
      PrintStream ps = new PrintStream(os);
      String token = br.readLine();
      ps.println(token);
    } catch (IOException e) {
    }
  }
}

"Ick." I said. "More of the same kind of ugly code. We keep on having to create PrintStream and BufferedReader objects from the socket. We really need to clean that up."

"We’ll do that just as soon as the test works," said Jerry. And then he looked at me expectantly. "Oh!" I said, "I forgot to run the test."

Sheepishly, I pushed the test button and watched it pass.

"That wasn’t hard to get working," I said; "now let’s get rid of that ugly code."

Keeping the keyboard, I extracted several functions from the previous mess in EchoService:

Commit 21, TestSocketServer.java (partial)
class EchoService implements SocketServer {
  public void serve(Socket s) {
    try {
      BufferedReader br = getBufferedReader(s);
      PrintStream ps = getPrintStream(s);
      String token = br.readLine();
      ps.println(token);
    } catch (IOException e) {
    }
  }

  private PrintStream getPrintStream(Socket s) throws IOException {
    OutputStream os = s.getOutputStream();
    PrintStream ps = new PrintStream(os);
    return ps;
  }

  private BufferedReader getBufferedReader(Socket s) throws IOException {
    InputStream is = s.getInputStream();
    InputStreamReader isr = new InputStreamReader(is);
    BufferedReader br = new BufferedReader(isr);
    return br;
  }
}

"Yes." Said Jerry. "That makes the serve method of EchoService much better, but it clutters the class quite a bit. Also, it doesn’t really help the testRecieveMessage function. That function has the same kindofugliness. I wonder if getBufferedReader and getPrintStream are in the right place."

"This is going to be a recurring problem," I said. "Anybody who wants to use SocketService is going to have to convert the socket into a BufferedReader and a PrintStream."

"I think that’s your answer!" Jerry replied. "The getBufferedReader and getPrintStream methods really belong in SocketService."

This made a lot of sense to me. So I moved the two functions into the SocketService class and changed EchoService accordingly.

Commit 22, SocketService.java (partial)
public class SocketService {
  // ...

  public static PrintStream getPrintStream(Socket s) throws IOException {
    OutputStream os = s.getOutputStream();
    PrintStream ps = new PrintStream(os);
    return ps;
  }

  public static BufferedReader getBufferedReader(Socket s) throws IOException {
    InputStream is = s.getInputStream();
    InputStreamReader isr = new InputStreamReader(is);
    BufferedReader br = new BufferedReader(isr);
    return br;
  }
}
Commit 22, TestSocketServer.java (partial)
class EchoService implements SocketServer {
  public void serve(Socket s) {
    try {
      BufferedReader br = SocketService.getBufferedReader(s);
      PrintStream ps = SocketService.getPrintStream(s);
      String token = br.readLine();
      ps.println(token);
    } catch (IOException e) {
    }
  }
}

The tests all still ran. Keeping my momentum, I said: "Now I should be able to fix the testReceiveMessage method too." So, while Jerry watched, I made that change too.

Commit 23, TestSocketServer.java (partial)
public void testReceiveMessage() throws Exception {
  ss.serve(999, new EchoService());
  Socket s = new Socket("localhost", 999);
  BufferedReader br = SocketService.getBufferedReader(s);
  PrintStream ps = SocketService.getPrintStream(s);
  ps.println("MyMessage");
  String answer = br.readLine();
  s.close();
  assertEquals("MyMessage", answer);
}

"Yeah, that’s a lot better," Jerry said.

"Not only that, but the tests still pass." I said. Then I noticed something. "Oops, there’s another one in testSendMessage." And so I made that change too.

Commit 24, TestSocketServer.java (partial)
public void testSendMessage() throws Exception {
  ss.serve(999, new HelloServer());
  Socket s = new Socket("localhost", 999);
  BufferedReader br = SocketService.getBufferedReader(s);
  String answer = br.readLine();
  assertEquals("Hello", answer);
}

The tests all still ran. I sat there looking through the TestSocketServer class, trying to find any other code I could eliminate.

"Are you done?" asked Jerry.

After a few more seconds of scanning, I relaxed, and said, "I think so."

"Good." He said. "I was starting to see smoke coming out of your ears. I think it’s time for a quick break."

"OK, but I have a question first."

"Shoot."

I looked back and forth between Jerry and the code for a few seconds, and then I said: "We haven’t made a single change to SocketService. We’ve added two new tests — testSendMessage, and testReceiveMessage — and they both just worked. And yet we spent a lot of time writing those tests, and also refactoring. What good did it do us? We didn’t change the production code at all!"

Jerry raised an eyebrow and gave me an impenetrable regard. "It’s interesting that you noticed that. Do you think we wasted our time?"

"It didn’t feel like a waste, because we proved to ourselves that you could send and receive messages. Still, we wrote and refactored a lot of code that didn’t help us add anything to the production code."

"You don’t think that getBufferedReader and getPrintStream were worth putting into production?"

"They’re pretty trivial, and all they’re really doing is supporting the tests."

Jerry sighed and looked away for a minute. Then he turned back to me and said: "If you were just coming onto this project, and I showed you these tests, what would they teach you?"

That was a strange question. My first reaction was to answer by saying that they’d teach me that the authors had wanted to prove their code worked. But I held that thought back. Jerry wouldn’t have asked me that question if he hadn’t wanted me to carefully think about the answer.

What would I learn by reading those tests? I’d learn how to create a SocketService, and how to bind a SocketServer derivative to it. I’d also learn how to send and receive messages. I’d learn the names and locations of the classes in the framework, and how to use them.

So I gathered up my courage and said: "You mean we wrote these tests as examples to show to others?"

"That’s part of the reason, Alphonse. Yes, others will be able to read these tests and see how to work the code we’re writing. They’ll also be able to work through our reasoning. Moreover, they’ll be able to compile and execute these tests in order to prove to themselves that our reasoning was sound."

"There’s more to it than that—" he continued, "but we’ll leave that for another time. Now let’s take our break."

My elbow still twinged, so I was glad to see that the turbo was repaired. On the way up the lift I kept rolling same thought around in my head. "Tests are a form of documentation. Compilable, executable, and always in sync."