Great work, looks really clean! Check out Minithesis [1] and the paper explaining internal shrinking [2]. It will strongly improve your library's shrinking capabilities and prevent users from having to write their own shrinkers for custom objects in most cases.
Wow, I didn't know about them. Thanks for letting me know!
cortesoft 298 days ago [-]
Can this be used alongside rspec? Like have these be tests that are included in the rspec test run, and have the results alongside your other rspec tests?
ohbarye 298 days ago [-]
The answer to both questions is yes.
This gem doesn't aim to reimplement a testing framework. It can also be considered just an assertion. You can use this alongside any testing framework like RSpec, minitest, and etc.
jack_riminton 298 days ago [-]
Interesting! I wonder how this could be applied to a Rails project (since that's what ruby is most used for). For example how would it know to test a wide selection of possible values for different variables if they're not explicitly set up?
297 days ago [-]
parentheses 298 days ago [-]
I imagine it wouldn't be too hard to build up your own data generators for Rails models (or other entities).
ohbarye 297 days ago [-]
Yeah, the easiest way is to generate attributes and instantiate any Rails model like the one below:
Pbt.assert do
Pbt.property(name: Pbt.printable_string, age: Pbt.integer(min: 0, max: 100)) do |name:, age:|
user = User.new(name: name, age: age)
# write your test here
end
end
Alifatisk 298 days ago [-]
> Rails [...] that's what ruby is most used for
I wish that wasn't the case, I use Ruby heavily for gluing different systems together.
mikepurvis 298 days ago [-]
Outside of language level preferences, is there something about the ecosystem around Ruby that makes it particularly well suited to this? I would have thought Python was the glue language winner just because there are already bindings to the entire universe available for it.
portpecos 298 days ago [-]
Ruby just simply isn’t the glue language winner because of the heavy emphasis on rails.
From a systems perspective, I had to switch to python because it has pyroute2, which supports rtnl, devlink, ethtool and more.
I would have thought ruby had a full-fledged netlink library right now considering the stability of chef and puppet.
I started off with ruby for systems glue but now I have a mix of python and ruby. I wish it was all ruby but the lack of updated “glue gems” and the prevalence of updated “glue eggs” means python really is the “glue language winner”.
sam2426679 297 days ago [-]
> I wish it was all ruby
Why do you not wish it was all Python?
ilvez 297 days ago [-]
My answer to this would be: This is totally personal, but for me Ruby is just a language that allows concisely and readable way express myself to get stuff done. Python just does not read so good and forces to do more boiler-plate. Probably skill-issue, but I knew Python before Ruby, so :shrug: To give out any examples, I would need to have some Python code on me, but I don't :D
Yeah, that's so typical. I've almost become used to this, seeing useful gems being very old. To me, I see that as either abandoned (which means I have to fork it and polish it) or the gem is considered complete.
Alifatisk 298 days ago [-]
Ruby is my choice primarily because of its syntax and the powerful meta-programming capabilities. The ecosystem in itself is not any richer or better than Python, no, Python has faaar superior libraries and vibrant support.
In my case, I've found ruby-toolbox.com to be useful when I need to scavenge for libraries.
298 days ago [-]
parentheses 298 days ago [-]
Very specific question: when tests fail due to a single example, you suggest creating a specific assertion with the given seed.
I imagine the seed is used to generate data and depending on the order of your generators, it produces different results.
For example, in:
PBT.assert(seed = ..) do
PBT.property(PBT.integer, PBT.string) ...
end
Would changing the order of parameters to `property` change the actual test case?
tybug 298 days ago [-]
(not OP but I would be surprised if the answer wasn't) yes, because you're changing the order in which the random draws are interpreted. But this isn't a problem in practice because you generally aren't changing the generator in the middle of debugging a failure.
parentheses 297 days ago [-]
So, this means refactoring becomes potentially difficult. While the gem is still a great accomplishment and very useful, I'd have to engineer my way around this issue before using it with things like a Rails Model which could have changing shape.
@OP:
I wonder if the README (and possibly runner) should suggest writing a test-case that doesn't rely on PBT when the user wants to preserve a case for future testing.
The issue here is that if you're saving a singular example and it represents a weird corner case, it's totally conceivable that a small change will result in an invisible change to that test case.
Another idea: it'd be great if the test could simply take examples that are failing and add them to a `failing_examples.rb` or some such. I know I'd use a feature like this quite a bit.
ohbarye 297 days ago [-]
I think there are usually three actions a programmer can take when PBT fails.
- Create a test case that doesn't depend on PBT as you suggest.
- Fix the production code being tested since its failure is an unexpected bug.
- Fix the PBT itself. This means that the programmer has had wrong assumption for the test target.
I think it's difficult for the tool to know which choice is the best on a failure. But if there's any good idea, I'd like to incorporate it. :)
297 days ago [-]
ohbarye 297 days ago [-]
>yes, because you're changing the order in which the random draws are interpreted. But this isn't a problem in practice because you generally aren't changing the generator in the middle of debugging a failure.
Correct. The test inputs are determined by a seed and generators (including the order of generators).
bloopernova 298 days ago [-]
Can anyone recommend an introduction to PBT for someone who knows nothing about it?
Edited to add: D'oh! There's an explanation in the linked repo.
ohbarye 297 days ago [-]
It's very basic content though, here's the slides of my presentation at RubyKaigi 2024. This also includes the PBT gems's explanations.
Very cool. Wonder how much time this adds to a test suite as you incorporate it more. Might need to have some different test targets to keep things fast.
ohbarye 297 days ago [-]
Thank you.
In my opinion, PBT should be used in combination with example-based testing. Besides, since example-based testing cases account for the majority of tests, I think it's rare that PBT's execution time is dominant.
[1] https://github.com/DRMacIver/minithesis
[2] https://drops.dagstuhl.de/entities/document/10.4230/LIPIcs.E...
This gem doesn't aim to reimplement a testing framework. It can also be considered just an assertion. You can use this alongside any testing framework like RSpec, minitest, and etc.
I wish that wasn't the case, I use Ruby heavily for gluing different systems together.
From a systems perspective, I had to switch to python because it has pyroute2, which supports rtnl, devlink, ethtool and more.
I would have thought ruby had a full-fledged netlink library right now considering the stability of chef and puppet.
But all I could find was this from 8 years ago: https://github.com/BytemarkHosting/netlinkrb
I started off with ruby for systems glue but now I have a mix of python and ruby. I wish it was all ruby but the lack of updated “glue gems” and the prevalence of updated “glue eggs” means python really is the “glue language winner”.
Why do you not wish it was all Python?
Yeah, that's so typical. I've almost become used to this, seeing useful gems being very old. To me, I see that as either abandoned (which means I have to fork it and polish it) or the gem is considered complete.
I imagine the seed is used to generate data and depending on the order of your generators, it produces different results.
For example, in:
Would changing the order of parameters to `property` change the actual test case?@OP:
I wonder if the README (and possibly runner) should suggest writing a test-case that doesn't rely on PBT when the user wants to preserve a case for future testing.
The issue here is that if you're saving a singular example and it represents a weird corner case, it's totally conceivable that a small change will result in an invisible change to that test case.
Another idea: it'd be great if the test could simply take examples that are failing and add them to a `failing_examples.rb` or some such. I know I'd use a feature like this quite a bit.
- Create a test case that doesn't depend on PBT as you suggest.
- Fix the production code being tested since its failure is an unexpected bug.
- Fix the PBT itself. This means that the programmer has had wrong assumption for the test target.
I think it's difficult for the tool to know which choice is the best on a failure. But if there's any good idea, I'd like to incorporate it. :)
Correct. The test inputs are determined by a seed and generators (including the order of generators).
Edited to add: D'oh! There's an explanation in the linked repo.
https://speakerdeck.com/ohbarye/unlocking-potential-of-prope...
Speaking of my personal story, I used the fast-check documentation and Fred Herbert's book (their links are in the README) to study.
https://arialdomartini.github.io/property-testing
It would be cool if we could eventually make use of RBS or inline Sorbet or something better than both to get the types for property testing for free.
https://github.com/ksss/raap
In my opinion, PBT should be used in combination with example-based testing. Besides, since example-based testing cases account for the majority of tests, I think it's rare that PBT's execution time is dominant.
As for the combination and usages, refs: https://medium.com/criteo-engineering/introduction-to-proper...