Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Propose new features with refactoring #10

Open
Gab-km opened this issue Sep 29, 2014 · 5 comments
Open

Propose new features with refactoring #10

Gab-km opened this issue Sep 29, 2014 · 5 comments

Comments

@Gab-km
Copy link
Contributor

Gab-km commented Sep 29, 2014

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:

  1. Refactoring (or something to redesign) PyQCheck modules without changing PyQCheck APIs as much as we can
  2. Then, implementing the features

If you like it, I will try to do the job.

@Gab-km
Copy link
Contributor Author

Gab-km commented Oct 1, 2014

@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. 😉

@futoase
Copy link
Owner

futoase commented Oct 2, 2014

👍

@Gab-km
Copy link
Contributor Author

Gab-km commented Oct 6, 2014

Thanks a lot! 😉

This change will be large, so may I have your opinion for the way to send Pull Requests?

  1. To send 1 PR with all changes
  2. To send some PRs for each features, i.e. refactoring, introducing for_all, ...
  3. Or else

@Gab-km
Copy link
Contributor Author

Gab-km commented Oct 8, 2014

@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. 😃

@Gab-km
Copy link
Contributor Author

Gab-km commented Oct 22, 2014

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.

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

No branches or pull requests

2 participants