CMSC 433, Spring 2006

Programming Language Technologies and Paradigms

Project 2

Due February 27 2006

Updates

  1. The spec for Person.update revised to be specific about what its output must be.

In all of the other projects for this class, you write test cases to help debug your code, and we write test cases to grade your code. In this project, you will write JUnit test cases for our code. In this case, our code will be an implementation of the Observer pattern.

Background: Observer Pattern

You should begin by reviewing course material on the Observer Pattern. Chapter 2 of the textbook covers the Observer Pattern in detail. The code you will test is a modification of Java 1.5's observer pattern classes from java.util. The specification for these classes is given in the detailed specification. Here are highlights of the differences:

  1. Observable is an interface, rather than a class.
  2. Observable and Observer have been parameterized, using generics, by the type of data that is passed to the Observer.update method. This obviates the need for dynamic downcasts within Observer.update.
  3. When registering an Observer with addObserver, you must pass a (possibly null) Filter object. Only if the valid method of this object returns true when given the updated state will the associated observer be notified.
  4. We have removed the setChanged and hasChanged methods. To notify the observers, simply call one of the notifyObservers methods directly.

An Application of Observers: Newspaper Simulation

As an application of the observer pattern, we have written newspaper publishing and subscription simulation. There are four classes/interfaces: an implementation of interface NewsSimulator reads in events from a list, where each event is a String, and runs the simulation; Publisher represents a newspaper publisher and implements the Observable interface; Person represents a newspaper subscriber and implements the Observer interface; finally Newspaper represents newspapers published by Publisher and received by the update method of Person. The API documentation explains how these work.

The Project

Your job is to write (black box) test cases to ensure that implementations of Publisher and NewsSimulator meet their specifications (we will provide implememtations of Person and Newspaper). In particular, you must supply us with a class StudentTests that extends junit.framework.TestCase. Inside this class you will write JUnit test cases to test implementations of the specification. Here is a skeleton for the class you will write:
public class StudentTests extends TestCase {
  public void testCase1() { ... } // feel free to use more informative names
  public void testCase2() { ... }
  ...
}

This class will contain one public, no-argument, void-returning method for each test that you write, and each method's name must begin with the word test (all lowercase). Recall that by following this convention, JUnit can automatically create a test suite from your StudentTests class. Each test should be self-contained, with no dependency to other tests. Do not use static fields. You may want to use setUp and/or tearDown methods. You may also define other methods in StudentTests and even helper classes (under the cmsc433.p2 package) as necessary. Be careful not to create your own classes that mask the JUnit classes. For example, a class called Test (in a source file Test.java) could cause problems.

When testing Publisher, a test case must begin with a call to TestFactory.getObservable() to retrieve an object that implements Observable to be tested; never create an Observable object directly through its constructor. When we are evaluating your submission, we will supply your test cases with different implementations of Observable via TestFactory.getObservable. (If you modify things for testing purposes, be sure to change them back and make sure your StudentTests pass the correct implementation before submitting.) Similarly, when testing NewsSimulator, a test case must begin with a call to TestFactory.getNewsSimulator. When testing the simulator, you can assume that it uses correct implementation of Publisher.

The initial project code contains one correct implementation of Observable and NewsSimulator under the cmsc433.p2.correct package, and three buggy implementations under the cmsc433.p2.buggy1, cmsc433.p2.buggy2, and cmsc433.p2.buggy3 packages. In these packages, the Observable implementation has one of the following flaws:

  1. The notifyObservers() call does not notify an observer that previously registered.
  2. The deleteObserver(o) call does not remove an observer o, which was previously registered.
  3. The notifyObservers(p) call ignores any registered filters (always updates).
The PublicTests check two things: (1) that all of your test cases (in StudentTests) pass the correct implementation; (2) that at least one of your test cases fails on each of the sample bad implementations. The release tests will do the same thing, but on implementations with flaws you have not seen. Therefore, try not peeking at the bad implementations and see if you can write test cases that fail on the three flaws.

Be systematic in building your tests. You will probably want to create a set of tests for each public method of the classes to be tested. For each of these sets you will probably have one test for each possible outcome, possible erroneous input, etc.

Finally, you can assume that all testing and implementations are single-threaded. I.e., you don't have to worry about an Observable notifying an Observer in a separate thread.

Grading

We will grade your assignment by running your test suite on a series of 25 buggy implementations of Observable and NewsSimulator; three of these are provided to you and 22 are hidden release tests. Each buggy implementation will contain exactly one mistake relative to the correct implementation, although of course the same bug could be revealed by many different test cases.

Since you only have to have at least one test case that finds a particular bug, when you are writing test cases you don't need to recheck for things that other cases handle. For example, once you've written test cases that check the addObserver method of Observable, your other tests can assume that addObserver works correctly: If it does for a particular buggy version, then your assumption is correct, and if not, the other test cases will find the problem.

For each buggy implementation, you will receive credit for finding that bug if (and only if) you have at least one test in your test suite that both passes our correct implementation(s) and fails the buggy implementation. In other words, writing a test case that simply fails all implementations will not get you any points. Put another way: In order to count for anything, your test cases must pass the correct implementation. Your test cases must also pass any other implementation that satisfies the specification. Thus we may also discount test cases that fail a different correct implementation than the one provided.

You should write test cases keeping in mind that the buggy implementations are buggy in ways that could happen in real life. We have not put in malicious things, like making a method fail on exactly the 4,237th call to addObserver, for example.

Extra Credit

Although we believe we've given you a completely correct implementation in cmsc433.p2.correct, there's a chance that our implementation still contains bugs. As an incentive for starting the project early, the first person to report a particular implementation bug to us will receive 5 extra points, for a maximum of 15 bonus points per person. You should report these extra-credit bugs by posting them on the Class forum.

FindBugs

This project employs both annotations and generics, which are features only of Java 5.0. Thus you must develop this project with Java 5.0.