If you are going through a book like Crafting Interpreters and just input the code from the book and run it, maybe you don’t need tests. I would also argue you are probably not going to learn the material very well. Writing tests on its own will help build your understanding of the code the book is explaining to you, but the real point of adding unit tests is to support doing the exercises. I am not going to bother explaining the value of unit tests in general or test driven development, but I think it’s worth highlighting that the value of unit tests is particularly clear when you are refactoring and extending an existing codebase (which is basically what you are doing when you implement the features from the exercises).

This is a quick guide to the features from jUnit I used to test jLox. I used the bare minimum number of concepts I felt that I needed to write tests effectively, so this is not a comprehensive guide to jUnit, and not necessarily following best practices. I wrote most of my tests as integration tests once the full interpreter stack was available: feeding Lox code to the interpreter and using print statements to test the output. If I was implementing a real interpreter I would do a much more careful job testing the individual components with unit tests, but those integration tests were good enough to help debug issues that I inevitably introduced.

Test Cases

Test cases are placed in a test directory that sits next to main. Inside it has a directory structure that mirrors main. Tests should be placed in directories mirroring the files they test.

e.g. app/src/test/java/com/craftinginterpreters/lox/InterpreterTest.java

package com.craftinginterpreters.lox;

import org.junit.Test;

public class InterpreterTest {
    @Test public void testStuff() {}
}

Assertions

jUnit comes with a bunch of different types of assertions. I only used a few. They are all pretty self explanatory. Check out the docs for a list of assertion methods available to you: https://junit.org/junit4/javadoc/4.8/org/junit/Assert.html

app/src/test/java/com/craftinginterpreters/lox/InterpreterTest.java

package com.craftinginterpreters.lox;

import org.junit.Test;
import static org.junit.Assert.*;

public class InterpreterTest {
    @Test public void testAsserts() {
        assertEquals("true equals true", true, true);
        assertTrue("true is true", true);
        assertNotNull("true is not null", true);
    }
}

Testing Exceptions

I used one more type of assertion that I thought deserves a little attention. assertThrows allows you to test methods that throw exceptions by passing it a lambda.

app/src/test/java/com/craftinginterpreters/lox/InterpreterTest.java

package com.craftinginterpreters.lox;

import org.junit.Test;
import static org.junit.Assert.*;

import java.lang.UnsupportedOperationException;

public class InterpreterTest {
    @Test public void testException() {
            RuntimeError exception =
                assertThrows(UnsupportedOperationException.class, () ->
                             throw new UnsupportedOperationException("Not implemented"));

            assertEquals("Not implemented", exception.getMessage());
    }
}

Once you have the interpreter was set up, I found that the easiest way to test output from the system was the call the built in Lox function print, and then check that the correct strings were sent to standard output. It turns out it’s pretty easy to capture the standard output and test it with jUnit.

app/src/test/java/com/craftinginterpreters/lox/InterpreterTest.java

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.List;
import org.junit.Test;
import org.junit.Before;

public class InterpreterTest {
    private final PrintStream standardOut = System.out;
    private final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream();

    @Before
    public void setUp() {
        System.setOut(new PrintStream(outputStreamCaptor));
    }

    @Test public void testPrint() {
        System.out.println("lol");
        assertEquals("lol", outputStreamCaptor.toString().trim());
    }
}

I got most of this information from here if you are curious: https://www.baeldung.com/java-testing-system-out-println

Focusing a Test case

If you only want to run a single test case (especially useful if you are doing print debugging and drowning in output!), you can easily do that by passing a command line argument. If you are not using gradle, I assume it’s pretty similar:

./gradlew test --tests InterpreterTest.testPrint

Setting up a Mock Clock

At some point Crafting Interpreters will have you implement a clock function that grabs the system time and converts in to seconds. As I struggled to find a a way to override and mock out System.currentTimeMillis(), I realized that instead I could just set up the interpreter to accept a clock object as a constructor argument (defaulting to the system clock if none is provided), so I could use a mock clock in the tests. This is kinda overkill, but if you wanted to have this in your life here are the clock classes I used to set that up. Just create an instance of whichever clock and call clock.getCurrentTimeSeconds() instead of calling System.currentTimeMillis() directly inside the interpreter code. Inside your tests pass a TestClock to your interpreter, and then you can control the time with tick().

app/src/main/java/com/craftinginterpreters/lox/LoxClock.java

package com.craftinginterpreters.lox;

import java.util.List;

interface LoxClock {
    double getCurrentTimeSeconds();
}

app/src/main/java/com/craftinginterpreters/lox/SystemClock.java

package com.craftinginterpreters.lox;

import java.util.List;

class SystemClock implements LoxClock {
    public double getCurrentTimeSeconds() {
        return (double) System.currentTimeMillis() / 1000.0;
    }
}

app/src/main/java/com/craftinginterpreters/lox/TestClock.java

package com.craftinginterpreters.lox;

import java.util.List;

class TestClock implements LoxClock {
    int currentTime = 0;

    public double getCurrentTimeSeconds() {
        return (double)currentTime;
    }

    public void tick() {
        tick(1);
    }

    public void tick(int seconds) {
        currentTime += seconds;
    }
}