Write Unit Tests First and Use AI to Generate Code That Passes Them

Write Unit Tests First and Use AI to Generate Code That Passes Them

Introduction

Many developers turn to AI to generate unit tests for existing code, but what if we flipped the process? Instead of using AI to write tests, we can write unit tests first and use AI to generate the implementation that satisfies them. This approach aligns with test-driven development (TDD), ensuring that our code meets predefined requirements and is robust from the start.

In this article, we’ll explore how to use Java with Gradle and JUnit to write unit tests first, then leverage AI to generate code that fulfills the tests.


Why Write Tests First?

Writing tests before implementation has several advantages:

  • Ensures clarity: Defines what the code should do before implementation.
  • Improves reliability: Helps prevent regressions by ensuring new changes don’t break existing functionality.
  • Encourages modular design: Leads to better-structured and maintainable code.
  • Guides AI code generation: AI can focus on meeting the test requirements instead of generating arbitrary code.

Let’s see this in action.


Setting Up Java, Gradle, and JUnit

To follow along, ensure you have:

  • Java installed (JDK 11+ recommended)
  • Gradle installed
  • An AI coding assistant (like ChatGPT or GitHub Copilot)

Create a new Gradle project and add JUnit dependencies.

build.gradle (Kotlin DSL)

plugins {
    id 'java'
}

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2'
}

test {
    useJUnitPlatform()
}

Step 1: Writing Unit Tests First

We’ll start by writing a simple unit test for a Calculator class that doesn’t exist yet.

CalculatorTest.java

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

class CalculatorTest {
    @Test
    void testAddition() {
        Calculator calculator = new Calculator();
        assertEquals(5, calculator.add(2, 3));
    }
    
    @Test
    void testSubtraction() {
        Calculator calculator = new Calculator();
        assertEquals(1, calculator.subtract(3, 2));
    }
}

This test defines a Calculator class with add and subtract methods, even though it hasn’t been implemented yet.


Step 2: Using AI to Generate Code That Passes the Tests

Now, we can ask AI to generate a Calculator class that satisfies the tests.

AI-Generated Calculator.java

class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
    
    public int subtract(int a, int b) {
        return a - b;
    }
}

By running our tests, we ensure the AI-generated code meets the expected functionality.


Step 3: Running the Tests

Run the tests using Gradle:

gradle test

Expected output:

BUILD SUCCESSFUL

If the tests pass, we have successfully driven our development using tests first!


Scaling Up: More Complex Examples

Now that we have a basic example, let’s scale up with another feature: a method to calculate factorial.

New Test in CalculatorTest.java

@Test
void testFactorial() {
    Calculator calculator = new Calculator();
    assertEquals(120, calculator.factorial(5));
}

AI-Generated Code:

class Calculator {
    public int factorial(int n) {
        if (n == 0) return 1;
        return n * factorial(n - 1);
    }
}

Running gradle test again should pass all tests.


Conclusion

By writing tests first and using AI to generate the implementation, we:

  • Ensure that our code meets clear, testable requirements.
  • Avoid unnecessary or overly complex AI-generated code.
  • Maintain a structured, test-driven approach to development.

Next time you’re using AI in coding, try flipping the process—write the test cases first and let AI fill in the gaps!