Repository: HypothesisWorks/hypothesis

Issue:
Automatically generate django forms
Django models and forms have more than enough metadata for us to automatically introspect them and figure out how to build one. We should have a hypothesis-extra package to do this.



Discussion:
Note: hypothesis-django is now a (experimental and unfinished) thing. Many models can be automatically generated. Forms are not yet supported but should be entirely doable.

@DRMacIver  is the issue is still open and if it is please eleborate it more specifically.I want to contribute in it.
Yes, it's still open, but I'm worried that we might have different ideas of what will be involved - the `good first issue` label means we think it's suitable for someone who hasn't contributed to Hypothesis before, but I would *not* suggest it as your first open source contribution!  (This issue is for developing a substantial new feature, which requires experience using and testing Django forms, and using Hypothesis to write tests.)

However!  If you're open to working on other issues, #162 could use your help to move the `emails()` strategy from `hypothesis.provisional` to `hypothesis.strategies`.  Comment on that issue if you'd like to give it a go, and feel free to ask questions :smile: 
I can confirm (IIRC thanks to @vidyarani-dg) that `hypothesis.extra.django.TestCase` and `hypothesis.extra.django.TransactionTestCase` work with Django forms, even though we don't have form-related inference yet.
Hi! I've used models.py as a template and produced a functioning forms.py, I haven't built out any documentation or augmented the django toystore example, but I will happily finish that out if my approach seems sane and acceptable. 
https://github.com/thismatters/hypothesis/blob/master/hypothesis-python/src/hypothesis/extra/django/forms.py
Awesome! You're definitely on the right track, and I'd love to get this in.

However - I'd like to get #1695 done first so we can start with the API we want to keep. Would you mind just holding off on this until next week? Once I'm home from my camping trip I'll be able to give a better review too :smile:
Sure thing! I mainly wrote it because I wanted to use it, so I can keep using and polishing in my own project until the scheduled work is done.
OK! We now have #1743 (awaiting review), and I have time for some quick feedback on your draft :smile: 

- I'd suggest using a topic branch, rather than `master` - it makes staying up to date or resolving conflicts with upstream *much* easier, and you'll probably need that!
- Instead of the complicated form wrapper, you can generate from a dict: `st.builds(form, st.fixed_dictionaries(result))`
- You can add more entries to the new Field lookup dict / slap more `@register(df.SomeField)` decorators on the same functions.  The Field APIs *are* basically the same between models and forms, right?  Oh, and allow df.Field in the various argument validation checks.
- The `from_model` function should be pretty to copy-paste-tweak.  `from_form` should also live in `_impl.py`; we don't need another module.
- We'll need decent test suite for `from_form`, but "make enough models that we have one of every field type and feature" is more tedious than difficult, I hope, with the models tests to crib from.
#1743 has been merged, so we're ready for a PR when you are 🎉 
I can probably work on it this weekend. 
Regarding your second point, I'm using the form wrapper to allow for _other_ keyword arguments to be passed when instantiating the form, and to package up `results` into its own keyword argument (namely `data`). E.g.:
```python
form = MyFormClass(data=results, my_special_parameter=some_object)
```
(here `results` is not the strategies, but an example case of those strategies)

I'm not seeing how to do that with ``st.builds`` or ``st.fixed_dictionaries`` directly. Any hints?

This is important to support since form instances have the fields (as opposed to models where the class owns the fields), and the other keyword arguments can affect what fields are present in the instance (or how those fields are validated).

Resolving patch:
diff --git a/hypothesis-python/src/hypothesis/extra/django/__init__.py b/hypothesis-python/src/hypothesis/extra/django/__init__.py
--- a/hypothesis-python/src/hypothesis/extra/django/__init__.py
+++ b/hypothesis-python/src/hypothesis/extra/django/__init__.py
@@ -16,7 +16,12 @@
 # END HEADER
 
 from hypothesis.extra.django._fields import from_field, register_field_strategy
