Skip to content

Property Based Tests#9

Merged
mhanberg merged 24 commits intomhanberg:mainfrom
clark-lindsay:add-property-based-tests
May 5, 2023
Merged

Property Based Tests#9
mhanberg merged 24 commits intomhanberg:mainfrom
clark-lindsay:add-property-based-tests

Conversation

@clark-lindsay
Copy link
Copy Markdown
Contributor

@clark-lindsay clark-lindsay commented Apr 28, 2023

Description

Adds property-based tests with StreamData to improve confidence in the test suite and make the library more reliable and easier to extend.

Most of the property based testing is condensed around a single property test that asserts that an arbitrary {schematic, data} tuple, where the data and the schematic are compatible and will successfully unify/2, satisfies the following pseudocode: data |> unify |> dump == data

As this is a draft, i am open to any and all feedback

Closes #8

Copy link
Copy Markdown
Owner

@mhanberg mhanberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have to pull this down and play around with it, nice work so far!

Comment thread test/schematic_test.exs Outdated
Comment thread test/schematic_test.exs Outdated
@mhanberg
Copy link
Copy Markdown
Owner

Does it make sense to organize the prop tests based on "properties" of the code rather than by function?

like, the property is that inputs can be unified and dumped back and forth, so the stream data generator generates random arbitrary inputs/schematics and ensures that input == unify(schematic, input) |> then(&dump(schematic, &1))

I assume it's currently broken up by function because you were just incrementally working through them, but before we merge it is worth considering whether combining them into fewer properties makes sense.

Comment thread test/support/generators.ex Outdated
Comment thread test/support/generators.ex Outdated
|> StreamData.one_of()
end

def from_schematic(%Schematic{kind: kind}) do
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is closer to what we want.

I think there should probably be some function like gen or generate that takes a schematic, and then spits out the stream data equivalent.

I think you still need to move the stream data generator for the blueprintable map schematic into this.

Then, I feel like you would be able to to more or less have a single property that generates an arbitrary schematic that can be any depth and combination of them, and test that it unifies and dumps back to itself.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this functionality can probably be re-used to make the user facing stream data API

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i just added something that is getting towards this idea. it's a little rough, and doesn't cover key transformation for maps, but with this is place we can probably drop a lot of the smaller property tests that i wrote along the way to this. i'll take a look at that tomorrow

