flyingmutant / rapid

Rapid is a modern Go property-based testing library
https://pkg.go.dev/pgregory.net/rapid
Mozilla Public License 2.0
579 stars 25 forks source link

Add custom generator sample in readme #25

Closed rusinikita closed 1 year ago

rusinikita commented 3 years ago

Goal: Show extensibility with custom generator and generator result filter.

Problem: User must read the source code to figure out not simple var generation. He wants the generation of slices, structs, ids.

type testStruct struct {
    id, value int
    name      string
}

func TestExample(t *testing.T) {
    generatedIds := map[int]bool{}

    gen := rapid.Custom(func(rt *rapid.T) testStruct {
        id := rapid.Int().Filter(func(v int) bool {
            return !generatedIds[v]
        }).Draw(rt, "id").(int)

        generatedIds[id] = true

        return testStruct{
            id:    id,
            value: rapid.Int().Draw(rt, "value").(int),
            name:  rapid.String().Draw(rt, "name").(string),
        }
    })

    rapid.Check(t, func(t *rapid.T) {
        generatedIds = map[int]bool{}

                 testSlice1 := rapid.SliceOf(gen).Draw(t, "test_slice1").([]testSamer)
        testSlice2 := rapid.SliceOf(gen).Draw(t, "test_slice2").([]testSamer)

        mergeResult := Merge(testSlice1, testSlice2)
        revertedResult := Remove(mergeResult, testSlice2)

        assert.Equal(t, testSlice, revertedResult)
    })
}

I will try to figure out a better sample for PR.

Is this a good idea?

flyingmutant commented 3 years ago

Thanks for the idea, I'll try to come up with a good thing to include into the README. The existing documentation is probably not very discoverable; maybe giving better names to examples will help a bit as well.

morganhein commented 1 year ago

I would love to use this package, but right now with 90% of the methods not having basic documentation, and rich objects are no-obvious for usage in rapid, I easily deduce how to test my objects, let alone in good faith suggest others on my team use this. I really want to, but without documentation it's just too big a hurdle, especially considering property testing is not an automatic "gimme" in general yet.

flyingmutant commented 1 year ago

Yep, documentation is definitely lacking right now. I hope to find some time around New Year to improve it, with the goal of releasing v1.0 with better docs. By the way, new Make[V any] generator should make it easier to generate a lot of data types with mininal effort.

morganhein commented 1 year ago

@flyingmutant Thank you for all the hard work you do in this open source library, and responding.

I am very interested in using this library so i'm not ready to give up yet. As you mentioned the above function is used to generate a random object. That makes sense and the interface was fairly self-explanatory there.

However, I think I am missing some fundamental knowledge about how this package is intended to work. If you're up for answering a few more questions before your documentation push occurs, that would be welcome, but I won't expect it.

  1. What is the intent behind the draw method? It looks very integral, but after a cursory glance at the method, I cannot intuit the main purpose: https://github.com/flyingmutant/rapid/blob/6b89b51cb185b24408400f80fb3bf8afe5127ab4/generator.go#L40 I'm assuming it's required, but...
  2. Pertaining the generation of scalars: s it required, per this issue (https://github.com/flyingmutant/rapid/issues/2) to generate them using rapid when composing a richer object? I am using go-faker heavily already so would like to re-use that functionality for generation, however it does not seem to be playing well with rapid.
  3. I'm assuming that: rapid requires control over generation of each property down to the scalar so that it knows about each thing and can "reduce" an error's proof. Is this correct?

    I have more questions, but I feel like the above are enough to lead me on the road to self discovery, so i'll stop here.

If I can deduce some of this functionality i'd be willing to put up a PR with some documentation on what i've learned. Either way, thanks again, and happy holidays.

flyingmutant commented 1 year ago

At its core, rapid does a fairly simple thing: generates random data based on the specification you provide, and check properties that you define on the generated data.

Checking is easy -- you simply write if statements and call something like t.Fatalf when things look wrong.

Generating is a bit more involved. When you construct a Generator, nothing happens: Generator is just a specification of how to draw the data you want. When you call draw, rapid will take some bytes from its internal random bitstream, use them to construct the value based on the Generator specification, and track how the random bytes used correspond to the value (and its subparts). This knowledge about the structure of the values being generated, as well as their relationship with the parts of the bitstream allows rapid to intelligently and automatically minify any failure found.

I don't like the name draw very much; I've used it in part because Hypothesis (which I regard very high) does the same.

morganhein commented 1 year ago

@flyingmutant Thanks! This is exactly what I think I was missing. Would you like a PR with some examples/documentation based on my knowledge?

flyingmutant commented 1 year ago

@flyingmutant Thanks! This is exactly what I think I was missing. Would you like a PR with some examples/documentation based on my knowledge?

That'd be great, if you don't mind sending something that might not be merged as-is, but for example used as a basis for the 1.0 docs later.

flyingmutant commented 1 year ago

Closing in favor of #43.