hajimehoshi / ebiten

Ebitengine - A dead simple 2D game engine for Go
https://ebitengine.org
Apache License 2.0
11.02k stars 660 forks source link

debug: Snapshot testing #3010

Closed greenthepear closed 5 months ago

greenthepear commented 5 months ago

Operating System

What feature would you like to be added?

Debugging/testing tools to make snapshot testing easier, possibly by exposing some of the internal code used for /image_test.go.

Why is this needed?

Writing snapshot tests that work with go test for generated images is quite tricky because the game loop needs to be running for ebiten.Image methods like .At() to be accessed. I had to open an issue for my framework to try to get some help.

Projects that have achieved it mention how hard it is: https://github.com/tinne26/etxt/blob/main/testdata_generate.go - this file's comments are insightful about the subject:

// Ebitengine is hard to test with "go test" because you need to create
// standalone programs with a main function and so on. There are multiple
// ways to work around that, and the truth is that for testing etxt there's
// already the gtxt CPU version that allows achieving high test coverage
// with a fairly decent degree of reliability. Still, in order to make sure
// that the gtxt version (CPU rendering) and the default Ebitengine version
// (GPU rendering [notice that rasterization still happens on CPU]) results
// are matching, we can use go:generate to run standalone Ebitengine programs,
// get some raw image results, plug that into basic tests and print it all
// as static test files.

With the number of frameworks and custom libraries people make for ebitengine, a simple way to make sure everything renders right I feel is a must.

hajimehoshi commented 5 months ago

You can run tests by TestMain:

type game struct {
    m    *testing.M
    code int
}

func (g *game) Update() error {
    g.code = g.m.Run()
    return ebiten.Termination
}

func (*game) Draw(*ebiten.Image) {
}

func (*game) Layout(int, int) (int, int) {
    return 320, 240
}

func TestMain(m *testing.M) {
    g := &game{
        m:    m,
        code: 1,
    }
    if err := ebiten.RunGame(g); err != nil {
        panic(err)
    }
    os.Exit(g.code)
}
hajimehoshi commented 5 months ago

As the above solution seems enough to you, let me close this.