-
Notifications
You must be signed in to change notification settings - Fork 3
Description
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.