Comment thread test/schematic_test.exs Outdated
check all(
{blueprint, input} <-
StreamData.bind(
StreamData.map_of(StreamData.binary(), Generators.schematic(), min_length: 1),
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

map schematics can technically have any value as a key in the blueprint, so we would want to expand this generator to reflect that.

We'll also want it to generate map blue prints that reflect the key transformation capabilities: https://hexdocs.pm/schematic/Schematic.html#map/1-transforming-keys

@clark-lindsay
Copy link
Copy Markdown
Contributor Author

Does it make sense to organize the prop tests based on "properties" of the code rather than by function?

like, the property is that inputs can be unified and dumped back and forth, so the stream data generator generates random arbitrary inputs/schematics and ensures that input == unify(schematic, input) |> then(&dump(schematic, &1))

I assume it's currently broken up by function because you were just incrementally working through them, but before we merge it is worth considering whether combining them into fewer properties makes sense.

yeah, i think that that makes sense. i didn't jump into this with a wholistic understanding of everything, so yeah i was just working through incrementally. now that i have a better understanding of the shape of the whole thing, i think this makes sense

Comment thread test/schematic_test.exs
Comment on lines +22 to +27
property "input |> unify |> dump == input" do
check all({schematic, input} <- Generators.schematic_and_data()) do
{:ok, input} ==
unify(schematic, input) |> then(fn {:ok, result} -> dump(schematic, result) end)
end
end
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will this replace the other properties?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

most of them, i think. i'm gonna comb through today and remove the ones that are definitely overshadowed by this

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏🏻

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replaced a bunch of existing properties that were superseded by this one. still a few remaining, but i suspect only one or two besides this are worth keeping. lmk if you feel differently

Comment thread test/support/generators.ex Outdated
@mhanberg
Copy link
Copy Markdown
Owner

mhanberg commented May 3, 2023

One annoying comment, can you add stream_data to the formatter.exs import deps and then remove the parens from all the all instances?

😅

@clark-lindsay
Copy link
Copy Markdown
Contributor Author

One annoying comment, can you add stream_data to the formatter.exs import deps and then remove the parens from all the all instances?

😅

haha sure thing. not "annoying" btw, i totally get it

@clark-lindsay clark-lindsay marked this pull request as ready for review May 3, 2023 22:05
@mhanberg
Copy link
Copy Markdown
Owner

mhanberg commented May 4, 2023

@clark-lindsay Can you rebase this and then I'll review

Comment thread lib/schematic.ex Outdated
Comment thread test/schematic_test.exs Outdated
Comment thread test/schematic_test.exs Outdated
Comment thread test/support/generators.ex Outdated
Comment thread test/support/generators.ex Outdated
Comment thread test/support/generators.ex Outdated
Comment thread test/support/generators.ex Outdated
Comment thread test/support/generators.ex Outdated
Comment thread test/support/generators.ex Outdated
Comment thread test/support/generators.ex Outdated
WORK IN PROGRESS: may end up removing the tests that the new
property-based tests overshadow, which would do away with the awkward
naming convention that i started with.

additionally, i wrote the generators in the `test/support` directory
after the JSON spec because it seemed to match well with the data types
supported by `schematic` right now (w/ the exception of `tuple`), but
this may just be incidental
WORK IN PROGRESS: there are a few rough edges here, so i'll record my
current thoughts on them:
  - i'm still not sure how valuable a separate module for `StreamData`
    generators is, but right now it feels like it improves the
    organization of the test code
  - currently a `list/0` schematic can match a non-homogenous list,
    whereas any call to `list/1` must match the given schematic
    homogenously (but you **could** pass a `one_of` schematic...)
    - is this the right design? i think it ***is right***, but i left
      myself a "TODO" to think about it some more
  - should property-based tests be written to describe the  properties
    of the code for the failure paths? or is that better left to
    hand-written edge case unit tests?
also extended some of the data generation in the `Generators` test
support module
- better reporting from `mix test`
- easier to notice/find/grep-for tests are property-based
- automatically tags the tests, so one can use `mix test --only
  property` to target _just_ the property-based tests
also extend and start to document support generators, in support of
ongoing test expansion and improvements
i don't entirely understand why there was a call to `Map.delete/2` in
the `unify` definition for the `map` schematic, but with it in place i
was dropping `""` keys when `dump`-ing.

removing the call has no effect on any other tests, and so i believe
that it's not necessary, ***but*** i may have written the test
incorrectly and misunderstood the desired behaviour...
i still can't think of a good way to generate tuples, but i at least
want the schematic `kind` accounted for in the tests in the meantime
thanks @motch for the _much_ improved name idea
the `property` "input |> unify |> dump == input" supercedes most of the
existing `property`s in the test file, which were written as stepping
stones for me to understand the behaviours and design of the code to
work towards more general properties that could be tested

remaining `property`s that were not deleted still have cases that are
not covered by the aforementioned "input |> unify |> dump == input"
test, and some `TODO`s have been added to address the generation of
schematics and data so that these `property`s will become redundant and
then be removed
also:
  - extended the schematics that can be generated from
    `Generators.schematic_and_data/0`
  - removed a `property` that is now superseded by the "input |> unify
    |> dump |> input" `property` test
  - added a few specific cases to the `tuple/2` example-based tests that
    reproduced the bug found in the `unify` callback from `tuple/2`
@clark-lindsay clark-lindsay force-pushed the add-property-based-tests branch from 1e02862 to 12d21ea Compare May 4, 2023 15:42
Comment thread lib/schematic.ex Outdated

```elixir
iex> schematic = tuple([str(), int()])
iex> schematic = Schematic.tuple([str(), int()])
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you say you could remove the Schematic. part?

@mhanberg mhanberg merged commit 44ac388 into mhanberg:main May 5, 2023
@mhanberg
Copy link
Copy Markdown
Owner

mhanberg commented May 5, 2023

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Property Based Tests

2 participants