← all work

Backend · Compilers

JUnit 4 → 5 Translator

A source-to-source translator that automatically migrates JUnit 4 test suites to JUnit 5 using compiler techniques — an ANTLR grammar for Java that parses and rewrites tests.

Role
Author · open-source
Stack
  • Java
  • ANTLR
  • Compiler design
  • AST rewriting

The problem

At Encora, a large legacy monolith carried a test suite written entirely in JUnit 4. Migrating it to JUnit 5 by hand would have meant hundreds of hours of tedious, error-prone refactoring — and many of the changes aren't simple find-and-replace. Renaming @Before to @BeforeEach is easy; faithfully rewriting @Test(expected = ...) into assertThrows, or rules into extensions, is a structural transformation that text substitution gets wrong.

The approach: a real compiler front-end

I built an open-source source-to-source translator that treats the migration as a compiler problem. It uses an ANTLR grammar for Java to parse each test file into a parse tree, walks that tree to recognize JUnit 4 constructs, and rewrites them into their JUnit 5 equivalents while preserving the surrounding code and formatting.

Java source ANTLR lexer + parser tree-walk & rewrite rules JUnit 5 source

Before / after

The transform handles the structural cases, not just the cosmetic ones:

JUnit 4
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;

public class OrderTest {
    @Before
    public void setUp() { ... }

    @Test(expected = IllegalStateException.class)
    public void rejectsEmptyCart() {
        new Order().checkout();
    }
}
JUnit 5
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class OrderTest {
    @BeforeEach
    void setUp() { ... }

    @Test
    void rejectsEmptyCart() {
        assertThrows(IllegalStateException.class,
            () -> new Order().checkout());
    }
}

Why it matters

  • Parsing the real grammar makes the rewrites reliable where regex-based scripts break — expected exceptions, parameterized tests, rules, and static imports all transform correctly.
  • It runs across an entire codebase at once, turning a multi-week manual effort into a repeatable command.
  • It's the practical side of CS fundamentals I enjoy: grammars, parse trees, and AST rewriting applied to a concrete engineering cost.

Outcome

The translator eliminated hundreds of hours of manual refactoring on the migration, and is available as open source on GitHub.