{
"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
}