{ "cells": [ { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Automated Testing and Automatic documentation\n", "\n", "## Foreword\n", "\n", "### ACSE 1 Lecture Four 15th October 2020 - Version 3.0.3" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "tags": [ "hide-cell" ] }, "outputs": [ { "data": { "text/html": [ "\n" ], "text/plain": [ "" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# This cell sets the css styles for the rest of the notebook.\n", "# Unless you are particlarly interested in that kind of thing,\n", "# you can run this once, thensafely ignore it\n", "\n", "%run add_colours.py\n", "css_styling()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "
\n", " \n", "Yesterday we covered:\n", "- Software environments.\n", " \n", "In this lecture we will cover:\n", "- Software Code Testing\n", "- Automated testing frameworks\n", "- Automatic Documentation Generation\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "
Contact details:
\n", "\n", "\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### By the end of this lecture you should:\n", " - Understand the basics of code testing and ways to automate it.\n", " - Understand the different sorts of test.\n", " - Have seen how to use Sphinx to generate software documentation from docstrings\n", " " ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Code Testing\n", "\n", "When writing software, especially scientific software, a key question is whether the code is correct, and provably correct.\n", "\n", "How do we check something for correctness? We test.\n", "\n", "### What is a test?\n", "\n", "The simplest kind of test is the ad hoc kind you run when hacking about with code. If I have created a new function `add(x, y)` which adds together two numbers and returns the result, then it might be a good idea to try something like\n", "```python\n", "print(add(1, 0) == 1)\n", "print(add(1, 1) == 2)\n", "```\n", "This can't catch every possible mode of failure. For example, an `add` defined function like\n", "```python\n", "def add(x, y):\n", " return x**2+y**2\n", "```\n", "would pass both the tests given above, despite not being returning `x+y`.\n", "\n", "The examples above are both examples of \"testing to pass\", in that we have written a statement we expect to return true, and will be happy if it does so. One important thing to check is that operations you expect to raise an exception error actually do so (i.e. testing to fail). There are usually ways of writing a test to fail as a test to pass if needed. E.g. for a `divide` operator:\n" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "True\n" ] } ], "source": [ "def divide(x, y):\n", " \"\"\"return x divided by y.\"\"\"\n", " return x/y\n", "\n", "\n", "## Check we get an error when dividing by zero.\n", "try:\n", " divide(1, 0)\n", " print(False)\n", "except ZeroDivisionError:\n", " print(True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "By writing our tests like this we can use a statemnt like `assert X` to turn the test from raising an exception if when it does what we want into raising an exception if it doesn't." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Test Driven Development\n", "\n", "The most extreme version of this philosopy is strict Test Driven Development (TDD), sometimes also called \"Red-Green\" testing. Here the idea is that when implementing new code *first* you write a test, ensuring that it fails (red), then *second* you write just enough code to pass the test you've writen (green). Finally you [refactor]() (i.e. rewrite) any code you believe can be improved (simplified or speeded up), _while ensuring that your existing tests pass or are fixed_. \n", "\n", "This is intended to be an iterative process, so once you are done with one test, you move on to the next. Let's have an example, pushing things as far as they can go:\n", "\n", "__Problem__: Write a function that returns the repeated elements in a list.\n", "\n", "So, first, we write a test.\n", "\n", "```python\n", "assert(f([0, 1, 1]) == 1)\n", "```\n", "\n", "Does it fail on a do nothing implementation?" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "False\n" ] } ], "source": [ "def f(x):\n", " \"\"\"Return the repeated elements in a list.\"\"\"\n", " pass\n", "\n", "\n", "print(f([0, 1, 1]) == 1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ok, our test is failing, let's actually write some code. A quick way to catch repeated elements is to use a set." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "True\n" ] } ], "source": [ "def f(x):\n", " \"\"\"Return the repeated elements in a list.\"\"\"\n", " vals = set()\n", " for _ in x:\n", " if _ in vals:\n", " return _\n", " else:\n", " vals.add(_)\n", "\n", "\n", "print(f([0, 1, 1]) == 1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Our test is now passing. There's no old code to improve, so we either write another test, or move on. Let's try having a couple of repeated elements\n", "```python\n", "f([0,1,1,0]) == [0,1]\n", "```" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "True\n", "False\n" ] } ], "source": [ "\n", "print(f([0, 1, 1]) == 1)\n", "print(f([0,1,1,0]) == [0,1])" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "False\n", "True\n" ] } ], "source": [ "def f(x):\n", " \"\"\"Return the repeated elements in a list.\"\"\"\n", " vals = set()\n", " out = []\n", " for _ in x:\n", " if _ in vals:\n", " out.append(_)\n", " else:\n", " vals.add(_)\n", " return sorted(out)\n", "\n", "\n", "print(f([0, 1, 1]) == 1)\n", "print(f([0, 1, 1, 0]) == [0, 1])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We've broken the first test. But it's actually the test which is bad, so we'll fix it." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "True\n", "True\n" ] } ], "source": [ "print(f([0, 1, 1]) == [1])\n", "print(f([0, 1, 1, 0]) == [0,1])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ok. Another iteration. What about if an element turns up 3 times?" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "False\n", "[0, 0, 1]\n" ] } ], "source": [ "print(f([0, 1, 1, 0, 0]) == [0, 1])\n", "print(f([0, 1, 1, 0, 0]))" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "True\n", "True\n", "True\n" ] } ], "source": [ "def f(x):\n", " \"\"\"Return the set of repeated elements in a list.\"\"\"\n", " vals = set()\n", " out = set()\n", " for _ in x:\n", " if _ in vals:\n", " out.add(_)\n", " else:\n", " vals.add(_)\n", " return out\n", "\n", "\n", "print(f([0, 1, 1]) == set([1]))\n", "print(f([0, 1, 1, 0]) == set([0, 1]))\n", "print(f([0, 1, 1, 0, 0]) == set([0, 1]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And so on. At some point you run out of tests to set and the code is finished." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "

Exercise One: TDD

\n", "\n", "\n", "Try and write your own attempt at TTD for the following problems:\n", "\n", "
    \n", "
  1. Write an implementation of a fizz buzz function. Fizz Buzz is a children's game, where players count up from one around a circle. \n", "
      \n", "
    • However, any number divisible by 3 is replaced by the word \"fizz\".
    • \n", "
    • Any number divisible by 5 is replaced by the word \"buzz\".
    • \n", "
    • Numbers divisible by both become \"fizz buzz\".
    • \n", "
    \n", " Players who say the worng thing lose the game. Writing a program to generate the first n integers in fizz buzz encoding has sometimes been used as an interview question for programmers.\n", "\n", "

    Your function should accept an integer, $x$, and return either $x$, 'fizz', 'buzz' or 'fizz buzz' according to the rules above.

  2. \n", " \n", "
    \n", " \n", " Remember, in TTD first you write a test for an aspect of the function, then you write the code to solve it.\n", " \n", "
  3. Write a program to put the elements of an $n$ dimensional numpy array, $X$, into order of size.\n", "
      \n", "
    • The code should have output $Y$ with Y[i,...]<[j,...] for all i<j.
    • \n", "
    • The code should have output $Y$ with Y[k,i..]<[k,j,..] for all i<j and fixed k
    • \n", "
    • And so on.
    • \n", "
    • Note that this means when written in the form\n", "

      [[..[x_1, .., x_n], [x_n+1, .., x_2n], .., x_N]]

      \n", "we have x_n< x_n+i for all i>0.
    • \n", "
  4. \n", "\n", "
  5. Write a function to accept or reject a string as a candidate for a password based on the following criteria:\n", "
      \n", "
    • The string is between 8 and 1024 characters long.
    • \n", "
    • The string contains a number
    • \n", "
    • The string contains a capital letter
    • \n", "
    • The string contains none of the following characters: @<>!
    • \n", "
    \n", "
\n", "\n", "Write only enough new code to satisfy each test you write, and to fix your previous tests, before moving on to another test stage. Once the test passes, remember to have a look at your code to see if anything can be refactored.\n", "\n", "The goal here is to concentrate on the TDD process, not on the code itself, but for completeness, [model answers](https://msc-acse.github/ACSE-1/lectures/lecture10-solutions.html) are available.\n", " \n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "At this point you can hopefully see that \n", "1. TDD is a fine ideal for correct software engineering\n", "2. No sane person would write all their tests at that level of detail and in that order all the time.\n", "\n", "However, there are still some very important ideas to pull out of the paradigm:\n", "\n", "1. Make sure your tests can fail if things go wrong.\n", "2. Consider both the usual and the (obvious) corner cases.\n", "3. Each bug you fix is a missing test you might need to add." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Ways to implement and run tests" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Lets have a slighly more concrete example. We'll look for solutions to the quadratic equation\n", "$$ ax^2 + bx + c =0,$$\n", "where $x$ is a real number. The formula for solutions is\n", "$$ x=\\frac{-b \\pm \\sqrt{b^2-4ac}}{2a}.$$\n", "Now to write some code in a module file" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "import numpy as np # for the sqrt function\n", "\n", "def solve_quadratic(a,b,c):\n", " \"\"\"Solve a quadratic equation ax^2+bx+c=0 in the reals\"\"\"\n", " if 0>> mean([1, 5, 9)\n", " 5\n", " \n", " \"\"\"\n", " return sum(x)/len(x)\n", "\n", "if __name__ == \"__main__\":\n", " import doctest\n", " doctest.testmod()\n", "\n", "```\n", "\n", "In this case we can run the test by calling the module as a script:\n", "```bash\n", "python3 -m docstring_test\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If the test suceeds it silently returns a successful exit code. If the test fails (e.g. we replace the out put of 5.0 in the example) then an error message is printed, looking like the following:\n", "\n", "```bash\n", "**********************************************************************\n", "File \"docstring_test.py\", line 6, in __main__.mean\n", "Failed example:\n", " mean([1, 5, 9])\n", "Expected:\n", " 3.0\n", "Got:\n", " 5.0\n", "**********************************************************************\n", "1 items had failures:\n", " 1 of 1 in __main__.mean\n", "***Test Failed*** 1 failures.\n", "```\n", "\n", "Doctests can also be run in Python on plain text files as\n", "\n", "```python\n", "import doctest\n", "doctest.testfile(\"example.txt\")\n", "```\n", "or from the command line as\n", "\n", "```bash\n", "python -m doctest example.txt\n", "```\n", "\n", "In fact, you can use the same syntax to skip cluttering up your code with the `if __name__ == \"__main__\":` block in your python modules." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "

Exercise Two: doctest

\n", "\n", "

Write some tests using the doctest module inside your own module or script.

\n", "\n", "
    \n", "
  1. First write some tests which pass.
  2. \n", "
  3. Next write some tests which do not pass.
  4. \n", "
  5. Swap your work with another student (why not use github?).
  6. \n", "
  7. Fix their failing tests, either by editing the code or changing the tests.
  8. \n", "
\n", "\n", "

You can use some of the modules you wrote earlier in the course, or else write some new code.

\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### The `unittest` module\n", "\n", "It's still useful to automate things a bit more, so that we can . Python provides an inbuilt `unittest` module, which (with some work) can be used to build a test framework. It introduces the basic concept of the three stages of a test:\n", "1. Set up. We create anything which must already exist for a test to make sense.\n", "2. Running. The test itself operates\n", "3. Tear down. We clean up anything which won't get dealt with automatically" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "The file `unittest_example.py` contains the following code\n", "\n", "```python\n", "import unittest\n", "\n", "import numpy as np # for the sqrt function\n", "\n", "def solve_quadratic(a,b,c):\n", " \"\"\"Solve a quadratic equation ax^2+bx+c=0 in the reals\"\"\"\n", " if 0\n", "\n", "

Exercise Three: unittest

\n", "\n", "Try this yourself by writing a unittest using the `unittest` module. You can start from some of the code you wrote for the introductory exercises, or earlier in the week if you want. Try breaking and then fixing the test.\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Pytest\n", "\n", "The `pytest` package (not included in a default Python installation) simplifies the actions of writing tests even further, as well as providing a more informative interface. Pytest can be installed with\n", "```bash\n", "pip install pytest\n", "```\n", "which also adds a tool of the same name which can be run from the command line. This tool can be used to run both `doctest`s, (add the `--doctest-modules`) and unit tests based on the unit test module (just leave out the `unittest.main()`), as well as tests in its own format.\n", "\n", "We have created a [GitHub repository](https://github.com/jrper/CI) in which the file `pytest_example.py` contains the following code\n", "\n", "```python\n", "\n", "def test_solve_quadratic():\n", " \n", " assert foo.solve_quadratic(1,0,-1)==(-1.0,1.0)\n", " assert foo.solve_quadratic(1,0,0)==0.0\n", " assert foo.solve_quadratic(1,0,1) is None\n", " \n", "```\n", "\n", "while the `foo.py` module contains our `solve_quadratic` function. We can run the pytest tests as well as the others using the following syntax:\n", "\n", "```bash\n", "python -m pytest --doctest-modules pytest_example.py docstring_test.py unittest_example.py\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "

Exercise Four: Pytest

\n", "\n", "
    \n", "
  • Clone the repository using git, install pytest using pip or conda and run the tests.
  • \n", "
  • Try breaking some of the tests by editting the .py files.
  • \n", "
  • Write your own pytest test for some of your code.
  • \n", "
\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Code coverage.\n", "\n", "In general, it is best practice to ensure that your tests exercise every line of your code at least once. This is especially true for interpretted languages like Python, which only checks the syntax of a file (i.e its \"grammar\") when it is first loaded, but doesn't check that the meaning makes sense.\n", "\n", "That means you can write code like the following in a module file and it will load without error\n", "\n", "__example.py:__\n", "```python\n", "def f(x):\n", " return y\n", "```\n", "\n", "but trying to actually run it will error out, in this case with a NameError.\n", "\n", "```python\n", "import example\n", "example.f(1)\n", "```\n", "\n", "returns\n", "```\n", "NameError: name 'y' is not defined\n", "```\n", "\n", "There is a Python package, `pytest-cov`, which adds coverage information to `pytest`. Once installed with `pip`, coverage information can be collected for a package or module called `mycoolproject` by calling `pytest` as \n", "\n", "```\n", "python -m pytest --cov=mycoolproject .\n", "```\n", "\n", "This will generate output like\n", "```bash\n", "---------- coverage: platform darwin, python 3.6.8-final-0 -----------\n", "Name Stmts Miss Cover\n", "------------------------------------------------------\n", "mycoolproject/__init__.py 4 0 100%\n", "mycoolproject/analysis.py 33 24 27%\n", "mycoolproject/live.py 58 40 31%\n", "mycoolproject/tests/test_example.py 11 1 91%\n", "mycoolproject/validator.py 11 0 100%\n", "------------------------------------------------------\n", "TOTAL 117 65 56%\n", "```\n", "\n", "Note that while 100% code coverage is useful, it only means that the code has been run, not that every possible code logic branch has been tested. It is a useful, but neither necessary nor sufficient clue to healthy code.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The many sorts of test\n", "\n", "When developing software, opportunities (and often the need) to test appear at many different levels, and these families have developed their own names.\n", "\n", "#### Unit tests\n", "\n", "Unit tests are small and (ideally) quick tests which verify the behaviour of a single programming \"unit\" (i.e. a module or function). Thus they ensure that the unit satisfies the \"contract\" it makes in isolation.\n", "\n", "Since the function is tested by itself, rather than on the job, inputs must be \"mocked up\" (i.e. hardcoded at compile time, or replaced with trivial substitutes) to generate known outputs and external dependencies may be replaced with \"test stubs\" which short circuit to quickly give the information needed by the unit being tested. The `unittest` module defines a useful class for this, called `unittest.Mock`. It also provides some other helpful functions. \n", "\n", "#### Integration tests\n", "\n", "Tests which combine multiple program units together and confirm that the interaction proceeds as expected. This might mean chaining multiple functions you write together, or using the real external dependencies rather than the fakes you generated for your unit tests. Integration tests tend exercise more code at once, but are slower to run and can be difficult to write effectively\n", "\n", "#### Feature/Functionality tests\n", "\n", "Tests which confirm an entire feature is working successfully as a whole, effectively interacting with your software as a user would. For numerical codes, this often involves analytic solutions and/or using simple \"toy\" problems which are well understood and have nice solutions. For more general software, it might even involve testing human interaction to make sure that GUIs are clear and robust.\n", "\n", "#### Regression tests\n", "\n", "Tests which check that fixed bugs stay fixed. I.e. that introducing a new change does not break the existing functionality. The tests may be at the level of the unit, integration or features. If bug reports have a minimal example attached, then this can provide the basis for useful examples for regression testing (this is a form of \"red-green\" testing similar to test driven development)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Automatic documentation\n", "\n", "Documentation is written for three broad groups of people:\n", "\n", "1. Users (\"I don't care how it does it, I care what it does.\")\n", "1. User-Developers (\"I build what I need.\")\n", "1. Developers (\"I make it, but I don't use it.\")\n", "\n", "These individuals have different needs from the documentation you provide to them. Users want to know what software can do, and how to run it for their own problems. Developers need to understand how code works, but not necessarily what it's intended for in a wider sense. User-developers need to understand both sides. Commercial software is generally written by pure developers, but a lot of scientific software (and many other small projects) is written by user-developers.\n", "\n", "For all groups, the most important piece of documentation in Python is the docstring, since it remains with the code it applies to, and connects to the Python online help. For developers & user-developers, additional comments in the body of the source text may also be useful, however users will probably never choose to look there.\n", "\n", "Although it is now generally acknowledged that the best place for living documentation is in the source code, near where it applies, since this gives the best probability of developers updating it when they changes, it is still useful to maintain a proper (electronic) manual or write-up0, both for ease of reference, and to give a general project overview.\n", "\n", "Several tools have been developed to close this gap by automatically collating comments and function call signatures from the source code and converting it into a human readable document to label as a \"manual\". Perhaps the most famous open source, cross-language documentation tool is [Doxygen](http://www.doxygen.nl). However we'll look further at a tool called [Sphinx](), which originated in the Python community and is the tool of choice on several large Python projects including SciPy, Django and [Python itself](https://docs.python.org/3/about.html) \n", "\n", "### Sphinx\n", "\n", "You can install the core Sphinx documentation generation tools with the command\n", "\n", "```bash\n", "pip install sphinx\n", "```\n", "\n", "Sphinx works by converting [reStructured text](http://docutils.sourceforge.net/docs/user/rst/quickstart.html), whether inside docstrings or in special files into HTML pages or PDF files.\n", "\n", "The simplest use pattern is probably to use the automatic scanning tools to collect together all the docstrings into an index of APIs.\n", "\n", "This requires creating two files, the first containing the configuration options for the Sphinx toosl, and the second containing a skeleton reStructured text file into which to slot the generated documentation:\n", "\n", "_`docs/conf.py`_:\n", "\n", "```python\n", "import sys\n", "import os\n", "\n", "## We're working in the ./docs directory, but need the package root in the path\n", "## This command appends the directory one level up, in a cross-platform way. \n", "sys.path.insert(0, os.path.abspath(os.sep.join((os.curdir, '..'))))\n", "\n", "project = 'MyCoolProject'\n", "extensions = ['sphinx.ext.autodoc',\n", " 'sphinx.ext.napoleon',\n", " 'sphinx.ext.mathjax']\n", "source_suffix = '.rst'\n", "master_doc = 'index'\n", "exclude_patterns = ['_build']\n", "autoclass_content = \"both\"\n", "```\n", "\n", "_`docs/index.rst`_:\n", "\n", "```rst\n", "#############\n", "mycoolproject\n", "#############\n", "\n", "\n", "A heading\n", "---------\n", "\n", "This is just example text, perhaps with mathematics like :math:`x^2`, **bold text** and *italics*.\n", "It might also include citations [1]_ inline references to functions like :func:`my_func` or even whole code blocks::\n", "\n", " def my_example(a, b):\n", " \"\"\"Do something!\"\"\"\n", " return a**b+1\n", "\n", ".. automodule:: mycoolproject\n", " :members:\n", " \n", " \n", ".. rubric:: References\n", "[1] http://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#citations\n", "```\n", "\n", "\n", "With this setup we can build a `html` version of the documentation with the command\n", "\n", "```bash\n", "sphinx-build -b html docs docs/html\n", "```\n", "\n", "
\n", " \n", "
Warning
\n", "\n", "Microsoft Windows uses `\\` as the separator symbol between levels in the directory tree. Meanwhile Linux and Mac OSX use `/`. This makes it almost impossible for human-readable notes to give paths that work on both sets of computers (it's a lot easier for Python code, just use `os.sep` from the `os` module. In this section I use the \\*nix standard of `/` in the write-up, to match the Sphinx documentation. Please remember to convert in your head on a Windows computer.\n", "\n", "
\n", "\n", "If this is successful, you should be able to open `./docs/html/index.html` to see documentation automatically generated from the docstrings in your project. Sphinx also supports other output formats (for example LaTeX) with the `-b` flag. A recipe to generate a `pdf` manual on a suitably configured system is\n", "\n", "```bash\n", "sphinx-build -b latex docs .\n", "pdflatex MyCoolProject.tex\n", "pdflatex MyCoolProject.tex\n", "```\n", "\n", "which will generate `MyCoolProject.pdf`. We run LaTeX twice to ensure that references and citations (including the index) are set correctly. \n", "\n", "\n", "
\n", "\n", "

Exercise Five: Autodocumenting your module

\n", "\n", "
  • Use pip or conda to install sphinx on your computer.
  • \n", "
  • Create a docs directory inside your module and add conf.py and index.rst files based on the ones given above.
  • \n", "
  • Run sphinx-build to generate html documentation for your project.
  • \n", "
  • Try editting the index.rst file to add more text.
\n", " \n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "

In this lecture we learned

\n", "\n", "
    \n", "
  • How to write a formal test
  • \n", "
  • The basics of Test Driven Development
  • \n", "
  • To use automated testing using testing frameworks and py.test
  • \n", "
  • Documentation via Sphinx
  • \n", "
\n", "\n", "Next week: The Cloud, Continous Integration and Pandas \n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Further Reading\n", "\n", "- The Python documentaion on [unittest](https://docs.python.org/3/library/unittest.html) and [doctest](https://docs.python.org/3/library/doctest.html).\n", "- The Pytest [documentation](https://pytest.readthedocs.io/en/latest/).\n", "- A software carpentry [lecture](https://intermediate-and-advanced-software-carpentry.readthedocs.io/en/latest/testing-python.html) on testing.\n", "- [Travis-ci.com tutorials](https://docs.travis-ci.com).\n", "- The Python documentation page on [venv](https://docs.python.org/3.6/library/venv.html)\n", "- A much fuller [Sphinx tutorial](https://pythonhosted.org/an_example_pypi_project/sphinx.html)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "celltoolbar": "Tags", "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.6.9" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": true, "sideBar": true, "skip_h1_title": true, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": false, "toc_position": {}, "toc_section_display": true, "toc_window_display": true } }, "nbformat": 4, "nbformat_minor": 2 }