Slide 1

Slide 1 text

Hypothesis Property Based Testing

Slide 2

Slide 2 text

class HistoricalEvent(object): def __init__(self, event_id, description, event_time): """ :type id: int :type description: str :type event_time: datetime.datetime """ self.id = event_id self.description = description self.event_time = event_time def __eq__(self, other): return self.__dict__ == other.__dict__ def __repr__(self): return str(self.__dict__)

Slide 3

Slide 3 text

class EventEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, HistoricalEvent): return { "id": obj.id "description": obj.description "event_time": obj.event_time.isoformat(), } return json.JSONEncoder.default(self, obj)

Slide 4

Slide 4 text

def fromJson(dct): """ :type data: dict Data hopefully representing a Historical Event :return: HistoricalEvent """ return HistoricalEvent( dct['id'], dct['description'], dateutil.parser.parse(dct['event_time']) )

Slide 5

Slide 5 text

Key Property

Slide 6

Slide 6 text

Hypothesis ● Based On Haskell’s QuickCheck ● Fantastic Docs ● Property Based Testing ○ You define the properties of the input and the output ○ It throws input matching your spec at your tests using strategies ○ Reduces failures to simple cases

Slide 7

Slide 7 text

@given( st.integers(), st.text(), datetimes(timezones=['UTC']) ) def test_to_from_json(self, id, desc, date): original_event = new HistoricalEvent(id, desc, date) encoded_event = json.dumps(event, cls=EventEncoder) decoded_event = json.loads( encoded_event, object_hook=HistoricalEvent.fromJson ) assert decoded_event == original_event

Slide 8

Slide 8 text

Ran 1 tests in 0.734s Falsifying example: test_serialization_deserialization( self=, user={ 'event_time': datetime.datetime(99, 1, 1, 0, 0, tzinfo=), 'id': 0, 'description': '' } ) Successfully shrunk example 64 times

Slide 9

Slide 9 text

def fromJson(dct): """ :type data: dict Data hopefully representing a Historical Event :return: HistoricalEvent """ return HistoricalEvent( dct['id'], dct['description'], dateutil.parser.parse(dct['event_time']) )

Slide 10

Slide 10 text

In [0]: example = datetime(99, 1, 1, 0, 0) In [1]: example.isoformat() Out[1]: '0099-01-01T00:00:00' In [2]: dateutil.parser.parse('0099-01-01T00:00:00') Out[2]: datetime.datetime(1999, 1, 1, 0, 0)

Slide 11

Slide 11 text

This flaw was not contrived

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

Takeaways ● Functionality tested with very little code ● This was not an obvious issue ○ But a real one! The first Century was a thing!

Slide 15

Slide 15 text

Thanks Matt Bachmann GitHub @mattbachmann Slides: https://goo.gl/zdNnTW