Over a million developers have joined DZone.

First Iteration — A Command-Line Application: Part 5

DZone's Guide to

First Iteration — A Command-Line Application: Part 5

The fifth part in a series on test-driven development with Java web apps and web services.

· Java Zone
Free Resource

Never build auth again! The Okta Developer Platform makes it simple to implement authentication, authorization, MFA and more in Java applications. Get started with the free API.

Review All Code Changes So Far

We are on our way to developing and exploring various technologies of web applications and web services, but we begin with a simple program that turns into a resuable component. Read the previous section here!

To view all the latest code (prior to changes made during this article), 

go to https://github.com/elicorrales/shape-calc-4b/tree/master/shape-calc .

For the latest code changed  during this article, see the end of article.

Continue With Test-Driven Development

Another Way to Test Exceptions in JUnit

I made a minor change to some of the more basic tests. I added a JUnit rule (since this project is using JUnit 4.x),

public ExpectedException illegalArgThrown = ExpectedException.none();

and then removed the try-catch blocks from three of the tests:

public void testQueueRequestWithNullShapeName() {

    double dimension = 0;
    calculator.queueCalculationRequest(null, CalcType.CALC_AREA, dimension);

public void testQueueRequestWithNullCalcType() {

    double dimension = 0;
    calculator.queueCalculationRequest(ShapeName.CIRCLE, null, dimension);

public void testQueueRequestWithNegativeDimension() {

    double dimension = -0.01;
   calculator.queueCalculationRequest(ShapeName.CIRCLE, CalcType.CALC_AREA, dimension);

Add Another Key JUnit Test — Correctness

Now it is time to add one of the very important, defining tests, that will compel us to dive in do some real software work in analysis, design, and implementation.  This test forces our calculator to actually do its job of calculating.

Here is the test:

public void testTestForCorrectCalculatedResults() {


    double dimension = 1;
    calculator.queueCalculationRequest(ShapeName.CIRCLE, CalcType.CALC_AREA, dimension);
    dimension = 2;
    calculator.queueCalculationRequest(ShapeName.CIRCLE, CalcType.CALC_VOLUME, dimension);
    dimension = 3;
    calculator.queueCalculationRequest(ShapeName.SQUARE, CalcType.CALC_AREA, dimension);
    dimension = 4;
    calculator.queueCalculationRequest(ShapeName.SQUARE, CalcType.CALC_VOLUME, dimension);
    dimension = 5;
    calculator.queueCalculationRequest(ShapeName.EQUILATERALTRIANGLE, CalcType.CALC_AREA, dimension);
    dimension = 6;
    calculator.queueCalculationRequest(ShapeName.EQUILATERALTRIANGLE, CalcType.CALC_VOLUME, dimension);
    dimension = 1;
    calculator.queueCalculationRequest(ShapeName.SPHERE, CalcType.CALC_AREA, dimension);
    dimension = 2;
    calculator.queueCalculationRequest(ShapeName.SPHERE, CalcType.CALC_VOLUME, dimension);
    dimension = 3;
    calculator.queueCalculationRequest(ShapeName.CUBE, CalcType.CALC_AREA, dimension);
    dimension = 4;
    calculator.queueCalculationRequest(ShapeName.CUBE, CalcType.CALC_VOLUME, dimension);
    dimension = 5;
    calculator.queueCalculationRequest(ShapeName.TETRAHEDRON, CalcType.CALC_AREA, dimension);
    dimension = 6;
    calculator.queueCalculationRequest(ShapeName.TETRAHEDRON, CalcType.CALC_VOLUME, dimension);

    List<CalculationRequest> requests = calculator.getAllPendingRequests();

    int numRun = calculator.runAllPendingRequestsNoStopOnError();

    requests = calculator.getAllPendingRequests();

    List<CalculationResult> results = calculator.getAllCalculationResults();

    //for direct access so we can test for correct result
    Map<CalculationRequest,CalculationResult> resultsMap = new HashMap<CalculationRequest, CalculationResult>(results.size());
    for (CalculationResult res : results) {

    dimension = 1;
    assertEquals(3.14,resultsMap.get(new CalculationRequest(ShapeName.CIRCLE, CalcType.CALC_AREA, dimension)).getResult(),0.01);
    dimension = 2;
    assertEquals(0,resultsMap.get(new CalculationRequest(ShapeName.CIRCLE, CalcType.CALC_VOLUME, dimension)).getResult(),0.0);
    dimension = 3;
    assertEquals(9,resultsMap.get(new CalculationRequest(ShapeName.SQUARE, CalcType.CALC_AREA, dimension)).getResult(),0.0);
    dimension = 4;
    assertEquals(0,resultsMap.get(new CalculationRequest(ShapeName.SQUARE, CalcType.CALC_VOLUME, dimension)).getResult(),0.0);
    dimension = 5;
    assertEquals(10.83,resultsMap.get(new CalculationRequest(ShapeName.EQUILATERALTRIANGLE, CalcType.CALC_AREA, dimension)).getResult(),0.01);
    dimension = 6;
    assertEquals(0,resultsMap.get(new CalculationRequest(ShapeName.EQUILATERALTRIANGLE,CalcType.CALC_VOLUME, dimension)).getResult(),0.0);
    dimension = 1;
    assertEquals(12.57,resultsMap.get(new CalculationRequest(ShapeName.SPHERE, CalcType.CALC_AREA, dimension)).getResult(),0.01);
    dimension = 2;
    assertEquals(33.51,resultsMap.get(new CalculationRequest(ShapeName.SPHERE, CalcType.CALC_VOLUME, dimension)).getResult(),0.01);
    dimension = 3;
    assertEquals(54,resultsMap.get(new CalculationRequest(ShapeName.CUBE, CalcType.CALC_AREA, dimension)).getResult(),0.0);
    dimension = 4;
    assertEquals(64,resultsMap.get(new CalculationRequest(ShapeName.CUBE, CalcType.CALC_VOLUME, dimension)).getResult(),0.0);
    dimension = 5;
    assertEquals(43.3,resultsMap.get(new CalculationRequest(ShapeName.TETRAHEDRON, CalcType.CALC_AREA, dimension)).getResult(),0.01);
    dimension = 6;
    assertEquals(25.46,resultsMap.get(new CalculationRequest(ShapeName.TETRAHEDRON, CalcType.CALC_VOLUME, dimension)).getResult(),0.01);

    results = calculator.getAllCalculationResults();

Analyze, Design, and Implement the Code to Pass Test

There were a lot of code changes and additions, trying to ahere to OOA/OOD/OOP principles, etc.  I am not going to go into much detail of the changes, other than to point out the Callable interface from the Java concurrency libraries.

Callable and Future — Java Concurrency

You can have a task  implement the Callable (similar to Runnable) interface, with the difference being that it can return an object of your choice.

When you submit the task to the executor, it returns a Future (result). You can then call  get() on the Future result object, and it will block until that task has completed. Invoking get() will return your object that was returned from the task's run() method.

Here is the code...  this is inside our ShapeCalculatorServiceImpl:

for (loop) {

    // part of the loop that submits all the tasks (the calculation requests)

    Future<CalculationResult> futureResult = executor.submit(task);



for (Future<CalculationResult> futureResult : futureResults) {

    CalculationResult result = futureResult.get();

    // do something with the result

The "task" code is inside CalculatorSingleTask.  This implements the Callable interface and we do the work (perform the shape calculations) inside the run() method, and return a CalculationResult.


I was trying to compose some home-grown tests to see if the calculator would throw any exceptions.  These are not deterministic tests, but I still wanted to play with them.

Here is a possible (contrived? Maybe not) use of the CountdownLatch:

//scenario: we need to start up some tasks to do some lengthy work.
//we need them to initialize themselves to a certain point, and then
//to wait ; all of them to wait at the very same point until
//they are all ready to go.
//we need them to coordinate all their actions with the main thread
//and for the main thread to tell them when to really begin
//(once intialized) and for the main thread to wait until all the
//worker threads are done.

//so here is an inner class that will run any Runnable task
//and help us do the above scenaio.

        // this class will run the Runnable tasks (see further down)
        // in a coordinated (with main thread) fashion.
        // the purpose of the List is to have a dynamic way to know
        // what count to initialize the latches with.
        final class LatchedThread extends Thread {
            private CountDownLatch _readyLatch;
            private CountDownLatch _startLatch;
            private CountDownLatch _stopLatch;
            LatchedThread(Runnable runnable, List<LatchedThread> threads){

            void setReadyLatch(CountDownLatch l) { _readyLatch = l; }

            void setStartLatch(CountDownLatch l) { _startLatch = l; }

            void setStopLatch(CountDownLatch l) { _stopLatch = l; }

            // we can not allow the thread to start the task
            // until we make sure the thread has all the necessary
            // notification mechanisms in place
            public void start() {
                if (null==_readyLatch) {
                  throw new IllegalArgumentException("_readyLatch not set"); }
                if (null==_startLatch) {
                  throw new IllegalArgumentException("_startLatch not set"); }
                if (null==_stopLatch) {
                  throw new IllegalArgumentException("_stopLatch not set"); }

            public void run() {
                try {
                    _readyLatch.countDown(); //this thread signals its readiness 
                    _startLatch.await();     //this thread waits to run
                    super.run();             //this thread will now do the task
                    _stopLatch.countDown();  //this thread signals its finished
                } catch (InterruptedException ie) {}

        //these are just specific-task-related - not part of the 
        // general coordinated thread concept being discussed here
        double counter = 0.0;
        double loopMax = 4;

        //again, these below could be any tasks
        //these are just specific-task-related - not part of the 
        // general coordinated thread concept being discussed here
        Runnable deleteAllRequestsTask = () -> {
            for (double dimension=counter; dimension<loopMax; dimension+=0.0001) {

        Runnable addRequestsTask = () -> {
            for (double dimension=counter; dimension<loopMax; dimension+=0.001) {
                calculator.queueCalculationRequest(ShapeName.CIRCLE, CalcType.CALC_AREA, dimension);

        Runnable addRequestsTask2 = () -> {
            for (double dimension=counter; dimension<loopMax; dimension+=0.001) {
                calculator.queueCalculationRequest(ShapeName.SQUARE, CalcType.CALC_VOLUME, dimension);

        Runnable runAllRequestsNoStopTask = () -> {
            for (double dimension=counter; dimension<loopMax; dimension+=0.01) {

        //the main thread sets up the scenario.
        final List<LatchedThread> threads = new ArrayList<LatchedThread>();

        //each thread adds itself to the List, thus we establish a count
        //for the latches
        new LatchedThread(addRequestsTask,threads);
        new LatchedThread(addRequestsTask2,threads);
        new LatchedThread(runAllRequestsNoStopTask,threads);

        CountDownLatch readyLatch = new CountDownLatch(threads.size());
        CountDownLatch startLatch = new CountDownLatch(threads.size());
        CountDownLatch stopLatch = new CountDownLatch(threads.size());

        //conveniently, we can also use the list to finish initializing
        // the threads and setting them off on their own
        //do the initial start...
        for (LatchedThread t : threads) {

        //wait until all threads are in position to start
        // each thread will count down the ready latch, and main thread will
        // move beyond this point
        try { readyLatch.await(); } catch (InterruptedException ie) {}

        //now the main thread will count down the start latch, so that the
        //task threads can all leave the starting gate
        for (Thread t : threads) {

        //now the main thread must wait (to exit)
        //until all the task threads are done
        try { stopLatch.await(); } catch (InterruptedException ie) {}

The Use of Unsynchronized Collections

So I chose to use an underlying HashSet<CalculationRequest>  and HashSet<CalculationResult>, each wrapped by a PendingRequests and CalculatedResults, respectively.  Of course, that (using unsynchronized) might be considered a no-no (gasp).

However, try as I might, I was unable to produce any bad, corrupted states or multiple entries into these HashSets. You can review the JUnit tests for yourself.

None of the code in our Shape Calculator Service iterates through the HashSet objects themselves. The loops interact only with the wrapper objects, and those in turn just do either a get() or put().

I was able to sufficiently demonstrate, I beleive, that entries to the HashSet will only occur once.   Also, if an entry were to be removed, either from the PendingRequests, or CalculatedResults, no harm has been done.

In any case, since we want to move on to Iteration #2 (persistence) we shall leave this one as-is, since we will be replacing the internal HashSets with Hibernate Entities and Repositories, etc. 

Use of Annotations

In the new code, you will notice extensive use of annotations (i.e. Autowired).  Although I used them, it doesn't mean I am endorsing them. It was just a quick, simple way to do what I wanted done. Had I been developing something serious (real), I might have taken a different approach.

Remaining Steps for First Iteration

So I think we have sufficiently tested our Service. The next step is to build a quick, menu-driven, command-line application that will use our ShapeCalculatorService.

Plans for Second Iteration

And after that step, our Second Iteration would be to introduce persistence, taking a look at JPA / Hibernate, MySQL, etc.

You can review all the code changes that allowed the above tests to pass by going here: https://github.com/elicorrales/shape-calc-5/tree/master/shape-calc-p5

Stay tuned for the next article!

Build and launch faster with Okta’s user management API. Register today for the free forever developer edition!

java 8 ,junit ,test driven development ,concurrency ,ooad ,oop

Opinions expressed by DZone contributors are their own.


Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.


{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}