{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Testing your software in Python\n", "\n", "We'll follow the concise [Software Carpentry Testing Tutorial](http://katyhuff.github.io/python-testing) authored by Katy Huff.\n", "\n", "\n", "## A simple, testable `mean`\n", "\n", "Let's checkpoint here with a directory that should have:\n", "\n", "- `mymean.py`:\n", "\n", "```python\n", "\n", "def mean(num_list):\n", " first = num_list[0]\n", " if isinstance(first, complex):\n", " return NotImplemented\n", " return sum(num_list)/len(num_list)\n", "```\n", "\n", "- `tests.py`:\n", "\n", "```python\n", "from mymean import mean\n", "\n", "def test_mean():\n", " assert mean([1]) == 1\n", "\n", "def test_ints():\n", " num_list = [1, 2, 3, 4, 5]\n", " obs = mean(num_list)\n", " exp = 3\n", " assert obs == exp\n", "\n", "def test_zero():\n", " num_list=[0,2,4,6]\n", " obs = mean(num_list)\n", " exp = 3\n", " assert obs == exp\n", "\n", "def test_double():\n", " # This one will fail in Python 2\n", " num_list=[1,2,3,4]\n", " obs = mean(num_list)\n", " exp = 2.5\n", " assert obs == exp\n", "\n", "def test_long():\n", " big = 100000000\n", " obs = mean(range(1,big))\n", " exp = big/2.0\n", " assert obs == exp\n", "\n", "def test_complex():\n", " # given that complex numbers are an unordered field\n", " # the arithmetic mean of complex numbers is meaningless\n", " num_list = [2 + 3j, 3 + 4j, -32 - 2j]\n", " obs = mean(num_list)\n", " exp = NotImplemented\n", " assert obs == exp\n", "```\n", "\n", "- And let's also put in there a simple `.gitignore`:\n", "\n", "```\n", ".DS_Store\n", "*~\n", "__pycache__\n", "*.py[co]\n", ".coverage\n", ".ipynb_checkpoints\n", "Untitled*.ipynb\n", "_configtest.o.d\n", "```\n", "\n", "Next, let's make this into a public repo on our personal accounts. [Here's mine](https://github.com/fperez/testing) (note that one has a few more things that we'll add later)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Continuous Integration with Travis\n", "\n", "Let's setup [Travis CI](https://github.com/marketplace/travis-ci) for Github, using **the free plan for Open Source**:\n", "\n", "\n", "\n", "You'll need to accept the Github authorization for Travis to access your account. [More info here on the Travis docs](https://docs.travis-ci.com/user/getting-started).\n", "\n", "Once that's set up and your repo is up on github, go to your [Travis Profile page](https://travis-ci.org/profile) and turn on the green checkmark to authorize this repo for Travis builds:\n", "\n", "\n", "\n", "*Note:* if you don't see your repo listed, click on the green \"Sync Account\" button in the top right." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Running your tests on Travis\n", "\n", "Now, let's have Travis run the tests for us on each commit, automatically!\n", "\n", "Add the following, absolutely minimal `.travis.yml` file to your repo:\n", "\n", "```yaml\n", "language: python\n", "python:\n", " - \"3.6\"\n", "install:\n", " - pip install pytest\n", "script:\n", " - pytest tests.py\n", "```\n", "\n", "Once you make a `git push` to your repo, you should see a build start, e.g:\n", "\n", "\n", "\n", "and if all goes well, you should then see something like this in the logs:\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Exercise\n", "\n", "* Add a test of your `mean` function that uses numpy:\n", "\n", " - Generate 10,000 uniformly distributed random numbers with numpy.\n", " - Test that your mean is a proper numerical match to the numpy one.\n", " \n", "* Once this test passes locally, add it to your Travis run.\n", "\n", "### Travis + Conda\n", "\n", "If you want to build your travis run with Conda, you can create a proper environment as part of your Travis setup, here's a simple example `.travis.yml` that uses Conda:\n", "\n", "```yaml\n", "language: python\n", "python:\n", " # We don't actually use the Travis Python, but this keeps it organized.\n", " - \"3.6\"\n", "install:\n", " - sudo apt-get update\n", " - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh;\n", " - bash miniconda.sh -b -p $HOME/miniconda\n", " - export PATH=\"$HOME/miniconda/bin:$PATH\"\n", " - hash -r\n", " - conda config --set always_yes yes --set changeps1 no\n", " - conda update -q conda\n", " # Useful for debugging any issues with conda\n", " - conda info -a\n", "\n", " # List your dependencies here to create the necessary environment\n", " - conda create -q -n test-environment python=$TRAVIS_PYTHON_VERSION pytest\n", " - source activate test-environment\n", "\n", "script:\n", " - pytest tests.py\n", "```\n", "\n", "### Exercise: conda with an `environment.yml` file for Travis\n", "\n", "Explore whether it's possible to modify the above Travis setup to use a proper `environment.yml` file.\n", "\n", "### Exercise: add a README and status badge to your repo\n", "\n", "If you add a README file, you can display in it the build status of your repo [thanks to Travis' status badges](https://docs.travis-ci.com/user/status-images). It will look like this:\n", "\n", "![](fig/travis-badge.png)\n", "\n", "and that badge is actually a live link to your Travis build page for the repo." ] } ], "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.6.3" } }, "nbformat": 4, "nbformat_minor": 2 }