JV
GO

Java to Go

10 lessons

Progress0%
1Variables & Types2Classes → Structs3Interfaces4Exception Handling → Error Values5Threads → Goroutines6Slices and Collections7Packages and Modules8Testing9Go Standard Library10Context and Cancellation
All Mirror Courses
JV
GO
Testing
MirrorLesson 8 of 10
Lesson 8

Testing

Go's built-in testing vs JUnit 5 — table-driven tests, benchmarks

Introduction

In this lesson, you'll learn about testing in Go. Coming from Java, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.

Mirror Card
JV
From Java:

In Java, you're familiar with go's built-in testing vs junit 5 — table-driven tests, benchmarks.

GO
In Go:

Go has its own approach to go's built-in testing vs junit 5 — table-driven tests, benchmarks, which we'll explore step by step.

The Go Way

Let's see how Go handles this concept. Here's a typical example:

GO
Go Example
// calculator_test.go
package calculator

import (
    "testing"
    "errors"
)

// Basic test (= @Test)
func TestAdd(t *testing.T) {
    got := Add(1, 2)
    if got != 3 {
        t.Errorf("Add(1,2) = %d; want 3", got)
    }
}

// Table-driven test (= @ParameterizedTest @CsvSource)
func TestAddParameterized(t *testing.T) {
    tests := []struct {
        a, b, want int
    }{
        {1, 2, 3},
        {4, 5, 9},
        {-1, 1, 0},
    }
    for _, tc := range tests {
        t.Run(fmt.Sprintf("%d+%d", tc.a, tc.b), func(t *testing.T) {
            if got := Add(tc.a, tc.b); got != tc.want {
                t.Errorf("got %d; want %d", got, tc.want)
            }
        })
    }
}

// Test error case
func TestDivideByZero(t *testing.T) {
    _, err := Divide(1, 0)
    if err == nil {
        t.Error("expected error for divide by zero")
    }
}

// TestMain = @BeforeAll/@AfterAll for the whole package
func TestMain(m *testing.M) {
    setup()
    code := m.Run()
    teardown()
    os.Exit(code)
}

// Benchmark (no JUnit equivalent)
func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(1, 2)
    }
}

// go test ./...             = mvn test
// go test -run TestAdd ./.. = mvn test -Dtest=TestAdd
// go test -bench=.          = no JUnit equivalent

Comparing to Java

Here's how you might have written similar code in Java:

JV
Java (What you know)
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;

class CalculatorTest {

    private Calculator calc;

    @BeforeEach
    void setUp() {
        calc = new Calculator();
    }

    @Test
    void testAdd() {
        assertEquals(3, calc.add(1, 2));
        assertEquals(0, calc.add(-1, 1));
    }

    @ParameterizedTest
    @CsvSource({"1,2,3", "4,5,9", "-1,1,0"})
    void testAddParameterized(int a, int b, int expected) {
        assertEquals(expected, calc.add(a, b));
    }

    @Test
    void testDivideByZero() {
        assertThrows(ArithmeticException.class,
            () -> calc.divide(1, 0));
    }

    @AfterEach
    void tearDown() { /* cleanup */ }
}

// Run: mvn test
// Run specific: mvn test -Dtest=CalculatorTest#testAdd
Mirror Card
JV
From Java:

You may be used to different syntax or behavior.

GO
In Go:

No framework needed — 'go test' is built into the toolchain

Mirror Card
JV
From Java:

You may be used to different syntax or behavior.

GO
In Go:

Table-driven tests (struct slice + t.Run) replace @ParameterizedTest @CsvSource

Mirror Card
JV
From Java:

You may be used to different syntax or behavior.

GO
In Go:

TestMain replaces @BeforeAll/@AfterAll for package-level setup/teardown

Mirror Card
JV
From Java:

You may be used to different syntax or behavior.

GO
In Go:

No assertions library — use manual if checks with t.Error/t.Errorf/t.Fatal

Mirror Card
JV
From Java:

You may be used to different syntax or behavior.

GO
In Go:

Benchmarks (BenchmarkXxx) are built-in — JUnit needs external benchmark library

Step-by-Step Breakdown

1. Test Function Structure

Go test functions are TestXxx(t *testing.T). No class, no annotations. Files end in _test.go — excluded from production build.

JV
Java
@Test
void testAdd() {
    assertEquals(3, calc.add(1, 2));
}
GO
Go
func TestAdd(t *testing.T) {
    if got := Add(1, 2); got != 3 {
        t.Errorf("Add(1,2) = %d; want 3", got)
    }
}

2. Table-Driven Tests

Table-driven tests are the Go idiom for parametrized testing. Each test case is a struct — t.Run creates a sub-test for each.

JV
Java
@ParameterizedTest
@CsvSource({"1,2,3", "4,5,9"})
void test(int a, int b, int want) { assertEquals(want, add(a,b)); }
GO
Go
tests := []struct{ a,b,want int }{{1,2,3},{4,5,9}}
for _, tc := range tests {
    t.Run(fmt.Sprintf("%d+%d",tc.a,tc.b), func(t *testing.T){
        if got:=Add(tc.a,tc.b); got!=tc.want { t.Fail() }
    })
}

3. Error vs Fatal

t.Error marks failed and continues; t.Fatal marks failed and stops. Unlike JUnit where assertEquals always stops on failure.

JV
Java
assertEquals(expected, actual);  // JUnit stops here on fail
GO
Go
if result == nil { t.Fatal("nil result") }  // stops
if result.Name != "Bob" {
    t.Errorf("name=%q; want Bob", result.Name) // continues
}

4. Benchmarks

Go benchmarks measure performance with b.N iterations — the framework adjusts N for stable results.

JV
Java
// JUnit: @BenchmarkMode(Mode.AverageTime) with JMH library
GO
Go
func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(1, 2)
    }
}
// go test -bench=. -benchmem
// BenchmarkAdd-8   1000000000   0.25 ns/op   0 B/op
Rule of Thumb
Call b.ResetTimer() after setup code so setup time isn't included in the benchmark measurement.

Common Mistakes

When coming from Java, developers often make these mistakes:

  • No framework needed — 'go test' is built into the toolchain
  • Table-driven tests (struct slice + t.Run) replace @ParameterizedTest @CsvSource
  • TestMain replaces @BeforeAll/@AfterAll for package-level setup/teardown
Common Pitfall
Don't assume Go works exactly like Java. While the concepts may be similar, the syntax and behavior can differ significantly.

Key Takeaways

  • _test.go files; TestXxx(t *testing.T) — no framework or annotations
  • Table-driven: struct slice + t.Run = @ParameterizedTest equivalent
  • t.Error continues on fail; t.Fatal stops — choose based on whether remaining checks are valid
  • Benchmarks built-in: BenchmarkXxx(b *testing.B); go test -bench=. -benchmem
Rule of Thumb
The best way to learn is by doing. Try rewriting some of your Java code in Go to practice these concepts.
PreviousNext