{ "cells": [ { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Property-based testing" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from hypothesis import given, assume, example, note, settings, strategies as st\n", "from hypothesis.stateful import RuleBasedStateMachine, rule, invariant\n", "from random import randint\n", "from collections import Counter\n", "import unittest" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Motivation" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Would you accept `plus`?" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "def plus(x, y):\n", " if x and y:\n", " return 2\n", " return 1" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "assert plus(1, 0) == 1\n", "assert plus(0, 1) == 1\n", "assert plus(1, 1) == 2" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "def plus(x, y):\n", " if x and y:\n", " return 2\n", " return 1\n" ] } ], "source": [ "%history 2" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "def plus_v2(a, b):\n", " return a + b" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Would you accept `plus_v2`?" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "for _ in range(1000):\n", " a = randint(-100, 100)\n", " b = randint(-100, 100)\n", " assert plus_v2(a, b) == a + b" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "def plus_v2(a, b):\n", " return a + b\n" ] } ], "source": [ "%history 3" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Can we test `plus_v2` without `+`?" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "def associative(a, b):\n", " return plus_v2(plus_v2(a, b), 1) == plus_v2(a, plus_v2(b, 1))" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "def commutative(a, b):\n", " return plus_v2(a, b) == plus_v2(b, a)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "def zero_is_identity(a):\n", " return plus_v2(a, 0) == a" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "for _ in range(1000):\n", " a = randint(-100, 100)\n", " b = randint(-100, 100)\n", " assert associative(a, b)\n", " assert commutative(a, b)\n", " assert zero_is_identity(a)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Rewriting in Hypothesis" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "@given(a=st.integers(), b=st.integers())\n", "def test_plus_v2(a, b):\n", " assert associative(a, b)\n", " assert commutative(a, b)\n", " assert zero_is_identity(a)\n", " \n", "test_plus_v2()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Generating values" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "-30412" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "st.integers().example()" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "-1e-05" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "st.floats().example()" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "u'\\U0005423a'" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "st.text().example()" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "[False]" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "st.lists(st.booleans()).example()" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "['Context',\n", " 'Decimal',\n", " 'FloatKey',\n", " 'Fraction',\n", " 'InvalidArgument',\n", " 'LRUReusedCache',\n", " 'NOTHING',\n", " 'Nothing',\n", " 'RandomSeeder',\n", " 'ResolutionFailed',\n", " 'STRATEGY_CACHE',\n", " 'SearchStrategy',\n", " '_AVERAGE_LIST_LENGTH',\n", " '__all__',\n", " '__builtins__',\n", " '__doc__',\n", " '__file__',\n", " '__name__',\n", " '__package__',\n", " '_defer_from_type',\n", " '_strategies',\n", " 'absolute_import',\n", " 'assume',\n", " 'base_defines_strategy',\n", " 'binary',\n", " 'booleans',\n", " 'builds',\n", " 'cacheable',\n", " 'ceil',\n", " 'characters',\n", " 'check_strategy',\n", " 'check_type',\n", " 'check_valid_bound',\n", " 'check_valid_integer',\n", " 'check_valid_interval',\n", " 'check_valid_sizes',\n", " 'choices',\n", " 'complex_numbers',\n", " 'composite',\n", " 'convert_value',\n", " 'count_between_floats',\n", " 'data',\n", " 'dates',\n", " 'datetimes',\n", " 'decimals',\n", " 'deferred',\n", " 'defines_strategy',\n", " 'defines_strategy_with_reusable_values',\n", " 'dictionaries',\n", " 'division',\n", " 'dt',\n", " 'enum',\n", " 'fixed_dictionaries',\n", " 'float_to_int',\n", " 'floats',\n", " 'floor',\n", " 'fractions',\n", " 'from_regex',\n", " 'from_type',\n", " 'frozensets',\n", " 'gcd',\n", " 'get_type_hints',\n", " 'getfullargspec',\n", " 'hrange',\n", " 'implements_iterator',\n", " 'infer',\n", " 'int_to_float',\n", " 'integers',\n", " 'is_negative',\n", " 'isclass',\n", " 'isfunction',\n", " 'iterables',\n", " 'just',\n", " 'lists',\n", " 'math',\n", " 'none',\n", " 'not_set',\n", " 'note_deprecation',\n", " 'nothing',\n", " 'one_of',\n", " 'operator',\n", " 'permutations',\n", " 'print_function',\n", " 'proxies',\n", " 'random_module',\n", " 'randoms',\n", " 'recursive',\n", " 'reduce',\n", " 'register_type_strategy',\n", " 'renamed_arguments',\n", " 'required_args',\n", " 'runner',\n", " 'sampled_from',\n", " 'sets',\n", " 'shared',\n", " 'streaming',\n", " 'text',\n", " 'text_type',\n", " 'timedeltas',\n", " 'times',\n", " 'try_convert',\n", " 'tuples',\n", " 'uuids']" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dir(st)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Property-based testing patterns\n", "\n", "* \"Doesn't blow up\"\n", "* Encode/decode\n", "* Idempotence\n", "* Oracle" ] } ], "metadata": { "celltoolbar": "Slideshow", "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.14" } }, "nbformat": 4, "nbformat_minor": 1 }