thinwybk
thinwybk

Reputation: 4753

How can I create configurable custom hypothesis strategies which use `builds()`?

I created custom Hypothesis strategies using builds() and @composite (the design is inspired by this example from the docs). The strategies are designed similar to the pseudo code below:

# strategies.py

from hypothesis.strategies import builds, composite, draw, floats, integers

class SutConfiguration:
    """ Data which represents the configuration of the system under test. """
    def __init__(self, integer, float):
        self.integer = integer
        self.float = float

# custom strategy which uses builds()
SutConfigurationStrategy = builds(
    SutConfiguration,
    integer=integers(min_value=APP_SPECIFIC_INT_MIN, max_value=APP_SPECIFIC_INT_MAX),
    float=floats(min_value=APP_SPECIFIC_FLOAT_MIN, max_value=APP_SPECIFIC_FLOAT_MAX),
)

@composite
def overall_test_configuration(draw, sut_st=SutConfigurationStrategy, env_st=SutEnvironmentStrategy):
    """Custom strategy which uses draw."""
    sut_config = draw(sut_st)
    env_config = draw(env_st)
    return (sut_config, rc_stereomatching_config, env_config)

The strategy is uses as usual e.g. using unittest as test runner:

# test.py

import unittest
from <package>.strategies import overall_test_configuration

class TestSut(unittest.TestCase):
    """Class containing several tests for the system under test."""
    @given(overall_test_configuration())
    def test_something():
        """Test which uses overall_test_configuration"""
        ...

Now I would like to make the strategies configurable to the actual application to define e.g. min_value in integers(min_value=APP_SPECIFIC_INT_MIN, ...) when defining the test function. This can be done for @composite strategies via agruments like done here. But how can I make the strategies which use builds() configurable?

Upvotes: 3

Views: 3619

Answers (2)

thinwybk
thinwybk

Reputation: 4753

The solution applied to the pseudo code above to make e.g. the integer value of the SUT configuration SutConfigurationStrategy configurable would look like this:

# strategies.py

from hypothesis.strategies import builds, composite, draw, floats, integers

class SutConfiguration:
    """ Data which represents the configuration of the system under test. """
    def __init__(self, integer, float):
        self.integer = integer
        self.float = float

# custom strategy which uses builds()
def SutConfigurationStrategy(min_int_config, max_int_config):
    return builds(SutConfiguration,
                  integer=integers(min_value=min_int_config, max_value=max_int_config),
                  float=floats(min_value=APP_SPECIFIC_FLOAT_MIN, max_value=APP_SPECIFIC_FLOAT_MAX),
)

@composite
def overall_test_configuration(draw, sut_min_int_config, sut_max_int_config, sut_st=SutConfigurationStrategy, env_st=SutEnvironmentStrategy):
    """Custom strategy which uses draw."""
    sut_config = draw(sut_st(sut_min_int_config, sut_max_int_config))
    env_config = draw(env_st)
    return (sut_config, rc_stereomatching_config, env_config)

The strategies integer value can then e.g. be configured to minimal of 10 and maximal of 100 (again: unittest as test runner):

# test.py

import unittest
from <package>.strategies import overall_test_configuration

class TestSut(unittest.TestCase):
    """Class containing several tests for the system under test."""
    @given(overall_test_configuration(min_int_config=10, max_int_config=100))
    def test_something():
        """Test which uses overall_test_configuration"""
        ...

Side note: It is not optimal to use class SutConfiguration here to encapsulate the data. A Namedtuple would be a better choice here...

Upvotes: 3

DRMacIver
DRMacIver

Reputation: 2315

You can define a function that returns a strategy like any other:

def some_custom_strategy(a, b):
    return builds(foo, bar(a, b))

This is all that's happening with composite when you have extra arguments - composite is defining a function that returns a strategy, and those extra arguments are passed through the function to the underlying decorated function.

Upvotes: 5

Related Questions