{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Writing Testable Numerics Code"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Here's the contents of a file containing numerics code:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "\u001b[34mimport\u001b[39;49;00m \u001b[04m\u001b[36mnumpy\u001b[39;49;00m \u001b[34mas\u001b[39;49;00m \u001b[04m\u001b[36mnp\u001b[39;49;00m\n",
            "\n",
            "\u001b[34mdef\u001b[39;49;00m \u001b[32mnorm_1\u001b[39;49;00m(ary):\n",
            "    \u001b[33m\"\"\"Computes the 1-norm of vectors or matrices *ary* passed in as numpy arrays.\"\"\"\u001b[39;49;00m\n",
            "    \n",
            "    \u001b[34mif\u001b[39;49;00m \u001b[36mlen\u001b[39;49;00m(ary.shape) == \u001b[34m1\u001b[39;49;00m:\n",
            "        \u001b[34mreturn\u001b[39;49;00m np.sum(np.abs(ary))\n",
            "    \u001b[34melif\u001b[39;49;00m \u001b[36mlen\u001b[39;49;00m(ary.shape) == \u001b[34m2\u001b[39;49;00m:\n",
            "        \u001b[34mreturn\u001b[39;49;00m np.max(np.sum(np.abs(ary), axis=\u001b[34m1\u001b[39;49;00m))\n",
            "    \u001b[34melse\u001b[39;49;00m:\n",
            "        \u001b[34mraise\u001b[39;49;00m \u001b[36mValueError\u001b[39;49;00m(\u001b[33m\"\u001b[39;49;00m\u001b[33mary must be vector or matrix\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m)\n"
          ]
        }
      ],
      "source": [
        "!pygmentize norms.py"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Note:\n",
        "\n",
        "- Docstring\n",
        "- Defensive programming"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "\u001b[34mimport\u001b[39;49;00m \u001b[04m\u001b[36mnumpy\u001b[39;49;00m \u001b[34mas\u001b[39;49;00m \u001b[04m\u001b[36mnp\u001b[39;49;00m\n",
            "\n",
            "\u001b[34mdef\u001b[39;49;00m \u001b[32mtest_norm_1\u001b[39;49;00m():\n",
            "    \u001b[34mfrom\u001b[39;49;00m \u001b[04m\u001b[36mnorms\u001b[39;49;00m \u001b[34mimport\u001b[39;49;00m norm_1\n",
            "\n",
            "    \u001b[34mfor\u001b[39;49;00m i \u001b[35min\u001b[39;49;00m \u001b[36mrange\u001b[39;49;00m(\u001b[34m10\u001b[39;49;00m):\n",
            "        A = np.random.randn(\u001b[34m20\u001b[39;49;00m, \u001b[34m20\u001b[39;49;00m)\n",
            "        x = np.random.randn(\u001b[34m20\u001b[39;49;00m)\n",
            "        \n",
            "        \u001b[34massert\u001b[39;49;00m norm_1(A\u001b[30;01m@x\u001b[39;49;00m) <= norm_1(A) * norm_1(x)\n"
          ]
        }
      ],
      "source": [
        "!pygmentize test_norms.py"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "* Now use [pytest](https://pytest.org) to run the test."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "\u001b[1m============================= test session starts ==============================\u001b[0m\n",
            "platform linux -- Python 3.7.2+, pytest-4.0.2, py-1.7.0, pluggy-0.8.0\n",
            "rootdir: /home/andreas, inifile: pytest.ini\n",
            "plugins: celery-4.2.1\n",
            "collected 1 item                                                               \u001b[0m\n",
            "\n",
            "test_norms.py \u001b[32m.\u001b[0m\u001b[36m                                                          [100%]\u001b[0m\n",
            "\n",
            "\u001b[32m\u001b[1m=========================== 1 passed in 0.13 seconds ===========================\u001b[0m\n"
          ]
        }
      ],
      "source": [
        "!python -m pytest"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "A typical use for these tests would be to run them on every commit to a codebase.\n",
        "\n",
        "Example: https://github.com/inducer/boxtree (click the \"Pipeline\" button)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": []
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.7.2+"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 2
}