Home > Software engineering >  How do you write a test for sort.Slice()?
How do you write a test for sort.Slice()?

Time:07-21

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)
            }
        })
    }
}
  • Related