Propose new features with refactoring
Gab-km opened this issue · 5 comments
Target: Extensions for PyQCheck
I'd like to add new features from another QuickCheck libraries (ex. scalacheck, etc) into PyQCheck. The features are like following:
for_all function
for_all
function allows us to create property a little easier than we are writing now.
from pyqcheck import PyQCheck, for_all
PyQCheck(verbose=True).add(
for_all(
('boolean', 'boolean'),
'!(x || y) == !x && !y',
lambda x, y: (not(x or y)) == ((not x) and (not y))
)
).run(10).result()
This is equivalent to the code below:
from pyqcheck import PyQCheck, Arbitrary
PyQCheck(verbose=True).add(
Arbitrary('boolean', 'boolean').property(
'!(x || y) == !x && !y', lambda x, y: (not(x or y)) == ((not x) and (not y))
)
).run(10).result()
from_gen function
Gen
class and from_gen
function allows us to create our own arbitrary with generator syntax in Python.
import random
import sys
from pyqcheck import PyQCheck, Gen, from_gen
def gen_int(min_int=1, max_int=None):
min_int = min_int if isinstance(min_int, int) else 1
max_int = max_int if max_int is not None and isinstance(max_int, int) else sys.maxsize
while True:
yield random.randint(min_int, max_int)
PyQCheck(verbose=True).add(
from_gen([Gen(gen_int), Gen(gen_int)]).property(
'x * y == y * x and x + y == y + x',
lambda x, y: x * y == y * x and x + y == y + x
)
).run(10).result()
We can limit these ranges.
from pyqcheck import PyQCheck, Gen, from_gen
def gen_int(min_int=1, max_int=None):
min_int = min_int if isinstance(min_int, int) else 1
max_int = max_int if max_int is not None and isinstance(max_int, int) else sys.maxsize
while True:
yield random.randint(min_int, max_int)
PyQCheck().add(
from_gen(
[Gen(gen_int, {"min": 10, "max": 100}), # range of 10 - 100
Gen(gen_int, {"min": 30})] # range of 30 - max of default
).property(
'10 <= x <= 100 and y >= 30',
lambda x, y : 10 <= x <= 100 and y >= 30
)
).run(10).result()
from_gen_and_shrink function
from_gen_and_shrink
function allows us to create our own arbitrary like from_gen
, which can set a shrinker.
The shrinker is a procedure to minimize our test cases as we want.
import random
import sys
from pyqcheck import PyQCheck, Gen, from_gen_and_shrink
def gen_int(min_int=1, max_int=None):
min_int = min_int if isinstance(min_int, int) else 1
max_int = max_int if max_int is not None and isinstance(max_int, int) else sys.maxsize
while True:
yield random.randint(min_int, max_int)
PyQCheck(verbose=True).add(
from_gen_and_shrink(
[Gen(gen_int, {"min": 10, "max": 100})],
lambda x: [x-2, x-1, x, x+1, x+2] # limit test cases within 2 if failed
).property(
'15 <= x <= 95,
lambda x: 15 <= x <= 95
)
).run(10).result()
It may result like this:
----- PyQCheck test results... -----
label: 15 <= x <= 95
success: 7
failure: 3
shrinks: 2 times
verbose:
☀ <lambda>(19)
☀ <lambda>(29)
☀ <lambda>(59)
☀ <lambda>(85)
☀ <lambda>(72)
☀ <lambda>(40)
☂ <lambda>(14)
☀ <lambda>(16)
☂ <lambda>(13)
☂ <lambda>(12)
-----
How to go: step by step
The features above are, however difficult to implement because of PyQCheck's current structure. For example, arbitrary
module grabs almost all like a dictator or God.
So I would like to resolve this proposition step by step as following:
- Refactoring (or something to redesign) PyQCheck modules without changing PyQCheck APIs as much as we can
- Then, implementing the features
If you like it, I will try to do the job.
@futoase If I made you confused, I will apologize you... 😢
I have coded the task above by 70%, so all you need is to judge my proposition and pull requests.
You don't have to be bothered to struggle with them. 😉
👍
Thanks a lot! 😉
This change will be large, so may I have your opinion for the way to send Pull Requests?
- To send 1 PR with all changes
- To send some PRs for each features, i.e. refactoring, introducing
for_all
, ... - Or else
@futoase If you don't have any special instruction for PR, I will send you 4 PRs in order (for refactoring, for_all
, from_gen
, and from_gen_and_shrink
) to make each of them reviewed. Thank you. 😃
Now the first step of refactoring and redesigning PyQCheck is done.
I will implement the rest of the tasks, new features of for_all
, from_gen
and from_gen_and_shrink
, in sequence.