aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/fuzzy.rst35
-rw-r--r--factory/fuzzy.py29
-rw-r--r--tests/test_fuzzy.py71
3 files changed, 120 insertions, 15 deletions
diff --git a/docs/fuzzy.rst b/docs/fuzzy.rst
index 582a654..1843920 100644
--- a/docs/fuzzy.rst
+++ b/docs/fuzzy.rst
@@ -97,6 +97,41 @@ FuzzyInteger
.. attribute:: high
int, the inclusive higher bound of generated integers
+
+FuzzyDecimal
+------------
+
+.. class:: FuzzyDecimal(low[, high])
+
+ The :class:`FuzzyDecimal` fuzzer generates random integers within a given
+ inclusive range.
+
+ The :attr:`low` bound may be omitted, in which case it defaults to 0:
+
+ .. code-block:: pycon
+
+ >>> FuzzyDecimal(0.5, 42.7)
+ >>> fi.low, fi.high
+ 0.5, 42.7
+
+ >>> fi = FuzzyDecimal(42.7)
+ >>> fi.low, fi.high
+ 0.0, 42.7
+
+ >>> fi = FuzzyDecimal(0.5, 42.7, 3)
+ >>> fi.low, fi.high, fi.precision
+ 0.5, 42.7, 3
+
+ .. attribute:: low
+
+ decimal, the inclusive lower bound of generated decimals
+
+ .. attribute:: high
+
+ decimal, the inclusive higher bound of generated decimals
+
+ .. attribute:: precision
+ int, the number of digits to generate after the dot. The default is 2 digits.
FuzzyDate
diff --git a/factory/fuzzy.py b/factory/fuzzy.py
index f3e6a31..7fa0908 100644
--- a/factory/fuzzy.py
+++ b/factory/fuzzy.py
@@ -25,7 +25,7 @@
from __future__ import unicode_literals
-
+from decimal import Decimal
import random
import string
import datetime
@@ -121,8 +121,27 @@ class FuzzyInteger(BaseFuzzyAttribute):
return random.randint(self.low, self.high)
+class FuzzyDecimal(BaseFuzzyAttribute):
+ """Random decimal within a given range."""
+
+ def __init__(self, low, high=None, precision=2, **kwargs):
+ if high is None:
+ high = low
+ low = 0.0
+
+ self.low = low
+ self.high = high
+ self.precision = precision
+
+ super(FuzzyDecimal, self).__init__(**kwargs)
+
+ def fuzz(self):
+ return Decimal(random.uniform(self.low, self.high)).quantize(Decimal(10) ** -self.precision)
+
+
class FuzzyDate(BaseFuzzyAttribute):
"""Random date within a given date range."""
+
def __init__(self, start_date, end_date=None, **kwargs):
super(FuzzyDate, self).__init__(**kwargs)
if end_date is None:
@@ -150,12 +169,12 @@ class BaseFuzzyDateTime(BaseFuzzyAttribute):
if start_dt > end_dt:
raise ValueError(
"""%s boundaries should have start <= end, got %r > %r""" % (
- self.__class__.__name__, start_dt, end_dt))
+ self.__class__.__name__, start_dt, end_dt))
def __init__(self, start_dt, end_dt=None,
- force_year=None, force_month=None, force_day=None,
- force_hour=None, force_minute=None, force_second=None,
- force_microsecond=None, **kwargs):
+ force_year=None, force_month=None, force_day=None,
+ force_hour=None, force_minute=None, force_second=None,
+ force_microsecond=None, **kwargs):
super(BaseFuzzyDateTime, self).__init__(**kwargs)
if end_dt is None:
diff --git a/tests/test_fuzzy.py b/tests/test_fuzzy.py
index a521ee2..2202c8f 100644
--- a/tests/test_fuzzy.py
+++ b/tests/test_fuzzy.py
@@ -22,6 +22,7 @@
import datetime
+from decimal import Decimal
from factory import compat
from factory import fuzzy
@@ -108,6 +109,56 @@ class FuzzyIntegerTestCase(unittest.TestCase):
self.assertEqual(8, res)
+class FuzzyDecimalTestCase(unittest.TestCase):
+ def test_definition(self):
+ """Tests all ways of defining a FuzzyDecimal."""
+ fuzz = fuzzy.FuzzyDecimal(2.0, 3.0)
+ for _i in range(20):
+ res = fuzz.evaluate(2, None, False)
+ self.assertTrue(Decimal(2.0) <= res <= Decimal(3.0), 'value is not between 2.0 and 3.0. It is %d' % res)
+
+ fuzz = fuzzy.FuzzyDecimal(4.0)
+ for _i in range(20):
+ res = fuzz.evaluate(2, None, False)
+ self.assertTrue(Decimal(0.0) <= res <= Decimal(4.0), 'value is not between 0.0 and 4.0. It is %d' % res)
+
+ fuzz = fuzzy.FuzzyDecimal(1.0, 4.0, precision=5)
+ for _i in range(20):
+ res = fuzz.evaluate(2, None, False)
+ self.assertTrue(Decimal(0.54) <= res <= Decimal(4.0), 'value is not between 0.54 and 4.0. It is %d' % res)
+ self.assertTrue(res.as_tuple().exponent, -5)
+
+ def test_biased(self):
+ fake_uniform = lambda low, high: low + high
+
+ fuzz = fuzzy.FuzzyDecimal(2.0, 8.0)
+
+ with mock.patch('random.uniform', fake_uniform):
+ res = fuzz.evaluate(2, None, False)
+
+ self.assertEqual(Decimal(10.0), res)
+
+ def test_biased_high_only(self):
+ fake_uniform = lambda low, high: low + high
+
+ fuzz = fuzzy.FuzzyDecimal(8.0)
+
+ with mock.patch('random.uniform', fake_uniform):
+ res = fuzz.evaluate(2, None, False)
+
+ self.assertEqual(Decimal(8.0), res)
+
+ def test_precision(self):
+ fake_uniform = lambda low, high: low + high + 0.001
+
+ fuzz = fuzzy.FuzzyDecimal(8.0, precision=3)
+
+ with mock.patch('random.uniform', fake_uniform):
+ res = fuzz.evaluate(2, None, False)
+
+ self.assertEqual(Decimal(8.001).quantize(Decimal(10) ** -3), res)
+
+
class FuzzyDateTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
@@ -137,12 +188,12 @@ class FuzzyDateTestCase(unittest.TestCase):
def test_invalid_definition(self):
self.assertRaises(ValueError, fuzzy.FuzzyDate,
- self.jan31, self.jan1)
+ self.jan31, self.jan1)
def test_invalid_partial_definition(self):
with utils.mocked_date_today(self.jan1, fuzzy):
self.assertRaises(ValueError, fuzzy.FuzzyDate,
- self.jan31)
+ self.jan31)
def test_biased(self):
"""Tests a FuzzyDate with a biased random.randint."""
@@ -197,12 +248,12 @@ class FuzzyNaiveDateTimeTestCase(unittest.TestCase):
def test_aware_start(self):
"""Tests that a timezone-aware start datetime is rejected."""
self.assertRaises(ValueError, fuzzy.FuzzyNaiveDateTime,
- self.jan1.replace(tzinfo=compat.UTC), self.jan31)
+ self.jan1.replace(tzinfo=compat.UTC), self.jan31)
def test_aware_end(self):
"""Tests that a timezone-aware end datetime is rejected."""
self.assertRaises(ValueError, fuzzy.FuzzyNaiveDateTime,
- self.jan1, self.jan31.replace(tzinfo=compat.UTC))
+ self.jan1, self.jan31.replace(tzinfo=compat.UTC))
def test_force_year(self):
fuzz = fuzzy.FuzzyNaiveDateTime(self.jan1, self.jan31, force_year=4)
@@ -255,12 +306,12 @@ class FuzzyNaiveDateTimeTestCase(unittest.TestCase):
def test_invalid_definition(self):
self.assertRaises(ValueError, fuzzy.FuzzyNaiveDateTime,
- self.jan31, self.jan1)
+ self.jan31, self.jan1)
def test_invalid_partial_definition(self):
with utils.mocked_datetime_now(self.jan1, fuzzy):
self.assertRaises(ValueError, fuzzy.FuzzyNaiveDateTime,
- self.jan31)
+ self.jan31)
def test_biased(self):
"""Tests a FuzzyDate with a biased random.randint."""
@@ -314,22 +365,22 @@ class FuzzyDateTimeTestCase(unittest.TestCase):
def test_invalid_definition(self):
self.assertRaises(ValueError, fuzzy.FuzzyDateTime,
- self.jan31, self.jan1)
+ self.jan31, self.jan1)
def test_invalid_partial_definition(self):
with utils.mocked_datetime_now(self.jan1, fuzzy):
self.assertRaises(ValueError, fuzzy.FuzzyDateTime,
- self.jan31)
+ self.jan31)
def test_naive_start(self):
"""Tests that a timezone-naive start datetime is rejected."""
self.assertRaises(ValueError, fuzzy.FuzzyDateTime,
- self.jan1.replace(tzinfo=None), self.jan31)
+ self.jan1.replace(tzinfo=None), self.jan31)
def test_naive_end(self):
"""Tests that a timezone-naive end datetime is rejected."""
self.assertRaises(ValueError, fuzzy.FuzzyDateTime,
- self.jan1, self.jan31.replace(tzinfo=None))
+ self.jan1, self.jan31.replace(tzinfo=None))
def test_force_year(self):
fuzz = fuzzy.FuzzyDateTime(self.jan1, self.jan31, force_year=4)