-1

I want to shorten my test code by using a struct for test input and another struct for test desired output for complex tests.

I have my test code like this and it works:

func TestMyTest(*testing.T) {
    type Test struct {
        n      int
        items  map[string][]int
        order  int
        expect []string
    }
    tests := []Test{
        {
            n: 3,
            items: map[string][]int{
                "item1": []int{1, 2},
                "item2": []int{3, 4},
                "item3": []int{5, 6},
            },
            order:  1,
            expect: []string{"item1"},
        },
        // ... more test cases
    }
    for testNo, test := range tests {
        output := myTest(test.n, test.items, test.order)
        desire := test.expect
        fmt.Printf("Test %v ", testNo+1)
        if reflect.DeepEqual(output, desire) {
            fmt.Println("PASS")
        } else {
            fmt.Println("FAIL")
        }
        fmt.Println("Got:", output, "Expected:", desire)
    }
}

My dummy function:

func myTest(n int, items map[string][]int, order int) []string {
    res := []string{"item1"}
    return res
}

However, if I have more complex input and output, I don't want to type all the parameters out, but instead, I'd like to group them in 1 struct like this:

func TestMyTest2(*testing.T) {
    type TestInput struct {
        n     int
        items map[string][]int
        order int
    }
    type TestExpect struct {
        expect []string
    }
    type Test struct {
        input  TestInput
        expect TestExpect
    }
    tests := []Test{
        {
            input: TestInput{
                n: 3,
                items: map[string][]int{
                    "item1": []int{10, 15},
                    "item2": []int{3, 4},
                    "item3": []int{17, 8},
                },
                order: 1,
            },
            expect: TestExpect{
                []string{"item3"},
            },
        },
        // ... more test cases
    }
    for testNo, test := range tests {
        output := myTest(test.input) // ERROR: have (TestInput) want (int, map[string][]int, int)
        desire := test.expect
        fmt.Printf("Test %v ", testNo+1)
        if reflect.DeepEqual(output, desire) {
            fmt.Println("PASS")
        } else {
            fmt.Println("FAIL")
        }
        fmt.Println("Got:", output, "Expected:", desire)
    }
}

The error I have:

have (TestInput) want (int, map[string][]int, int)

which makes sense but I've been struggling to "spread" the values inside my TestInput to pass in my function. What I'm trying to do is like in JS, I can do ...params to "spread" or in Python **params to "unpack".

I've looked at the answers here, here, here and here but still haven't been able to figure this out.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Viet
  • 6,513
  • 12
  • 42
  • 74
  • 5
    You can't "spread" them. You have to e.g. `myTest(test.input.n, test.input.items, test.input.order)`. – Adrian Oct 23 '20 at 16:16
  • 1
    Thank you @Adrian. Is there any way to shorten that? It seems like a lot of repetitive code though. I now have a func that takes in 9 parameters. In JS, I can just do `...params` to "spread" or in Python `**params` to "unpack" – Viet Oct 23 '20 at 16:24

1 Answers1

1

Use the reflect package to spread the arguments:

func spread(fn interface{}, args interface{}) interface{} {
    var in []reflect.Value
    s := reflect.Indirect(reflect.ValueOf(args))
    for i := 0; i < s.NumField(); i++ {
        in = append(in, s.Field(i))
    }
    out := reflect.ValueOf(fn).Call(in)
    return out[0].Interface()
}

The fields in TestInput must be exported for this to work.

Here's how to use it:

for i, test := range tests {
    output := spread(myTest, test.input)
    desire := test.expect.expect
    if !reflect.DeepEqual(output, desire) {
        t.Errorf("%d: got %v, want %v", i, output, desire)
    }
}

Run it on the Go Playground.

I think it's simpler to write out the arguments than to use reflection trickery. The code with the arguments written out is faster than the reflection code, but that may not matter for a test.

  • Thank you @iLoveRelection, this is hepful! – Viet Oct 23 '20 at 16:45
  • 1
    "it's simpler to write out the arguments than to use reflection trickery" I agree. However, what if you have 9+ parameters and their names are long? I'm looking for away to make it simpler. – Viet Oct 23 '20 at 16:47
  • 1
    @Viet I add a disclaimer about using the `reflect` package to avoid downvotes. –  Oct 23 '20 at 17:25
  • It's not just simpler to write it out, it's performs better, being that the parameter list is evaluated once at compile time, as opposed to evaluating at runtime every time the function is called using reflection. – Adrian Oct 23 '20 at 18:33
  • @Adrian The performance of using the reflect package is often not an issue for tests. It's very common to use reflect.DeepEqual in a test instead of writing the comparison out. –  Oct 23 '20 at 18:42
  • True. Just not something anyone should get into the habit of using to recreate functionality from other languages. As long as it's kept to just unit tests it's probably fine. – Adrian Oct 23 '20 at 18:44