franela / goblin

Minimal and Beautiful Go testing framework
MIT License
884 stars 79 forks source link

Can't range over data set inside Describe block #63

Closed twhit027 closed 6 years ago

twhit027 commented 6 years ago

Running table-driven tests with proper descriptions seems buggy (if it's even supported). If you range over test data within a Describe block, it runs each test with the very last data set in the range, giving invalid test results.

// Example of how it has to be done, but requires less-than optimal scoping
func TestFoo(t *testing.T) {
    g := Goblin(t)

    tests := []struct {
        Description string
        Input       bool
        Output      bool
    }{
        {
            Description: `should fail`,
            Input:       false,
            Output:      false,
        },
        {
            Description: `should pass`,
            Input:       false,
            Output:      true,
        },
    }

    for _, t := range tests {
        g.Describe(`foo()`, func() {
            g.It(t.Description, func() {
                out := foo(t.Input)
                g.Assert(out).Equal(t.Output)
            })
        })
    }
}

The above example is how you're required to write table-driven tests, which makes test output ugly by outputting the describe block for every iteration, and also makes scoping of tests more difficult (in terms of After, Before, etc). In this case, the first test will fail & the second test will pass, as expected.

// Example of how describe blocks should work, but breaks range
func TestFoo(t *testing.T) {
    g := Goblin(t)

    g.Describe(`foo()`, func() {
        tests := []struct {
            Description string
            Input       bool
            Output      bool
        }{
            {
                Description: `should fail`,
                Input:       false,
                Output:      false,
            },
            {
                Description: `should pass`,
                Input:       false,
                Output:      true,
            },
        }

        for _, t := range tests {
            g.It(t.Description, func() {
                out := foo(t.Input)
                g.Assert(out).Equal(t.Output)
            })
        }
    })
}

The second case is how I would expect the nesting of Describe blocks to work, but in this case, the tests will not run properly. Both tests will pass, running both iterations of the test with the last item in the data set. This is not a valid test result, as we're expecting the first test to fail.

The foo function in this example just accepts a boolean and inverts it.

func foo(input bool) bool {
    return !input
}

Is there any reason why tests using data ranges within a Describe block should break?

marcosnils commented 6 years ago

@twhit027 your second example is the correct approach. The reason why it's not working for you is not because of Goblin, but the fact that Go reuses the same variable in loops, check https://github.com/golang/go/wiki/CommonMistakes for further info.

you need to change your range loop to something like

        for _, run := range tests {
            var t = run
            g.It(t.Description, func() {
                out := foo(t.Input)
                g.Assert(out).Equal(t.Output)
            })
        }
twhit027 commented 6 years ago

Well, TIL! Thanks for the knowledge!