Java Arena
Step 6 of 6

Putting it all together

This module's tools, side by side:

Tool Example
Input for a Scanner, written in the code new Scanner("12\n8\n")
A test method @Test public void parkingTakesOneSpot()
Compare with the expected value assertEquals(2, garage.freeSpots())
Compare doubles, with a delta assertEquals(33.33, garage.percentFull(), 0.01)
Check a boolean assertTrue(garage.park()), assertFalse(garage.park())
The same start for every test @Before

The last one is new. When every test starts by making the same object, the test class can do that in one place: a method marked @Before runs before each test. It puts the object in an instance variable of the test class, and the tests use it from there.

Here it is in the tests of a coin flip log. Someone flips a coin and writes down each result, heads or tails, one per line, and the log reads them from a Scanner. The tests give it Strings, so the idea of the second step and JUnit work together:

FlipLog.java
import java.util.Scanner;

public class FlipLog {
    private int heads;
    private int tails;

    public FlipLog() {
        this.heads = 0;
        this.tails = 0;
    }

    public void readFlips(Scanner scanner) {
        while (scanner.hasNextLine()) {
            String flip = scanner.nextLine();
            if (flip.equals("heads")) {
                this.heads++;
            } else if (flip.equals("tails")) {
                this.tails++;
            }
        }
    }

    public int headsCount() {
        return this.heads;
    }

    public int tailsCount() {
        return this.tails;
    }

    public boolean mostlyHeads() {
        return this.heads > this.tails;
    }
}
FlipLogTest.java
import static org.junit.Assert.*;

import java.util.Scanner;
import org.junit.Before;
import org.junit.Test;

public class FlipLogTest {
    private FlipLog log;

    @Before
    public void setUp() {
        this.log = new FlipLog();
    }

    @Test
    public void countsBothSides() {
        this.log.readFlips(new Scanner("heads\ntails\nheads\n"));
        assertEquals(2, this.log.headsCount());
        assertEquals(1, this.log.tailsCount());
    }

    @Test
    public void otherLinesAreIgnored() {
        this.log.readFlips(new Scanner("edge\n\nheads\n"));
        assertEquals(1, this.log.headsCount());
        assertEquals(0, this.log.tailsCount());
    }

    @Test
    public void moreHeadsIsMostlyHeads() {
        this.log.readFlips(new Scanner("heads\ntails\nheads\n"));
        assertTrue(this.log.mostlyHeads());
    }

    @Test
    public void tieIsNotMostlyHeads() {
        this.log.readFlips(new Scanner("tails\nheads\n"));
        assertFalse(this.log.mostlyHeads());
    }
}
Output
FlipLogTest: 4 tests
  countsBothSides: passed
  moreHeadsIsMostlyHeads: passed
  otherLinesAreIgnored: passed
  tieIsNotMostlyHeads: passed
4 of 4 tests passed
  • import org.junit.Before; brings in @Before, and java.util.Scanner is imported as in any class that uses it.
  • private FlipLog log; is an instance variable of the test class, and setUp(), marked @Before, gives it a new FlipLog.
  • JUnit makes a new FlipLogTest object for every test, and runs setUp() on it before the test. So every test gets a new, empty FlipLog of its own: the three flips of countsBothSides never reach tieIsNotMostlyHeads.
  • Each test gives readFlips exactly the input it's about, from an empty line to a tie, with no typing.

The end of part 6

This step ends the material of the MOOC's sixth part. Part 6 put lists inside objects: a class kept a list in an instance variable, and its methods decided what could be done with it. Then programs were split in two, with the program's logic in classes of its own and a user interface class that reads the commands and prints the answers, so that each part can change without the other. This module was about knowing that it all works: reading a stack trace, troubleshooting step by step, giving code its input from a String, and unit tests with JUnit, written after the code or before it. Next, part 7 looks at different ways to structure a program, and at algorithms for sorting and searching. Part 6 closes with the MOOC's page on complex programs on its own site, for reading only.

Your task

Challenge 1 of 3

A timesheet reads the hours worked each day, one number per line, until the input ends. Empty lines are skipped, and reading goes on after them. totalHours() adds up the hours, and overtimeDays() counts the days of more than 8 hours. TimesheetTest makes a new Timesheet before each test with @Before, and has one test.

Write tests that give read its input as a String, and that catch a Timesheet that counts a day of exactly 8 hours as overtime. Two more versions, hidden, have bugs in how they read the lines.

Challenge 2

A quiz gives 10 points for a right answer and nothing for a wrong one. Three right answers in a row give 5 bonus points, and then the count starts over. A wrong answer ends the streak. QuizScoreTest is right, but two of its tests fail on QuizScore.

Run the tests and read the report: each failing test's name says which rule is broken. Fix QuizScore one bug at a time, running the tests after each fix. The checks run the tests of QuizScoreTest as they are given here (changing them won't help), and more tests of their own.

Challenge 3

On moving day, the list of boxes is read from a Scanner, one box per line, until the input ends. A line that starts with x (an x and a space) is a box that's on the van already, like x books, and any other line is a box still to load. Empty lines are skipped. Write MovingBoxes so the tests of MovingBoxesTest pass:

  • read(Scanner scanner) reads the lines. Calling it again reads more boxes onto the same list.
  • loadedCount() returns how many boxes are on the van, and leftToLoad() how many are not.
  • isDone() returns true when no box is left to load.

The checks run the tests of MovingBoxesTest as they are given here (changing them won't help), and more tests of their own.