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.
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:
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:
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.
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.