COP-2805 (Java II) Project - Unit Tests for Mini-Golf Project

 

Due: by the start of class on the dates shown on the syllabus

Overview:

(This project is a continuation of the previous project, MiniGolf Design, part I.)

After evaluating the various designs and proposals, the customer had picked our organization to deliver the final project!  They have also provided input on the overall design, which we must follow.

Below are some design notes (obviously incomplete), including the classes to have and the methods required for each.  NOTE!!  You may add additional classes and/or methods to this basic outline.  (You probably will need to!)  Note the fields for the classes aren't specified here; this is considered an implementation detail.

For this version, assume all code runs on a single JVM.  However, each swipe-station runs on its own thread, so any shared data must be made “thread-safe”.

The swipe card holds (for this initial version anyway) the player's name and the course name only.  Somehow you need to look-up the player's Round object (see class Round below).  This information does not need to be stored except in RAM.  The Main class will need to provide a getRound method that returns a Round object (or “null”) given a player's name and a course's name.

Our software development team has decided to embrace Test-Driven Development (TTD).  This means that, now that the requirements and design have been developed (although not fully finished), the next step is to design unit tests for the classes identified in the design.  Normally you would develop the tests at the same time as starting the implementation, but for our project, we are only developing unit tests.

Your group will be assigned some part of the design, for which you must develop JUnit unit tests.  (Other groups will develop the tests for the other parts.)  The main part of the design that was approved (sorry if you don't like it!) is described next.

The Primary Classes:

For the initial version we can find many candidate classes, but in the end only a few are needed to meet the requirements:

  1. Class Round

    Class Round holds a player's information for the current round of golf: their name, the course name, and the number of strokes per hole (for each hole played so far).  An object of this class is created when a customer pays for a round and their name is entered by the operator, which also creates a swipe-card.

    The round information is not stored beyond the closing time of any course (or, say “midnight” for 24 hour courses if any).  This means that unlike the “course” data, you don't need to persist round data to storage.  However when running low on RAM the garbage collector should be able to reclaim Round objects that have been completed.

    After a player has swiped their card, the card-swipe station will lookup the player's Round object.  Then it will invoke some methods on the Round object:

    • int currentScore ()
      Display the user's score so far (this is the number of strokes above or below par, for the holes played so far);
    • int numberOfHolesPlayed ()
      Returns the number of completed holes in this round;
    • void enterScore(int strokes)
      Enter a score for the next hole.  (Note the current design doesn't allow a player to skip holes, they must be played in order);
    • int[] scoreDetail()
      Returns the array of strokes per hole.  This method gets called by the final hole's swipe-station, to print out the players scorecard (which has pre-printed coupons on the reverse side.)  After this method is invoked the round object can be disposed of.
    • Getters for the properties: courseName, playerName, and possibly others (such as time round was started).
  2. Class Main

    Class Main will need a number of public static methods.  These get invoked by the card-swipe stations (actually their objects, which run in a separate thread each) when a new round is created, when a user swipes their card, and when a user enters a score.  The methods needed include:

    • static Round newRound ( String courseName, String playerName )
      This method creates and stores a new round object, and returns a reference to that Round object.
    • static Round getRound (String player, String courseName )
      This method looks up the Round object.  Note it isn't specified how you store Round objects, but a likely choice is some sort of “Collection”.  Keep in mind the garbage collection requirements for this object, discussed above.
    • static Course getCourse ( String courseName )
      Returns a Course object.  These must be persisted to storage but there aren't many courses so creating a “Collection” to hold them all, read from a file say, is reasonable.  However exactly how you do this is an implementation detail.
    • static int scoreSoFar( String courseName, String playerName )
      This method looks up the round information and returns it's score so far to the swipe station, to display.  (Yes it is redundant with Round.currentScore(), but the customer insisted — probably has a nephew or niece who is a “computer expert”.)
    • static void enterStrokes ( String courseName, String playerName, int strokes )
      This method is redundant.  It also will be used by the swipe-station once a player hits the button for their number of strokes for that hole.
    • Getters for the properties: course list, round list.
  3. Class Course

    Class Course holds information about a particular mini-golf course:  The course name and address info, and the “par” (number of strokes an average-good player would need) for each hole in that course.  (This data is read from persistent storage by Main when the application starts.)  Course objects are basically just simple data records.  They should be immutable.  They will need the following methods:

    • String getName ()
    • int getNumberOfHoles ()
    • int parForHole ( int hole )

In addition, class Main needs the main method for the whole application.  The main method reads the course data from storage and creates all the course objects.  It should do any other initialization required (e.g., create the collection object used to hold Round objects).

It is the job of the Main.main method to create the Course objects from the data records in a data file or a database.   A file can be either text, XML, or JSON, and the file format is up to you.  Or, you can use a database.  The file format specification (or database schema) must be included with your project submission!  (The reason is, without knowing what data is saved, it would be impossible to write proper unit tests.  Besides, our class only designed one file format all semester; the practice will be good for you.)

Other Classes:

A CardSwipeStation class that interfaces with the card-swipe station hardware is provided by an “out-source” vendor and will be available when needed.  This class will detect card-swipe events and keypad data entry, and will invoke the various public methods of the two classes above (Round and Main) as needed.

The card-swipe station for the last hole on a course includes a printer.  Once the score for this hole is entered, the player's score-card is printed and the round is complete.

Since we don't have the required hardware we can simulate the card-swipe class.  (A GUI to act as the card-swipe stations would be a nice touch, but is not required.)

Potential Changes (Requirements not part of the provided RFP):

  1. Allow players to play holes out of order (assume the CardSwipeStation class “knows” which card-swipe station (i.e., the hole number) was used to enter a score.  (Players must still play the final hole last; in addition to printing their score-card it swallows their golf ball.)
  2. Enhance the application to allow two players with the same name to play the same course simultaneously, or for two courses to have the same name.
  3. Enhance the application to support a “frequent golfer” feature, where a player can sign up and have the system remember their rounds (and the date they played them).  This feature could also support tournament play.

Project Outline:

You will need to create skeleton classes, with stub methods, for all classes and methods in the design.  They should compile and run, but do nothing.  Do not implement any methods!  Once you have created that skeleton (it shouldn't take long), your team can write and then run the JUnit tests.  Naturally, it is expected that all the tests will fail, since the stub methods don't do what they are supposed to do.  While developing tests, you may decide additional classes and/or methods are needed.  If so, develop skeletons for those classes as well.  Do not forget to include any such design documents when submitting your assignment.

Your unit test may require some saved data.  (For example, you may need some player data loaded, or some objects created, in order to test some methods.  Your group must decide and document the file format (or database schema), no matter which set of classes you pick to test.

Team Assignments:

One team will test class Round and any additional methods or foundational classes needed.

One team will test class Course and class Main, and add any additional methods or foundational classes needed.

(Your team can pick either part of the design to test.  Just let the instructor know once you have decided.)

To be turned in:

Individual team member ratings:

A rating of each team member's level of participation, from each member should be sent directly to the instructor.  Be sure to include yourself in the ratings!  The rating is a number from 0 (didn't participate at all), 1 (less than their fair share of the work), 2 (participated fully), or 3 (did more than their fair share of the work).  You may include additional comments if you wish to elaborate.  (I will keep such comments as confidential as possible under Florida law.)

The code:

A single copy of your team's code, including the skeleton classes and all the JUnit tests.  If your group prefers, you can put the code in a repo on GitHub, and send just a link to it.  Your project's final version should receive a Git tag of “MiniGolf Project - Unit Tests”, so I know which version to grade.  You can send your documents (as email attachments if necessary) to (preferred).

Hints:

In this term, we've had projects on each aspect of software development.  In this project I will be grading the quality and thoroughness of your unit tests, and the quality of the accompanying documentation.  You should review the Testing resources provided on the class web page, which include the testing lecture notes, links to JUnit API and other documentation, and example code.

Short JUnit Refresher:

With JUnit, you use the methods of the class org.junit.Assert, such as assertEquals or assertFalse, inside of test methods which are marked with the “@Test” annotation.  These test methods, along with other methods and fields, are part of a class known as a test suite.

For example, for the scoreDetail method, your test might look something like this:

public class MyTestSuite {
   @Test
   public void testScoreDetailLegalInput () {
      final int[] correctScores = { ... };
      final Round r = new Round( ...);
      int [] scores = r.scoreDetail();
      assertNotNull( scores );
      assertArrayEquals( correctScores, scores );
   }
}

That is just one test case, with a single legal Round object.  It is likely your tests will need many Round or other objects, so you should check into creating a test fixture.  A test fixture is simply one or more methods that get called prior to each test method's invocation.  Such methods can reset (or initialize) various fields in your test suite, so each test starts with a clean slate.  In this case, you could create a method that sets up some objects for you:

public class MyTestSuite {
   private List<Course> courseList;
   private Round legalRound1;
   private int [] legalRound1CorrectScores;
   private Round badRound;
   ...

   @Before
   public void init () {
      courseList = ...;
      legalRound = ....;
      legalRound1CorrectScores = { ... };
      badRound = ....;
      ...
   }

   @Test
   public void testScoreDetailLegalInput1 () {
      int [] scores = legalRound1.scoreDetail();
      assertNotNull( scores );
      assertArrayEquals( legalRound1CorrectScores, scores );
   }

   @Test
   public void testScoreDetailBoundryValues1 () {
      ...
   }
   
   ...
}

Methods marked with @Before all get called before every @Test method does, every time.  You can also create @After methods, to cleanup stuff.  (You can also have methods marked with “@BeforeClass”, which get run once at the start of a test run.  You could use that to re-create a sample file, setup a database, or start some server program.)

Remember that both Eclipse and NetBeans have wizards to create test suites (Eclipse uses the term Test Case for the whole class).  The hard part is to come up with a good set of tests.  You want confidence that if all the tests pass, the code being tested correctly implements the design.