-from hypothesis.extra.django._impl import TestCase, TransactionTestCase, from_model
+from hypothesis.extra.django._impl import (
+    TestCase,
+    TransactionTestCase,
+    from_model,
+    from_form,
+)
 
 __all__ = [
     "TestCase",
@@ -24,4 +29,5 @@
     "from_field",
     "from_model",
     "register_field_strategy",
+    "from_form",
 ]
diff --git a/hypothesis-python/src/hypothesis/extra/django/_fields.py b/hypothesis-python/src/hypothesis/extra/django/_fields.py
--- a/hypothesis-python/src/hypothesis/extra/django/_fields.py
+++ b/hypothesis-python/src/hypothesis/extra/django/_fields.py
@@ -24,6 +24,12 @@
 
 import django
 import django.db.models as dm
+import django.forms as df
+from django.core.validators import (
+    validate_ipv4_address,
+    validate_ipv6_address,
+    validate_ipv46_address,
+)
 
 import hypothesis.strategies as st
 from hypothesis.errors import InvalidArgument
@@ -36,6 +42,7 @@
     from datetime import tzinfo  # noqa
     from typing import Any, Type, Optional, List, Text, Callable, Union  # noqa
 
+
 # Mapping of field types, to strategy objects or functions of (type) -> strategy
 _global_field_lookup = {
     dm.SmallIntegerField: st.integers(-32768, 32767),
@@ -51,6 +58,14 @@
     dm.NullBooleanField: st.one_of(st.none(), st.booleans()),
     dm.URLField: urls(),
     dm.UUIDField: st.uuids(),
+    df.DateField: st.dates(),
+    df.DurationField: st.timedeltas(),
+    df.EmailField: emails(),
+    df.FloatField: st.floats(allow_nan=False, allow_infinity=False),
+    df.IntegerField: st.integers(-2147483648, 2147483647),
+    df.NullBooleanField: st.one_of(st.none(), st.booleans()),
+    df.URLField: urls(),
+    df.UUIDField: st.uuids(),
 }
 
 
@@ -63,6 +78,7 @@ def inner(func):
 
 
 @register_for(dm.DateTimeField)
+@register_for(df.DateTimeField)
 def _for_datetime(field):
     if getattr(django.conf.settings, "USE_TZ", False):
         return st.datetimes(timezones=timezones())
@@ -82,13 +98,20 @@ def using_sqlite():
 
 
 @register_for(dm.TimeField)
-def _for_time(field):
+def _for_model_time(field):
     # SQLITE supports TZ-aware datetimes, but not TZ-aware times.
     if getattr(django.conf.settings, "USE_TZ", False) and not using_sqlite():
         return st.times(timezones=timezones())
     return st.times()
 
 
+@register_for(df.TimeField)
+def _for_form_time(field):
+    if getattr(django.conf.settings, "USE_TZ", False):
+        return st.times(timezones=timezones())
+    return st.times()
+
+
 @register_for(dm.DurationField)
 def _for_duration(field):
     # SQLite stores timedeltas as six bytes of microseconds
@@ -99,16 +122,20 @@ def _for_duration(field):
 
 
 @register_for(dm.SlugField)
+@register_for(df.SlugField)
 def _for_slug(field):
+    min_size = 1
+    if getattr(field, "blank", False) or not getattr(field, "required", True):
+        min_size = 0
     return st.text(
         alphabet=string.ascii_letters + string.digits,
-        min_size=(0 if field.blank else 1),
+        min_size=min_size,
         max_size=field.max_length,
     )
 
 
 @register_for(dm.GenericIPAddressField)
-def _for_ip(field):
+def _for_model_ip(field):
     return dict(
         ipv4=ip4_addr_strings(),
         ipv6=ip6_addr_strings(),
@@ -116,7 +143,22 @@ def _for_ip(field):
     )[field.protocol.lower()]
 
 
+@register_for(df.GenericIPAddressField)
+def _for_form_ip(field):
+    # the IP address form fields have no direct indication of which type
+    #  of address they want, so direct comparison with the validator
+    #  function has to be used instead. Sorry for the potato logic here
+    if validate_ipv46_address in field.default_validators:
+        return ip4_addr_strings() | ip6_addr_strings()
+    if validate_ipv4_address in field.default_validators:
+        return ip4_addr_strings()
+    if validate_ipv6_address in field.default_validators:
+        return ip6_addr_strings()
+    raise InvalidArgument("No IP version validator on field=%r" % field)
+
+
 @register_for(dm.DecimalField)
