Given the how sort.Slice(array, func(int, int) bool)
uses the array in the passed in function, how would you write a test for the anonymous function?
The example code from the go documentation for sort.Slice()
is:
package main
import (
"fmt"
"sort"
)
func main() {
people := []struct {
Name string
Age int
}{
{"Gopher", 7},
{"Alice", 55},
{"Vera", 24},
{"Bob", 75},
}
sort.Slice(people, func(i, j int) bool { return people[i].Name < people[j].Name })
fmt.Println("By name:", people)
sort.Slice(people, func(i, j int) bool { return people[i].Age < people[j].Age })
fmt.Println("By age:", people)
}
If you broke out the anonymous functions into two separate functions (e.g., comparePeopleByName(i, j int)
and comparePeopleByAge(i, j, int)
) how would you test them?
It seems like such a weird idea to tie the function to the closure.
Note: I'd consider it bad form to test sort.Slice()
itself; it ships with the language and shouldn't (need to?) be tested.
CodePudding user response:
Move the functionality that you want to test to a named function. Test that function.
type Person struct {
Name string
Age int
}
func personLessByName(people []Person, i, j int) bool {
return people[i].Name < people[j].Name
}
func personLessByAge(people []Person, i, j int) bool {
return people[i].Age < people[j].Age
}
Use the function in the call to sort.Slice:
sort.Slice(people, func(i, j int) bool { return personLessByName(people, i, j) })
sort.Slice(people, func(i, j int) bool { return personLessByAge(people, i, j) })
Here'a test:
func TestComparePeople(t *testing.T) {
cases := []struct {
desc string
a, b Person
fn func([]Person, int, int) bool
expected bool
}{
{"name a < b", Person{"bill", 10}, Person{"melinda", 20}, personLessByName, true},
{"name a > b", Person{"melinda", 30}, Person{"bill", 20}, personLessByName, false},
{"name a = b", Person{"melinda", 90}, Person{"melinda", 90}, personLessByName, false},
{"age a < b", Person{"melinda", 10}, Person{"bill", 20}, personLessByAge, true},
{"age a > b", Person{"melinda", 30}, Person{"bill", 20}, personLessByAge, false},
{"age a = b", Person{"melinda", 90}, Person{"bill", 90}, personLessByAge, false},
}
for _, c := range cases {
people := []Person{c.a, c.b}
got := c.fn(people, 0, 1)
if c.expected != got {
t.Errorf("%s: expected %v, got %v", c.desc, c.expected, got)
}
}
}
CodePudding user response:
After messing around a little, I wrote this bit of code. It uses a factory to create the method so that the slice is contained in the closure for the function.
I stuck it in a gist for easy playing around with: https://gist.github.com/docwhat/e3b13265d24471651e02f7d7a42e7d2c
// main.go
package main
import (
"fmt"
"sort"
)
type Person struct {
Name string
Age int
}
func comparePeopleByName(people []Person) func(int, int) bool {
return func(i, j int) bool {
return people[i].Name < people[j].Name
}
}
func comparePeopleByAge(people []Person) func(int, int) bool {
return func(i, j int) bool {
return people[i].Age < people[j].Age
}
}
func main() {
people := []Person{
{"Gopher", 7},
{"Alice", 55},
{"Vera", 24},
{"Bob", 75},
}
sort.Slice(people, comparePeopleByName(people))
fmt.Println("By name:", people)
sort.Slice(people, comparePeopleByAge(people))
fmt.Println("By age:", people)
}
// main_test.go
package main
import "testing"
func TestComparePeopleByName(t *testing.T) {
testCases := []struct {
desc string
a, b Person
expected bool
}{
{"a < b", Person{"bob", 1}, Person{"krabs", 2}, true},
{"a > b", Person{"krabs", 1}, Person{"bob", 2}, false},
{"a = a", Person{"plankton", 1}, Person{"plankton", 2}, false},
}
for _, testCase := range testCases {
t.Run(testCase.desc, func(t *testing.T) {
people := []Person{testCase.a, testCase.b}
got := comparePeopleByName(people)(0, 1)
if testCase.expected != got {
t.Errorf("expected %v, got %v", testCase.expected, got)
}
})
}
}
func TestComparePeopleByAge(t *testing.T) {
testCases := []struct {
desc string
a, b Person
expected bool
}{
{"a < b", Person{"sandy", 10}, Person{"patrick", 20}, true},
{"a > b", Person{"sandy", 30}, Person{"patrick", 20}, false},
{"a = b", Person{"sandy", 90}, Person{"patrick", 90}, false},
}
for _, testCase := range testCases {
t.Run(testCase.desc, func(t *testing.T) {
people := []Person{testCase.a, testCase.b}
got := comparePeopleByAge(people)(0, 1)
if testCase.expected != got {
t.Errorf("expected %v, got %v", testCase.expected, got)
}
})
}
}