05open 25 min

Testing in Rust: built-in tests for developers used to Go's standard package

Map Go's standard-library testing habits onto Rust's #[test], assertions, and nearby test modules.

by the end of this lesson you can

  • Uses #[test] and assertion macros
  • Keeps multiple cases easy to scan
  • Stays close to Rust's built-in testing style

Overview

Go developers already know a standard-library-first testing culture. Rust feels similar in spirit, but the structure is different: test modules, assertion macros, and stronger integration with the language's type system.

In Go, you often

write tests in _test.go files with explicit case loops and standard-library helpers.

In Rust, the common pattern is

to use #[test], assertion macros, and inline or nearby test modules that stay close to the code they verify.

why this difference matters

This lesson should feel reassuring. Rust testing is explicit in much the same way Go testing is, but the syntax and organization are different.

Go

func TestSquare(t *testing.T) {
    if square(4) != 16 {
        t.Fatal("bad result")
    }
}

Rust

#[test]
fn square_returns_16() {
    assert_eq!(square(4), 16);
}

Deeper comparison

Go version

func TestSlugify(t *testing.T) {
    cases := []struct {
        in   string
        want string
    }{{"Hello World", "hello-world"}}

    for _, tc := range cases {
        if got := slugify(tc.in); got != tc.want {
            t.Fatalf("got %q, want %q", got, tc.want)
        }
    }
}

Rust version

#[test]
fn slugify_cases() {
    let cases = [("Hello World", "hello-world")];

    for (input, expected) in cases {
        assert_eq!(slugify(input), expected);
    }
}

Reflect

What feels familiar to a Go developer in Rust testing, and what changes in the way tests are organized?

what a strong answer notices

A strong answer mentions standard-tooling familiarity, explicit cases, and closer integration with modules and macros.

Rewrite

Rewrite this Go table-style test into an idiomatic Rust test.

Rewrite this Go

func TestDouble(t *testing.T) {
    cases := []struct{ in, want int }{{2, 4}, {3, 6}}
    for _, tc := range cases {
        if got := double(tc.in); got != tc.want {
            t.Fatalf("got %d, want %d", got, tc.want)
        }
    }
}

what good looks like

  • Uses #[test] and assertion macros
  • Keeps multiple cases easy to scan
  • Stays close to Rust's built-in testing style

Practice

Design a Rust test module for request validation with both valid and invalid cases.

success criteria

  • Groups related tests clearly
  • Uses assertions that make the intended contract obvious
  • Keeps setup explicit rather than overly abstract

Common mistakes

  • Expecting _test.go file layout instead of learning Rust's test-module conventions.
  • Recreating Go helpers before understanding Rust's simpler built-in patterns.
  • Treating macros as magic instead of ordinary testing tools.

takeaways

  • This lesson should feel reassuring. Rust testing is explicit in much the same way Go testing is, but the syntax and organization are different.
  • A strong answer mentions standard-tooling familiarity, explicit cases, and closer integration with modules and macros.
  • Groups related tests clearly