leanovate / gopter

GOlang Property TestER
MIT License
598 stars 40 forks source link

Options to improve test runtime #49

Closed blt closed 5 years ago

blt commented 5 years ago

Hi folks,

I've got a test whose runtime is real slow and I wonder if I'm doing something very wrong. Or, if I'm not doing something wrong if there are any options I've got to improve the test runtime. Here's a representative example of my actual tests:

package main

import (
        "github.com/leanovate/gopter"
        "github.com/leanovate/gopter/arbitrary"
        "github.com/leanovate/gopter/gen"
        "reflect"
        "testing"
)

type TestBook struct {
        Title   string
        Content string
}

func genTestBook() gopter.Gen {
        return gen.Struct(reflect.TypeOf(&TestBook{}), map[string]gopter.Gen{
                "Title":   gen.AlphaString(),
                "Content": gen.AlphaString(),
        })
}

type TestLibrary struct {
        Name       string
        Librarians uint8
        Books      []TestBook
}

func genTestLibrary() gopter.Gen {
        return gen.Struct(reflect.TypeOf(&TestLibrary{}), map[string]gopter.Gen{
                "Name":       gen.AlphaString(),
                "Librarians": gen.UInt8Range(1, 255),
                "Books":      gen.SliceOf(genTestBook()),
        })
}

type CityName = string
type TestCities struct {
        Libraries map[CityName][]TestLibrary
}

func genTestCities() gopter.Gen {
        return gen.StructPtr(reflect.TypeOf(&TestCities{}), map[string]gopter.Gen{
                "Libraries": gen.MapOf(gen.AlphaString(), gen.SliceOf(genTestLibrary())),
        })
}

func TestLibraries(t *testing.T) {
        parameters := gopter.DefaultTestParameters()
        parameters.MinSuccessfulTests = 10
        parameters.MaxSize = 4
        arbitraries := arbitrary.DefaultArbitraries()
        arbitraries.RegisterGen(genTestCities())

        properties := gopter.NewProperties(parameters)

        properties.Property("no unsupervised libraries", arbitraries.ForAll(
                func(tc *TestCities) bool {
                        for _, libraries := range tc.Libraries {
                                for _, library := range libraries {
                                        if library.Librarians == 0 {
                                                return false
                                        }
                                }
                        }
                        return true
                },
        ))

        properties.TestingRun(t)
}

My test builds a tree structure -- TestCities -> TestLibrary -> TestBook -- and then runs a property to assert that no libraries are unsupervised. By construction this property will always pass: genTestLibrary does not allow for TestLibrary.Libarians to be 0. As expected, the property does pass but the runtime is around 30 seconds on my host. An example:

> go test
+ no unsupervised libraries: OK, passed 10 tests.
Elapsed time: 356.71µs
PASS
ok      _/Users/btroutwine/go/propexample   26.830s

Am I doing anything obviously wrong here?

blt commented 5 years ago

Oh, I'm declaring my own generators because if I rely on the inferred generator the value of TestCities.Libraries is always an empty map? I would expect this to fail, but it does not.

package main

import (
        "github.com/leanovate/gopter"
        "github.com/leanovate/gopter/arbitrary"
        "testing"
)

type TestBook struct {
        Title   string
        Content string
}

type TestLibrary struct {
        Name       string
        Librarians uint8
        Books      []TestBook
}

type CityName = string
type TestCities struct {
        Libraries map[CityName][]TestLibrary
}

func TestLibraries(t *testing.T) {
        parameters := gopter.DefaultTestParameters()
        parameters.MinSuccessfulTests = 1000000
        arbitraries := arbitrary.DefaultArbitraries()

        properties := gopter.NewProperties(parameters)

        properties.Property("libraries always empty", arbitraries.ForAll(
            func(tc *TestCities) bool {
            return len(tc.Libraries) == 0
                },
        ))

    properties.TestingRun(t)
}
untoldwind commented 5 years ago

Just a short heads up since I was on vacation:

  1. It is indeed true, that DefaultArbitraries do not include "map[something]anything", i.e. in the above example Libraries will be always empty, unless you register a generator for "map[CityName][]TestLibrary" I'll try to add this feature (though refection with map is pretty painful).

  2. The long runtime actually seems not to be in "TestingRun" but rather somewhere in "genTestCities", i.e. while building the generator-tree ... this will require some research.

untoldwind commented 5 years ago

Added both of your examples to the tests (https://github.com/leanovate/gopter/blob/master/example_libraries_test.go). They should now only take a couple of milliseconds.

Otherwise just reopen the issue