diff options
-rw-r--r-- | docs/fuzzy.rst | 35 | ||||
-rw-r--r-- | factory/fuzzy.py | 29 | ||||
-rw-r--r-- | tests/test_fuzzy.py | 71 |
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) |