+@register_for(df.DecimalField)
 def _for_decimal(field):
     bound = Decimal(10 ** field.max_digits - 1) / (10 ** field.decimal_places)
     return st.decimals(min_value=-bound, max_value=bound, places=field.decimal_places)
@@ -124,6 +166,8 @@ def _for_decimal(field):
 
 @register_for(dm.CharField)
 @register_for(dm.TextField)
+@register_for(df.CharField)
+@register_for(df.RegexField)
 def _for_text(field):
     # We can infer a vastly more precise strategy by considering the
     # validators as well as the field type.  This is a minimal proof of
@@ -143,13 +187,26 @@ def _for_text(field):
         # compute intersections of the full Python regex language.
         return st.one_of(*[st.from_regex(r) for r in regexes])
     # If there are no (usable) regexes, we use a standard text strategy.
-    return st.text(
+    min_size = 1
+    if getattr(field, "blank", False) or not getattr(field, "required", True):
+        min_size = 0
+    strategy = st.text(
         alphabet=st.characters(
             blacklist_characters=u"\x00", blacklist_categories=("Cs",)
         ),
-        min_size=(0 if field.blank else 1),
+        min_size=min_size,
         max_size=field.max_length,
     )
+    if getattr(field, "required", True):
+        strategy = strategy.filter(lambda s: s.strip())
+    return strategy
+
+
+@register_for(df.BooleanField)
+def _for_form_boolean(field):
+    if field.required:
+        return st.just(True)
+    return st.booleans()
 
 
 def register_field_strategy(field_type, strategy):
@@ -159,7 +216,7 @@ def register_field_strategy(field_type, strategy):
     ``field_type`` must be a subtype of django.db.models.Field, which must not
     already be registered.  ``strategy`` must be a SearchStrategy.
     """
-    if not issubclass(field_type, dm.Field):
+    if not issubclass(field_type, (dm.Field, df.Field)):
         raise InvalidArgument(
             "field_type=%r must be a subtype of Field" % (field_type,)
         )
@@ -183,20 +240,29 @@ def from_field(field):
     than a Field *subtype*, so that it has access to instance attributes
     such as string length and validators.
     """
-    check_type(dm.Field, field, "field")
-    if field.choices:
+    check_type((dm.Field, df.Field), field, "field")
+    if getattr(field, "choices", False):
         choices = []  # type: list
         for value, name_or_optgroup in field.choices:
             if isinstance(name_or_optgroup, (list, tuple)):
                 choices.extend(key for key, _ in name_or_optgroup)
             else:
                 choices.append(value)
+        # form fields automatically include an empty choice, strip it out
+        if u"" in choices:
+            choices.remove(u"")
+        min_size = 1
         if isinstance(field, (dm.CharField, dm.TextField)) and field.blank:
             choices.insert(0, u"")
+        elif isinstance(field, (df.Field)) and not field.required:
+            choices.insert(0, u"")
+            min_size = 0
         strategy = st.sampled_from(choices)
+        if isinstance(field, (df.MultipleChoiceField, df.TypedMultipleChoiceField)):
+            strategy = st.lists(st.sampled_from(choices), min_size=min_size)
     else:
         if type(field) not in _global_field_lookup:
-            if field.null:
+            if getattr(field, "null", False):
                 return st.none()
             raise InvalidArgument("Could not infer a strategy for %r", (field,))
         strategy = _global_field_lookup[type(field)]
@@ -213,6 +279,7 @@ def validate(value):
                 return False
 
         strategy = strategy.filter(validate)
-    if field.null:
+
+    if getattr(field, "null", False):
         return st.none() | strategy
     return strategy
diff --git a/hypothesis-python/src/hypothesis/extra/django/_impl.py b/hypothesis-python/src/hypothesis/extra/django/_impl.py
--- a/hypothesis-python/src/hypothesis/extra/django/_impl.py
+++ b/hypothesis-python/src/hypothesis/extra/django/_impl.py
@@ -18,9 +18,12 @@
 from __future__ import absolute_import, division, print_function
 
 import unittest
+from functools import partial
 
 import django.db.models as dm
+import django.forms as df
 import django.test as dt
+from django.core.exceptions import ValidationError
 from django.db import IntegrityError
 
 import hypothesis._strategies as st
@@ -125,3 +128,89 @@ def _models_impl(draw, strat):
         return draw(strat)[0]
     except IntegrityError:
         reject()
+
+
+@st.defines_strategy
+def from_form(
+    form,  # type: Type[dm.Model]
+    form_kwargs=None,  # type: dict
+    **field_strategies  # type: Union[st.SearchStrategy[Any], InferType]
+):
+    # type: (...) -> st.SearchStrategy[Any]
+    """Return a strategy for examples of ``form``.
+
+    ``form`` must be an subclass of :class:`~django:django.forms.Form`.
+    Strategies for fields may be passed as keyword arguments, for example
+    ``is_staff=st.just(False)``.
+
+    Hypothesis can often infer a strategy based the field type and validators,
+    and will attempt to do so for any required fields.  No strategy will be
+    inferred for a disabled field or field for which a keyword argument
+    is passed to ``from_form()``.
+
+    This function uses the fields of an unbound ``form`` instance to determine
+    field strategies, any keyword arguments needed to instantiate the unbound
+    ``form`` instance can be passed into ``from_form()`` as a dict with the
+    keyword ``form_kwargs``. E.g.::
+
+        shop_strategy = from_form(Shop, form_kwargs={"company_id": 5})
+
+    Like for :func:`~hypothesis.strategies.builds`, you can pass
+    :obj:`~hypothesis.infer` as a keyword argument to infer a strategy for
+    a field which has a default value instead of using the default.
+    """
+    # currently unsupported:
+    # ComboField
+    # FilePathField
+    # FileField
+    # ImageField
+    form_kwargs = form_kwargs or {}
+    if not issubclass(form, df.BaseForm):
+        raise InvalidArgument("form=%r must be a subtype of Form" % (form,))
+
+    # Forms are a little bit different from models. Model classes have
+    # all their fields defined, whereas forms may have different fields
+    # per-instance. So, we ought to instantiate the form and get the
+    # fields from the instance, thus we need to accept the kwargs for
+    # instantiation as well as the explicitly defined strategies
+
+    unbound_form = form(**form_kwargs)
+    fields_by_name = {}
+    for name, field in unbound_form.fields.items():
+        if isinstance(field, df.MultiValueField):
+            # PS: So this is a little strange, but MultiValueFields must
+            # have their form data encoded in a particular way for the
+            # values to actually be picked up by the widget instances'
+            # ``value_from_datadict``.
+            # E.g. if a MultiValueField named 'mv_field' has 3
+            # sub-fields then the ``value_from_datadict`` will look for
+            # 'mv_field_0', 'mv_field_1', and 'mv_field_2'. Here I'm
+            # decomposing the individual sub-fields into the names that
+            # the form validation process expects
+            for i, _field in enumerate(field.fields):
+                fields_by_name["%s_%d" % (name, i)] = _field
+        else:
+            fields_by_name[name] = field
+    for name, value in sorted(field_strategies.items()):
+        if value is infer:
+            field_strategies[name] = from_field(fields_by_name[name])
+
+    for name, field in sorted(fields_by_name.items()):
+        if name not in field_strategies and not field.disabled:
+            field_strategies[name] = from_field(field)
+
+    return _forms_impl(
+        st.builds(
+            partial(form, **form_kwargs),
+            data=st.fixed_dictionaries(field_strategies),  # type: ignore
+        )
+    )
+
+
+@st.composite
+def _forms_impl(draw, strat):
+    """Handle the nasty part of drawing a value for from_form()"""
+    try:
+        return draw(strat)
+    except ValidationError:
+        reject()


Test patch:
diff --git a/hypothesis-python/tests/django/toystore/forms.py b/hypothesis-python/tests/django/toystore/forms.py
new file mode 100644
--- /dev/null
+++ b/hypothesis-python/tests/django/toystore/forms.py
@@ -0,0 +1,208 @@
+# coding=utf-8
+#
+# This file is part of Hypothesis, which may be found at
+# https://github.com/HypothesisWorks/hypothesis/
+#
+# Most of this work is copyright (C) 2013-2019 David R. MacIver
+# (david@drmaciver.com), but it contains contributions by others. See
+# CONTRIBUTING.rst for a full list of people who may hold copyright, and
+# consult the git log if you need to determine who owns an individual
+# contribution.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public License,
+# v. 2.0. If a copy of the MPL was not distributed with this file, You can
+# obtain one at https://mozilla.org/MPL/2.0/.
+#
+# END HEADER
+
+from __future__ import absolute_import, division, print_function
+
+from django import forms
+from django.forms import widgets
+
+from tests.django.toystore.models import (
+    CouldBeCharming,
+    Customer,
+    ManyNumerics,
+    ManyTimes,
+    OddFields,
+)
+
+
+class ReprModelForm(forms.ModelForm):
+    def __repr__(self):
+        """I recommend putting this in your form to show the failed cases."""
+        return "%r\n%r" % (self.data, self.errors)
+
+
+class ReprForm(forms.Form):
+    def __repr__(self):
+        return "%r\n%r" % (self.data, self.errors)
+
+
+class CouldBeCharmingForm(ReprModelForm):
+    class Meta:
+        model = CouldBeCharming
+        fields = "__all__"
+
+
+class CustomerForm(ReprModelForm):
+    class Meta:
+        model = Customer
+        fields = "__all__"
+
+
+class ManyNumericsForm(ReprModelForm):
+    class Meta:
+        model = ManyNumerics
+        fields = "__all__"
+
+
+class ManyTimesForm(ReprModelForm):
+    class Meta:
+        model = ManyTimes
+        fields = "__all__"
+
+
+class OddFieldsForm(ReprModelForm):
+    class Meta:
+        model = OddFields
+        fields = "__all__"
+
+
+class DynamicForm(ReprForm):
+    def __init__(self, field_count=5, **kwargs):
+        super(DynamicForm, self).__init__(**kwargs)
+        for i in range(field_count):
+            field_name = "field-%d" % (i,)
+            self.fields[field_name] = forms.CharField(required=False)
+
+
+class BasicFieldForm(ReprForm):
+    _boolean_required = forms.BooleanField()
+    _boolean = forms.BooleanField(required=False)
+    # This took me too long to figure out... The BooleanField will actually
+    # raise a ValidationError when it recieves a value of False. Why they
+    # didn't call it a TrueOnlyField escapes me, but *if* you actually want
+    # to accept both True and False in your BooleanField, make sure you set
+    # `required=False`. This behavior has been hotly contested in the bug
+    # tracker (e.g. https://code.djangoproject.com/ticket/23547), but it
+    # seems that since the tests and documentation are already written
+    # this behavior is Truth.
+    # see the note in the documentation
+    # https://docs.djangoproject.com/en/dev/ref/forms/fields/#booleanfield
+
+    _char_required = forms.CharField(required=True)
+    _char = forms.CharField(required=False)
+    _decimal = forms.DecimalField(max_digits=8, decimal_places=3)
+    _float = forms.FloatField()
+    _integer = forms.IntegerField()
+    _null_boolean = forms.NullBooleanField()
+
+
+class TemporalFieldForm(ReprForm):
+    _date = forms.DateField()
+    _date_time = forms.DateTimeField()
+    _duration = forms.DurationField()
+    _time = forms.TimeField()
+    _split_date_time = forms.SplitDateTimeField()
+
+
+class EmailFieldForm(ReprForm):
+    _email = forms.EmailField()
+
+
+class SlugFieldForm(ReprForm):
+    _slug = forms.SlugField()
+
+
+class URLFieldForm(ReprForm):
+    _url = forms.URLField()
+
+
+class RegexFieldForm(ReprForm):
+    _regex = forms.RegexField(regex=u"[A-Z]{3}\\.[a-z]{4}")
+
+
+class UUIDFieldForm(ReprForm):
+    _uuid = forms.UUIDField()
+
+
+class ChoiceFieldForm(ReprForm):
+    _choice = forms.ChoiceField(
+        choices=(("cola", "Cola"), ("tea", "Tea"), ("water", "Water"))
+    )
+    _multiple = forms.MultipleChoiceField(
+        choices=(("cola", "Cola"), ("tea", "Tea"), ("water", "Water"))
+    )
+    _typed = forms.TypedChoiceField(
+        choices=(("1", "one"), ("2", "two"), ("3", "three"), ("4", "four")),
+        coerce=int,
+        empty_value=0,
+    )
+    _typed_multiple = forms.TypedMultipleChoiceField(
+        choices=(("1", "one"), ("2", "two"), ("3", "three"), ("4", "four")),
+        coerce=int,
+        empty_value=0,
+    )
+
+
+class InternetProtocolForm(ReprForm):
+    _ip_both = forms.GenericIPAddressField()
+    _ip_v4 = forms.GenericIPAddressField(protocol="IPv4")
+    _ip_v6 = forms.GenericIPAddressField(protocol="IPv6")
+
+
+class BroadBooleanInput(widgets.CheckboxInput):
+    """Basically pulled directly from the Django CheckboxInput. I added
+    some stuff to ``values``
+    """
+
+    def value_from_datadict(self, data, files, name):
+        if name not in data:
+            return False
+        value = data.get(name)
+        # Translate true and false strings to boolean values.
+        values = {u"true": True, u"false": False, u"0": False, u"1": True}
+        if isinstance(value, str):
+            value = values.get(value.lower(), value)
+        return bool(value)
+
+
+class MultiCheckboxWidget(widgets.MultiWidget):
+    def __init__(self, subfield_count=12, **kwargs):
+        _widgets = [BroadBooleanInput()] * subfield_count
+        super(MultiCheckboxWidget, self).__init__(_widgets, **kwargs)
+
+    def decompress(self, value):
+        values = []
+        for _value in value.split(u"::"):
+            if _value in (u"0", u"", u"False", 0, None, False):
+                values.append(False)
+            else:
+                values.append(True)
+        return values
+
+
+class BroadBooleanField(forms.BooleanField):
+    pass
+
+
+class MultiBooleanField(forms.MultiValueField):
+    def __init__(self, subfield_count=12, **kwargs):
+        subfields = [BroadBooleanField()] * subfield_count
+        widget = MultiCheckboxWidget(subfield_count=subfield_count)
+        super(MultiBooleanField, self).__init__(fields=subfields, widget=widget)
+
+    def compress(self, values):
+        return u"::".join([str(x) for x in values])
+
+
+class ManyMultiValueForm(ReprForm):
+    def __init__(self, subfield_count=12, **kwargs):
+        super(ManyMultiValueForm, self).__init__(**kwargs)
+        self.fields["mv_field"] = MultiBooleanField(subfield_count=subfield_count)
+
+
+class ShortStringForm(ReprForm):
+    _not_too_long = forms.CharField(max_length=20, required=False)
diff --git a/hypothesis-python/tests/django/toystore/test_given_forms.py b/hypothesis-python/tests/django/toystore/test_given_forms.py
new file mode 100644
--- /dev/null
+++ b/hypothesis-python/tests/django/toystore/test_given_forms.py
@@ -0,0 +1,120 @@
+# coding=utf-8
+#
+# This file is part of Hypothesis, which may be found at
+# https://github.com/HypothesisWorks/hypothesis/
+#
+# Most of this work is copyright (C) 2013-2019 David R. MacIver
+# (david@drmaciver.com), but it contains contributions by others. See
+# CONTRIBUTING.rst for a full list of people who may hold copyright, and
+# consult the git log if you need to determine who owns an individual
+# contribution.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public License,
+# v. 2.0. If a copy of the MPL was not distributed with this file, You can
+# obtain one at https://mozilla.org/MPL/2.0/.
+#
+# END HEADER
+
+from __future__ import absolute_import, division, print_function
+
+from hypothesis import given
+from hypothesis.extra.django import TestCase, from_form, register_field_strategy
+from hypothesis.strategies import booleans, sampled_from
+from tests.django.toystore.forms import (
+    BasicFieldForm,
+    BroadBooleanField,
+    ChoiceFieldForm,
+    CustomerForm,
+    DynamicForm,
+    EmailFieldForm,
+    InternetProtocolForm,
+    ManyMultiValueForm,
+    ManyNumericsForm,
+    ManyTimesForm,
+    OddFieldsForm,
+    RegexFieldForm,
+    ShortStringForm,
+    SlugFieldForm,
+    TemporalFieldForm,
+    URLFieldForm,
+    UUIDFieldForm,
+)
+
+register_field_strategy(
+    BroadBooleanField, booleans() | sampled_from([u"1", u"0", u"True", u"False"])
+)
+
+
+class TestGetsBasicForms(TestCase):
+    @given(from_form(CustomerForm))
+    def test_valid_customer(self, customer_form):
+        self.assertTrue(customer_form.is_valid())
+
+    @given(from_form(ManyNumericsForm))
+    def test_valid_numerics(self, numerics_form):
+        self.assertTrue(numerics_form.is_valid())
+
+    @given(from_form(ManyTimesForm))
+    def test_valid_times(self, times_form):
+        self.assertTrue(times_form.is_valid())
+
+    @given(from_form(OddFieldsForm))
+    def test_valid_odd_fields(self, odd_form):
+        self.assertTrue(odd_form.is_valid())
+
+    def test_dynamic_form(self):
+        for field_count in range(2, 7):
+
+            @given(from_form(DynamicForm, form_kwargs={"field_count": field_count}))
+            def _test(dynamic_form):
+                self.assertTrue(dynamic_form.is_valid())
+
+            _test()
+
+    @given(from_form(BasicFieldForm))
+    def test_basic_fields_form(self, basic_field_form):
+        self.assertTrue(basic_field_form.is_valid())
+
+    @given(from_form(TemporalFieldForm))
+    def test_temporal_fields_form(self, time_field_form):
+        self.assertTrue(time_field_form.is_valid())
+
+    @given(from_form(EmailFieldForm))
+    def test_email_field_form(self, email_field_form):
+        self.assertTrue(email_field_form.is_valid())
+
+    @given(from_form(SlugFieldForm))
+    def test_slug_field_form(self, slug_field_form):
+        self.assertTrue(slug_field_form.is_valid())
+
+    @given(from_form(URLFieldForm))
+    def test_url_field_form(self, url_field_form):
+        self.assertTrue(url_field_form.is_valid())
+
+    @given(from_form(RegexFieldForm))
+    def test_regex_field_form(self, regex_field_form):
+        self.assertTrue(regex_field_form.is_valid())
+
+    @given(from_form(UUIDFieldForm))
+    def test_uuid_field_form(self, uuid_field_form):
+        self.assertTrue(uuid_field_form.is_valid())
+
+    @given(from_form(ChoiceFieldForm))
+    def test_choice_fields_form(self, choice_field_form):
+        self.assertTrue(choice_field_form.is_valid())
+
+    @given(from_form(InternetProtocolForm))
+    def test_ip_fields_form(self, ip_field_form):
+        self.assertTrue(ip_field_form.is_valid())
+
+    @given(from_form(ManyMultiValueForm, form_kwargs={"subfield_count": 2}))
+    def test_many_values_in_multi_value_field(self, many_multi_value_form):
+        self.assertTrue(many_multi_value_form.is_valid())
+
+    @given(from_form(ManyMultiValueForm, form_kwargs={"subfield_count": 105}))
+    def test_excessive_values_in_multi_value_field(self, excessive_form):
+        self.assertTrue(excessive_form.is_valid())
+
+    @given(from_form(ShortStringForm))
+    def test_short_string_form(self, short_string_form):
+        self.assertTrue(short_string_form.is_valid())