{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Hypothesis: Property-based testing\n", "\n", "In this notebook we use property-based testing to find problems in our code. [Hypothesis](https://hypothesis.readthedocs.io/en/latest/) is a library similar to Haskell’s [Quickcheck](https://hackage.haskell.org/package/QuickCheck). We’ll get to know it in more detail later, along with other test libraries: Hypothesis. [Hypothesis](https://jupyter-tutorial.readthedocs.io/en/latest/notebook/testing/hypothesis.html) can also provide mock objects and tests for numpy data types." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1. Imports" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import re\n", "\n", "from hypothesis import assume, given\n", "from hypothesis.strategies import emails, integers, tuples" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. Find range" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "def calculate_range(tuple_obj):\n", " return max(tuple_obj) - min(tuple_obj)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. Test with `strategies` and `given`\n", "\n", "With [hypothesis.strategies](https://hypothesis.readthedocs.io/en/latest/data.html) you can create different test data. For this, Hypothesis provides strategies for most types and arguments restrict the possibilities to suit your needs. In the example below, we use the [integers](https://hypothesis.readthedocs.io/en/latest/data.html#hypothesis.strategies.integers) strategy, which is applied to the function with the [Python-Decorator](https://docs.python.org/3/glossary.html#term-decorator) `@given`. More specifically, it takes our test function and converts it into a parameterised one to run over wide ranges of matching data:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "@given(tuples(integers(), integers(), integers()))\n", "def test_calculate_range(tup):\n", " result = calculate_range(tup)\n", " assert isinstance(result, int)\n", " assert result > 0" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "ename": "AssertionError", "evalue": "", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[4], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mtest_calculate_range\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n", "Cell \u001b[0;32mIn[3], line 2\u001b[0m, in \u001b[0;36mtest_calculate_range\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;129m@given\u001b[39m(tuples(integers(), integers(), integers()))\n\u001b[0;32m----> 2\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mtest_calculate_range\u001b[39m(tup):\n\u001b[1;32m 3\u001b[0m result \u001b[38;5;241m=\u001b[39m calculate_range(tup)\n\u001b[1;32m 4\u001b[0m \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(result, \u001b[38;5;28mint\u001b[39m)\n", " \u001b[0;31m[... skipping hidden 1 frame]\u001b[0m\n", "Cell \u001b[0;32mIn[3], line 5\u001b[0m, in \u001b[0;36mtest_calculate_range\u001b[0;34m(tup)\u001b[0m\n\u001b[1;32m 3\u001b[0m result \u001b[38;5;241m=\u001b[39m calculate_range(tup)\n\u001b[1;32m 4\u001b[0m \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(result, \u001b[38;5;28mint\u001b[39m)\n\u001b[0;32m----> 5\u001b[0m \u001b[38;5;28;01massert\u001b[39;00m result \u001b[38;5;241m>\u001b[39m \u001b[38;5;241m0\u001b[39m\n", "\u001b[0;31mAssertionError\u001b[0m: ", "\u001b[0mFalsifying example: test_calculate_range(\n tup=(0, 0, 0),\n)" ] } ], "source": [ "test_calculate_range()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we correct the test with `>=` and check it again:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "@given(tuples(integers(), integers()))\n", "def test_calculate_range(tup):\n", " result = calculate_range(tup)\n", " assert isinstance(result, int)\n", " assert result >= 0" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "test_calculate_range()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. Check against regular expressions\n", "\n", "[Regular expressions](https://en.wikipedia.org/wiki/Regular_expression) can be used to check strings for certain syntactical rules. In Python, you can use [re.match](https://docs.python.org/3/library/re.html#re.match) to check regular expressions.\n", "\n", "