Unit Testing Lesson

Unit Testing: Building with Confidence

Unit testing is a professional software engineering practice that involves writing small, automated tests to verify that a specific "unit" of your code—typically a single method or class—behaves exactly as you expect, without needing a robot.

The Problem: You Can't Always Test on the Robot

Testing code on a physical robot is essential, but it has limitations. The robot might be unavailable, testing can be risky, and it's difficult to test for rare "edge cases." We need a way to test the logic of our code in a controlled, repeatable, and safe environment.

The Solution: Unit Testing

A unit test is a small piece of code written to verify another piece of code. These tests live in the special `src/test/java` folder in your project and are never deployed to the robot. They run on your computer, allowing you to prove that your logic is correct.

The Anatomy of a Test: Arrange, Act, Assert

We use the JUnit framework for testing. A test method is marked with the `@Test` annotation and follows a simple pattern:

  1. Arrange: Set up all the inputs and expected outcomes for the test.
  2. Act: Call the specific method you want to test.
  3. Assert: Compare the actual result from the method call with your expected outcome using methods like `assertEquals()`. If the assertion fails, the test fails.

A Practical Example: Testing a Math Utility

Let's say we have a utility class in our robot code, `MathUtil.java`, that helps us with common calculations. This is a perfect candidate for unit testing.

The "Unit" to be Tested (`src/main/java/...`)

public final class MathUtil {
    // Applies a deadband to a joystick value.
    public static double applyDeadband(double value, double deadband) {
        if (Math.abs(value) > deadband) {
            return value;
        } else {
            return 0.0;
        }
    }
}
    

The Test Code (`src/test/java/...`)

We create a corresponding `MathUtilTest.java` file to verify our logic.

import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;

public class MathUtilTest {
    @Test
    void testApplyDeadbandWithValueInside() {
        // 1. Arrange
        double inputValue = 0.05;
        double deadband = 0.1;
        double expectedValue = 0.0;

        // 2. Act
        double actualValue = MathUtil.applyDeadband(inputValue, deadband);

        // 3. Assert
        assertEquals(expectedValue, actualValue);
    }

    @Test
    void testApplyDeadbandWithValueOutside() {
        // Arrange
        double inputValue = 0.5;
        double deadband = 0.1;
        double expectedValue = 0.5;

        // Act
        double actualValue = MathUtil.applyDeadband(inputValue, deadband);

        // Assert
        assertEquals(expectedValue, actualValue);
    }
}
    

Test Your Knowledge

Question: What are the three steps of the "Arrange, Act, Assert" pattern used in unit testing?