(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.
For the initial version we can find many candidate classes, but in the end only a few are needed to meet the requirements:
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 ()
int numberOfHolesPlayed ()
void enterScore(int strokes)
int[] scoreDetail()
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 )
static Round getRound (String player,
String courseName )
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 )
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 )
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 )
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.)
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.)
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.)
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.
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.)
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.)
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).
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.
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.