Unit Testing Lab

Unit Testing and Code Coverage

Severe Weather Policy

The goal of this lab is to gain experience writing JUnit tests and using code coverage analysis.

Background

Suppose that JMU had the following severe weather cancellation policy: (here's the real one)

JMU (FAKE) SEVERE WEATHER POLICY

If any of the following conditions are met, campus will be closed:

  • More than 6.0 inches of rain are predicted within the next 24 hours.
  • Wind speeds greater than 70.0 mph are predicted within the next 24 hours.
  • It is predicted that there will be more than 4.0 inches of rain and wind speeds will be above 45.0 mph.

If none of the above conditions are met, the University may still issue a warning and encourage individuals to avoid non-essential outdoor activities. A warning will be issued if any of the following conditions are met:

  • Predicted wind speeds are above 45.0 mph.
  • Predicted precipitation is above 4.0 inches.

The following Java class has been developed as part of a JMU Decision Support system:

The weatherAdvice method provides an implementation of the cancellation policy above. Your job is to develop a set of JUnit tests to confirm that this method is implemented correctly. Your testing must also confirm that the correct exception is thrown when the method receives invalid input.

Part 1 - 100% Coverage

Creating a JUnit Test Class

Once you have set up an Eclipse project containing WeatherUtils.java, create a stub JUnit test class:

  1. Right click the WeatherUtils.java in the Package Explorer and select New -> JUnit Test Case. Make sure New JUnit Jupiter test is selected. Click the Next button, check the box for WeatherUtils, and click Finish (don’t worry if you see a warning against using the default package). This should prompt you to add Junit 5 to the build path and create a new file named WeatherUtilsTest.java.
  2. Write Javadoc comments so that Checkstyle is happy. For example:
    import static org.junit.jupiter.api.Assertions.*;
    import org.junit.jupiter.api.Test;
    
    /**
     * Tests the WeatherUtils class.
    *
    * @author
    * @version
    */
    public class WeatherUtilsTest {
    
        /**
         * Tests the weatherAdvice method.
         */
        @Test
        public void testWeatherAdvice() {
            fail("Not yet implemented");
        }
    
    }
    
  3. Replace the fail method call with an assertion, for example:
    assertEquals("CANCEL", WeatherUtils.weatherAdvice(70.1, 0.0));
    
  4. Run the JUnit test by clicking the green Run button on the toolbar. If you see a green bar on the left, your test passed; if you see a red bar, your test failed.
  5. You can now run coverage analysis against your program by clicking the button to the right of Run (this button should say Coverage in the tooltip when you hover your mouse cursor over it). This should color-code your Java files to indicate which lines are being exercised by your tests. You should also see a new tab labeled Coverage that will display coverage percentages for each file.
    • Note: Consider adjusting your theme away from Dark themes if the highlighting that indicates your test coverage makes the file nearly illegible (for some of the dark themes).
  6. You can select which coverage metric to display (Line Counters, Branch Counters, etc.) by clicking on the View Menu (3 vertical dots icon) in the far right of the Coverage window.

Writing Additional Tests

Complete WeatherUtilsTest.java by writing an appropriate set of test methods. You should be able to check your code coverage from within Eclipse. Keep working until your tests cover 100% of the method.

Once you are confident that your unit tests are sufficient, demonstrate them and your coverage to the instructor or TA. Your goal is to achieve 100% method, statement, and branch coverage.

Note: In order to cover the public class WeatherUtils line, you will need to construct a (useless) WeatherUtils object. The following example "covers" the default constructor. Notice that there is nothing to assert.

/**
 * Tests the constructor (for 100% coverage).
 */
@Test
public void testDefaultConstructor() {
    new WeatherUtils();
}

Testing for Exceptions

There are several ways to test for thrown exceptions in JUnit. The easiest way (see below and also the assertThrows example on the CS wiki ) requires a language feature we haven’t covered yet, so we recommend you use the first example in the CS wiki which involves your junit test catching the expected error, and using junit’s fail method after the line you expected to throw.

The “canonical ” way is to use assertThrows:

/**
 * Test invalid wind speed.
 */
@Test
public void testInvalidWindSpeed() {
    assertThrows(IllegalArgumentException.class, () -> {
        WeatherUtils.weatherAdvice(-1.0, 0.0);
    });
}

The -> operator defines a lambda expression. In programming, a "lambda" is a block of code that can be assigned to a variable. Lambdas allow you to write unnamed methods on the fly and pass them as parameters to other methods. In the above example, we define an unnamed method that takes no paramters () and that invokes weatherAdvice.

Make sure you can answer the following questions:

  1. Why do you need to define a lambda expression? Why can't you just pass the result of WeatherUtils.weatherAdvice to assertThrows?

  2. What does code coverage look like when exceptions are thrown? Is there any way to get 100% coverage for a test class that has exceptions?

Submission

Upload your completed WeatherUtilsTest.java file to Gradescope. To be graded, your code must (1) pass Checkstyle and (2) pass JUnit. If any of your tests fail, you will need to revise and resubmit.

Part 2 - Boundary Cases (Optional)

100% coverage doesn’t necessarily mean that your tests are good enough. High-quality tests should be able to uncover errors in the code that is being tested. The next step is to run your tests against an implementation that is known to contain errors. If your tests are effective, they should indicate that there is a problem with this code. For this part of the lab, you will run your tests against a pre-compiled .class file that we have intentionally coded to contain at least one error.

  1. Create a new Java Project.
  2. Copy your completed WeatherUtilsTest.java file into the new project, and make sure that JUnit is on the classpath (right-click the project, go down to Build Path, and click Add LibrariesJUnit…(ensure JUnit5 is selected)…Finish).
  3. Download the file WeatherUtils.class to anywhere but your Eclipse project folder
  4. Go to Project / Properties and click on Java Build Path.
  5. Click on Libraries -> Classpath -> Add External Class Folder. Navigate to the folder where you downloaded the WeatherUtils.class file.
  6. Press “Okay”. You will see a new section in the Package Explorer window called “Referenced Libraries” and you should see your new folder beneath it.
  7. Execute the tests to confirm that at least one test fails.
Last modified April 30, 2022: practice coding exam (a2ce8c8)