summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPrabhu Ramachandran2017-02-22 14:56:35 +0530
committerPrabhu Ramachandran2017-02-22 14:56:35 +0530
commitd5c20e445b193fad5cb4339f00e11212fdd39c0a (patch)
tree94425bf5693fd7797fe128678614b4de364b0df9
parentba725c5af9ac1c7064211589ff561fb79f3853b4 (diff)
downloadpython-workshops-d5c20e445b193fad5cb4339f00e11212fdd39c0a.tar.gz
python-workshops-d5c20e445b193fad5cb4339f00e11212fdd39c0a.tar.bz2
python-workshops-d5c20e445b193fad5cb4339f00e11212fdd39c0a.zip
Add basic slides for workshop on pytest.
-rw-r--r--testing/README.md53
-rw-r--r--testing/code/gcd.py9
-rw-r--r--testing/code/test_class.py11
-rw-r--r--testing/code/test_fixtures.py51
-rw-r--r--testing/code/test_gcd.py21
-rw-r--r--testing/code/test_list_gitrepo.py46
-rw-r--r--testing/macros.tex88
-rw-r--r--testing/pytest.tex669
8 files changed, 948 insertions, 0 deletions
diff --git a/testing/README.md b/testing/README.md
new file mode 100644
index 0000000..06e6c4e
--- /dev/null
+++ b/testing/README.md
@@ -0,0 +1,53 @@
+# Python testing with `pytest`
+
+This is a very simple outline of this workshop:
+
+- General introduction to testing and Test Driven Development (TDD)
+
+- Start with a very simple example, `gcd`.
+
+- Show how to do the simplest TDD using `pytest`.
+
+- Basic `pytest` features:
+ - Testing cases where an exception should be raised
+ - Testing floating point numbers with `pytest.approx`
+ - Grouping tests in classes.
+
+- Next take a more complex problem:
+ - Find all the git repositories recursively inside a given directory.
+ - Write tests for this.
+ - General approach on how to do this.
+ - Use TDD.
+
+- Writing fixtures:
+ - Previous example as motivation for this.
+ - Creating simple fixtures.
+ - Fixture cleanup.
+ - Fixture with data.
+ - Fixtures using other fixtures.
+ - Fixture scope and other ways to use fixtures with `pytest.mark`
+
+- Some pointers on how to write better tests.
+ - Link to useful articles.
+
+- Interlude, travis-ci
+ - Push code to github
+ - Setup travis-ci
+ - .travis.yml
+ - Run tests.
+ - Show updates and PRs.
+ - Add badge to README.
+
+- Coverage and improving tests.
+ - Look at coverage
+ - Show simple reports
+ - HTML reports.
+
+- Setup codecov.
+ - See how codecov shows the reports and tracks coverage
+ - Explore codecov.io
+
+- More on testing with pytest
+ - Using tmpdir.
+ - Using capsys.
+ - Using monkeypatch/mock.
diff --git a/testing/code/gcd.py b/testing/code/gcd.py
new file mode 100644
index 0000000..12e4935
--- /dev/null
+++ b/testing/code/gcd.py
@@ -0,0 +1,9 @@
+def gcd(a, b):
+ while b != 0:
+ a, b = b, a%b
+ return a
+
+def atoi(s):
+ return int(s)
+
+
diff --git a/testing/code/test_class.py b/testing/code/test_class.py
new file mode 100644
index 0000000..36f67bd
--- /dev/null
+++ b/testing/code/test_class.py
@@ -0,0 +1,11 @@
+
+class TestSomething(object):
+ def test_something_has_hello(self):
+ x = 'hello1'
+ y = 'hello world'
+ assert x in y
+
+ def test_something_else(self):
+ a = [1, 2, 3]
+ b = {1, 2, 3}
+ assert a == b
diff --git a/testing/code/test_fixtures.py b/testing/code/test_fixtures.py
new file mode 100644
index 0000000..4676509
--- /dev/null
+++ b/testing/code/test_fixtures.py
@@ -0,0 +1,51 @@
+import pytest
+
+
+@pytest.fixture
+def my_fixture():
+ print('my_fixture')
+
+
+def test_foo(my_fixture):
+ print('runing test')
+
+
+@pytest.fixture
+def fix1():
+ print('fix1')
+ yield
+ # Cleanup
+ print('Good bye: fix1')
+
+
+def test_fix1(fix1):
+ print('Running test_fix1')
+
+
+@pytest.fixture
+def fix2(my_fixture):
+ yield
+ # Cleanup
+ print('Good bye: fix2')
+
+
+def test_fix2(fix2):
+ print('Running test_fix2')
+
+@pytest.fixture
+def some_data(my_fixture):
+ print('some_data fixture')
+ yield {'a': 1, 'b': 2}
+ # Cleanup
+ print('Good bye: fix2')
+
+
+def test_some_data(some_data):
+ print(some_data)
+ print('Running test_fix2')
+
+
+
+@pytest.mark.usefixtures('fix1', 'fix2')
+def test_foo():
+ print('Running test_foo')
diff --git a/testing/code/test_gcd.py b/testing/code/test_gcd.py
new file mode 100644
index 0000000..d1ee7df
--- /dev/null
+++ b/testing/code/test_gcd.py
@@ -0,0 +1,21 @@
+from gcd import gcd, atoi
+
+def test_gcd():
+ assert gcd(48, 64) == 16
+ assert gcd(44, 19) == 1
+
+def test_atoi_works():
+ x = '1'
+ assert atoi(x) == 1
+
+import pytest
+
+def test_atoi_raises_error():
+ x = 'hello'
+ with pytest.raises(ValueError):
+ atoi(x)
+
+
+if __name__ == '__main__':
+ test_gcd()
+
diff --git a/testing/code/test_list_gitrepo.py b/testing/code/test_list_gitrepo.py
new file mode 100644
index 0000000..871e31f
--- /dev/null
+++ b/testing/code/test_list_gitrepo.py
@@ -0,0 +1,46 @@
+import tempfile
+import os
+import shutil
+import pytest
+
+
+def is_git_repo(path):
+ git_dir = os.path.join(path, '.git')
+ return os.path.isdir(git_dir)
+
+
+def find_all_git_repos(path):
+ pass
+
+
+@pytest.fixture
+def git_repo():
+ tmp_dir = tempfile.mkdtemp()
+ git_repo = os.path.join(tmp_dir, '.git')
+ if not os.path.exists(git_repo):
+ os.makedirs(git_repo)
+
+ yield tmp_dir
+
+ shutil.rmtree(tmp_dir)
+
+
+def test_is_git_repo_should_detect_git_repo(git_repo):
+ # Given
+ dir = git_repo
+
+ # When
+ result = is_git_repo(dir)
+
+ # Then
+ assert result == True
+
+
+def test_is_git_repo_should_not_detect_non_git_repo():
+ tmp_dir = tempfile.mkdtemp()
+ if not os.path.exists(tmp_dir):
+ os.makedirs(tmp_dir)
+ try:
+ assert is_git_repo(tmp_dir) == False
+ finally:
+ shutil.rmtree(tmp_dir)
diff --git a/testing/macros.tex b/testing/macros.tex
new file mode 100644
index 0000000..500912c
--- /dev/null
+++ b/testing/macros.tex
@@ -0,0 +1,88 @@
+
+% Modified from: generic-ornate-15min-45min.de.tex
+\mode<presentation>
+{
+ \usetheme{Warsaw}
+ \useoutertheme{infolines}
+ \setbeamercovered{transparent}
+}
+
+\usepackage[english]{babel}
+\usepackage[latin1]{inputenc}
+%\usepackage{times}
+\usepackage[T1]{fontenc}
+
+% Taken from Fernando's slides.
+\usepackage{ae,aecompl}
+\usepackage{mathpazo,courier,euler}
+\usepackage[scaled=.95]{helvet}
+
+\definecolor{darkgreen}{rgb}{0,0.5,0}
+
+\usepackage{listings}
+\lstset{language=Python,
+ basicstyle=\ttfamily\bfseries,
+ commentstyle=\color{red}\itshape,
+ stringstyle=\color{darkgreen},
+ showstringspaces=false,
+ keywordstyle=\color{blue}\bfseries}
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% Macros
+\setbeamercolor{emphbar}{bg=blue!20, fg=black}
+\newcommand{\emphbar}[1]
+{\begin{beamercolorbox}[rounded=true]{emphbar}
+ {#1}
+ \end{beamercolorbox}
+}
+\newcounter{time}
+\setcounter{time}{0}
+\newcommand{\inctime}[1]{\addtocounter{time}{#1}{\tiny \thetime\ m}}
+
+\newcommand{\typ}[1]{\textbf{\texttt{{#1}}}}
+
+
+\newcommand{\kwrd}[1]{ \texttt{\textbf{\color{blue}{#1}}} }
+
+%%% This is from Fernando's setup.
+% \usepackage{color}
+% \definecolor{orange}{cmyk}{0,0.4,0.8,0.2}
+% % Use and configure listings package for nicely formatted code
+% \usepackage{listings}
+% \lstset{
+% language=Python,
+% basicstyle=\small\ttfamily,
+% commentstyle=\ttfamily\color{blue},
+% stringstyle=\ttfamily\color{orange},
+% showstringspaces=false,
+% breaklines=true,
+% postbreak = \space\dots
+% }
+
+%\pgfdeclareimage[height=0.75cm]{iitmlogo}{iitmlogo}
+%\logo{\pgfuseimage{iitmlogo}}
+
+
+%% Delete this, if you do not want the table of contents to pop up at
+%% the beginning of each subsection:
+\AtBeginSubsection[]
+{
+ \begin{frame}<beamer>
+ \frametitle{Outline}
+ \tableofcontents[currentsection,currentsubsection]
+ \end{frame}
+}
+
+\AtBeginSection[]
+{
+ \begin{frame}<beamer>
+ \frametitle{Outline}
+ \tableofcontents[currentsection,currentsubsection]
+ \end{frame}
+}
+
+% If you wish to uncover everything in a step-wise fashion, uncomment
+% the following command:
+%\beamerdefaultoverlayspecification{<+->}
+
+%\includeonlyframes{current,current1,current2,current3,current4,current5,current6}
diff --git a/testing/pytest.tex b/testing/pytest.tex
new file mode 100644
index 0000000..b0d5df2
--- /dev/null
+++ b/testing/pytest.tex
@@ -0,0 +1,669 @@
+
+\documentclass[14pt,compress]{beamer}
+
+
+\input{macros.tex}
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% Title page
+\title[Testing]{Unit Testing in Python}
+
+\author[FOSSEE] {Prabhu Ramachandran}
+
+\institute[FOSSEE -- IITB] {Department of Aerospace Engineering\\IIT Bombay}
+\date[] {Mumbai, India}
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% DOCUMENT STARTS
+\begin{document}
+
+\begin{frame}
+ \titlepage
+\end{frame}
+
+\begin{frame}
+ \frametitle{Outline}
+ \tableofcontents
+ % You might wish to add the option [pausesections]
+\end{frame}
+
+\section{Introduction}
+
+\begin{frame}
+ \frametitle{Objectives}
+ At the end of this session, you will be able to:
+ \begin{itemize}
+ \item Write your code using the TDD paradigm
+ \item Use the \typ{pytest} module to test your code
+ \item Run your tests on \url{travis-ci.org}
+ \item Look at test coverage of your code
+ \item Look at automatic coverage with \url{codecov.io}
+ \end{itemize}
+\end{frame}
+
+\begin{frame}
+ \frametitle{Why? What?}
+ \begin{itemize}
+ \item Do you think we make bridges without testing them?
+ \pause
+ \vspace*{1in}
+ \item Why should software be any different?
+ \end{itemize}
+\end{frame}
+
+\begin{frame}
+ \frametitle{So what?}
+ \begin{itemize}
+ \item Easy to develop code
+ \item Easy to refactor/improve code
+ \item Confidence in code base
+ \item Make you feel warm all over!
+ \end{itemize}
+\end{frame}
+
+
+\begin{frame}[plain]
+ \frametitle{What is TDD?}
+ \small
+
+ The basic steps of TDD are roughly as follows --
+ \begin{enumerate}
+ \item<1-> Decide upon feature to implement and methodology of
+ testing
+ \item<2-> Write tests for feature decided upon
+ \item<3-> Just write enough code, so that the test can be run, but it fails.
+ \item<4-> Improve the code, to just pass the test and at the same time
+ passing all previous tests
+ \item<5-> Run the tests to see, that all of them run successfully
+ \item<6-> Refactor the code you've just written -- optimize the algorithm,
+ remove duplication, add documentation, etc.
+ \item<7-> Run the tests again, to see that all the tests still pass
+ \item<8-> Go back to 1
+ \end{enumerate}
+\end{frame}
+
+\section{Let us TDD}
+
+\begin{frame}[fragile]
+ \frametitle{First Test -- GCD}
+ \begin{itemize}
+ \item simple program -- GCD of two numbers
+ \item What are our code units?
+ \begin{itemize}
+ \item Only one function \texttt{gcd}
+ \item Takes two numbers as arguments
+ \item Returns one number, which is their GCD
+ \end{itemize}
+\begin{lstlisting}
+c = gcd(44, 23)
+\end{lstlisting}
+ \item c will contain the GCD of the two numbers.
+ \end{itemize}
+\end{frame}
+
+\begin{frame}[fragile]
+ \frametitle{Test Cases}
+ \begin{itemize}
+ \item Important to have test cases and expected outputs even before
+ writing the first test!
+ \item $a=48$, $b=48$, $GCD=48$
+ \item $a=44$, $b=19$, $GCD=1$
+ \item Tests are just a series of assertions
+ \item True or False, depending on expected and actual behavior
+ \end{itemize}
+
+\end{frame}
+
+\begin{frame}[fragile]
+ \frametitle{Test Cases -- general idea}
+ \small
+\begin{lstlisting}
+tc1 = gcd(48, 64)
+if tc1 != 16:
+ print "Failed for a=48, b=64. Expected 16. \
+ Obtained %d instead." % tc1
+ exit(1)
+
+tc2 = gcd(44, 19)
+if tc2 != 1:
+ print "Failed for a=44, b=19. Expected 1. \
+ Obtained %d instead." % tc2
+ exit(1)
+
+print "All tests passed!"
+\end{lstlisting}
+\begin{itemize}
+\item The function \texttt{gcd} doesn't even exist!
+\end{itemize}
+\end{frame}
+
+\begin{frame}[fragile]
+ \frametitle{Test Cases -- code}
+ \begin{itemize}
+ \item Let us make it a function!
+ \item Use assert!
+ \end{itemize}
+\end{frame}
+
+\begin{frame}[fragile]
+ \frametitle{Test Cases -- code}
+\begin{lstlisting}
+# gcd.py
+def test_gcd():
+ assert gcd(48, 64) == 16
+ assert gcd(44, 19) == 1
+
+test_gcd()
+\end{lstlisting}
+\end{frame}
+
+\begin{frame}[fragile]
+ \frametitle{Stubs}
+ \begin{itemize}
+ \item First write a very minimal definition of \texttt{gcd}
+ \begin{lstlisting}
+def gcd(a, b):
+ pass
+ \end{lstlisting}
+ \item Written just, so that the tests can run
+ \item Obviously, the tests are going to fail
+ \end{itemize}
+\end{frame}
+
+\begin{frame}[fragile]
+ \frametitle{\texttt{gcd.py}}
+\begin{lstlisting}
+def gcd(a, b):
+ pass
+
+def test_gcd():
+ assert gcd(48, 64) == 16
+ assert gcd(44, 19) == 1
+
+if __name__ == '__main__':
+ test_gcd()
+\end{lstlisting}
+\end{frame}
+
+\begin{frame}[fragile]
+ \frametitle{First run}
+\begin{lstlisting}
+$ python gcd.py
+Traceback (most recent call last):
+ File "gcd.py", line 9, in <module>
+ test_gcd()
+ File "gcd.py", line 5, in test_gcd
+ assert gcd(48, 64) == 16
+AssertionError
+\end{lstlisting} %$
+
+ \begin{itemize}
+ \item We have our code unit stub, and a failing test.
+ \item The next step is to write code, so that the test just passes.
+ \end{itemize}
+\end{frame}
+
+\begin{frame}[fragile,plain]
+ \frametitle{Euclidean Algorithm}
+ \begin{itemize}
+ \item Modify the \texttt{gcd} stub function
+ \item Then, run the script to see if the tests pass.
+ \end{itemize}
+\begin{lstlisting}
+def gcd(a, b):
+ if a == 0:
+ return b
+ while b != 0:
+ if a > b:
+ a = a - b
+ else:
+ b = b - a
+ return a
+\end{lstlisting}
+\begin{lstlisting}
+$ python gcd.py
+All tests passed!
+\end{lstlisting} %$
+ \begin{itemize}
+ \item \alert{Success!}
+ \end{itemize}
+\end{frame}
+
+
+\begin{frame}[fragile]
+ \frametitle{Euclidean Algorithm -- Modulo}
+ \begin{itemize}
+ \item Repeated subtraction can be replaced by a modulo
+ \item modulo of \texttt{a\%b} is always less than b
+ \item when \texttt{a < b}, \texttt{a\%b} equals \texttt{a}
+ \item Combine these two observations, and modify the code
+\begin{lstlisting}
+def gcd(a, b):
+ while b != 0:
+ a, b = b, a % b
+ return a
+\end{lstlisting}
+ \item Check that the tests pass again
+ \end{itemize}
+\end{frame}
+
+\begin{frame}[fragile]
+ \frametitle{Euclidean Algorithm -- Recursive}
+ \begin{itemize}
+ \item Final improvement -- make \texttt{gcd} recursive
+ \item More readable and easier to understand
+\begin{lstlisting}
+def gcd(a, b):
+ if b == 0:
+ return a
+ return gcd(b, a%b)
+\end{lstlisting}
+ \item Check that the tests pass again
+ \end{itemize}
+\end{frame}
+
+\begin{frame}[fragile]
+ \frametitle{Document \texttt{gcd}}
+ \begin{itemize}
+ \item Undocumented function is as good as unusable
+ \item Let's add a docstring \& We have our first test!
+ \end{itemize}
+\end{frame}
+\begin{frame}[fragile]
+ \frametitle{Document \texttt{gcd}}
+\small
+\begin{lstlisting}
+def gcd(a, b):
+ """Returns the Greatest Common Divisor of the
+ two integers passed as arguments.
+
+ Args:
+ a: an integer
+ b: another integer
+
+ Returns: Greatest Common Divisor of a and b
+ """
+ if b == 0:
+ return a
+ return gcd(b, a%b)
+\end{lstlisting}
+\end{frame}
+
+
+\begin{frame}[fragile]
+ \frametitle{Persistent Test Cases}
+ \begin{itemize}
+ \item Tests should be pre-determined and written, before the code
+ \item The file shall have multiple lines of test code/data
+ \item Separates the code from the tests
+ \end{itemize}
+\end{frame}
+
+\begin{frame}[fragile]
+ \frametitle{Separate \texttt{test\_gcd.py}}
+\begin{lstlisting}
+from gcd import gcd
+
+def test_gcd():
+ assert gcd(48, 64) == 16
+ assert gcd(44, 19) == 1
+
+if __name__ == '__main__':
+ test_gcd()
+\end{lstlisting}
+\end{frame}
+
+\section{Using \texttt{pytest}}
+
+\begin{frame}
+ \frametitle{\texttt{pytest}}
+ \begin{itemize}
+ \item \url{doc.pytest.org}
+ \item Is a powerful test runner!
+ \item Detailed info from simple assert statements
+ \item Automatic discovery of tests
+ \item Modular fixures
+ \end{itemize}
+\end{frame}
+
+\subsection{Basics}
+
+\begin{frame}[fragile]
+ \frametitle{Lets start using it}
+ \begin{lstlisting}
+ $ pytest
+ \end{lstlisting}%$
+ Note that it automatically tested \typ{test\_gcd.py}\\
+
+ Now try:
+ \begin{lstlisting}
+ $ pytest -h
+ \end{lstlisting}%$
+\end{frame}
+
+\begin{frame}[fragile]
+ \frametitle{Lets start using it}
+ Let us make a mistake in \typ{gcd.py}
+ \begin{lstlisting}
+ $ pytest
+ \end{lstlisting}%$
+ Note that it produces far nicer output
+\end{frame}
+
+\begin{frame}[fragile]
+ \frametitle{Some basic features}
+\small
+ \begin{lstlisting}
+import pytest
+
+def test_casting_raises_value_error():
+ with pytest.raises(ValueError):
+ int('asd')
+
+def test_floating_point():
+ assert 0.1 + 0.2 == pytest.approx(0.3)
+ \end{lstlisting}
+\end{frame}
+
+\begin{frame}[fragile]
+ \frametitle{Basic features}
+\small
+ \begin{lstlisting}
+class TestSomething(object):
+ def test_something_has_hello(self):
+ x = 'hello'
+ y = 'hello world'
+ assert x in y
+
+ def test_something_else(self):
+ a = [1,2,3]
+ b = {1, 2, 3}
+ assert a == b
+\end{lstlisting}
+\end{frame}
+
+\begin{frame}
+ \frametitle{Another example as exercise}
+ \begin{block}{Problem}
+ Find all git repositories recursively inside a root directory
+\end{block}
+\pause
+\begin{itemize}
+\item Simplify the problem
+\item Make it testable
+\end{itemize}
+\end{frame}
+
+\begin{frame}
+ \frametitle{Some rules for writing tests}
+ \begin{itemize}
+ \item The test should be runnable by anyone (even by a computer), almost anywhere.
+ \item Don't write anything in the current directory (use a temporary directory).
+ \item Cleanup any files you create while testing.
+ \item Make sure tests do not affect global state.
+ \end{itemize}
+\end{frame}
+
+\begin{frame}
+ \frametitle{How do we do this?}
+ \begin{itemize}
+ \item Create some test data in a temporary directory
+ \item Test!
+ \item Cleanup the test data
+ \end{itemize}
+\end{frame}
+
+
+\subsection{Fixtures}
+
+\begin{frame}
+ \frametitle{Fixtures}
+ \begin{itemize}
+ \item What if you want to do something common before all tests?
+ \item Typically called a \textbf{fixture}
+ \end{itemize}
+\end{frame}
+
+\begin{frame}[fragile]
+ \frametitle{pytest fixtures}
+ \begin{lstlisting}
+import pytest
+@pytest.fixture
+def my_fixture():
+ print('Hello there')
+
+def test_foo(my_fixture):
+ print('runing test')
+
+ \end{lstlisting}
+\end{frame}
+
+\begin{frame}[fragile]
+ \frametitle{pytest fixture cleanup}
+ \begin{lstlisting}
+import pytest
+@pytest.fixture
+def fix1():
+ print('Hello there')
+ yield
+ print('Good bye')
+
+def test_foo(fix1):
+ print('runing test')
+
+ \end{lstlisting}
+\end{frame}
+
+\begin{frame}[fragile]
+ \frametitle{pytest fixture with data}
+ \begin{lstlisting}
+@pytest.fixture
+def some_data(my_fixture):
+ print('some_data fixture')
+ yield {'a': 1, 'b': 2}
+ # Cleanup
+ print('Good bye: fix2')
+
+
+def test_some_data(some_data):
+ print(some_data)
+ print('Running test_fix2')
+ \end{lstlisting}
+\end{frame}
+
+
+
+\begin{frame}[fragile]
+ \frametitle{pytest fixtures can use other fixtures}
+ \begin{lstlisting}
+@pytest.fixture
+def fix2(my_fixture):
+ yield
+ # Cleanup
+ print('Good bye')
+
+
+def test_fix2(fix2):
+ print('Running test_fix2')
+ \end{lstlisting}
+\end{frame}
+
+\begin{frame}[fragile]
+ \frametitle{Another way to use fixtures}
+ \begin{lstlisting}
+@pytest.mark.usefixtures('fix1', 'fix2')
+class TestClass:
+ def test_method1(self):
+ # ...
+ \end{lstlisting}
+\end{frame}
+
+\begin{frame}
+ \frametitle{Fixture scope}
+ \begin{itemize}
+ \item \typ{@pytest.fixture(scope='module')}
+ \item Scope can be
+ \begin{itemize}
+ \item \typ{'function'}: the default, executes for each function
+ \item \typ{'module'}
+ \item \typ{'class'}
+ \item \typ{'session'}: run only once!
+ \end{itemize}
+ \end{itemize}
+\end{frame}
+
+\subsection{Writing nicer tests}
+
+\begin{frame}
+ \frametitle{Some rules for writing tests}
+ \begin{itemize}
+ \item The test should be runnable by anyone (even by a computer), almost anywhere.
+ \item Don't write anything in the current directory (use a temporary directory).
+ \item Cleanup any files you create while testing.
+ \item Make sure tests do not affect global state.
+ \end{itemize}
+\end{frame}
+
+\begin{frame}
+ \frametitle{Writing nicer tests}
+ \begin{itemize}
+ \item Names of test functions
+ \item Use descriptive function names
+ \item Intent matters
+ \item Segregate the test code into the following
+
+ \begin{itemize}
+ \item Given: what is the context of the test?
+ \item When: what action is taken to actually test the problem
+ \item Then: what do we actually ensure.
+ \end{itemize}
+
+ \item \url{dannorth.net/introducing-bdd/}
+ \item \url{martinfowler.com/bliki/GivenWhenThen.html}
+ \end{itemize}
+\end{frame}
+
+\begin{frame}
+ \frametitle{Improve previous exercise}
+ \begin{block}{Problem}
+ Find all git repositories recursively inside a root directory
+\end{block}
+\begin{itemize}
+ \item Use all ideas discussed previously
+\end{itemize}
+
+\end{frame}
+
+
+\section{Travis-CI}
+
+\begin{frame}
+ \frametitle{Continuous integration}
+ \begin{itemize}
+ \item Run tests automatically
+ \item On every push
+ \item On every branch
+ \item On every pull-request
+ \item Free for public repos usually
+ \end{itemize}
+\end{frame}
+
+\begin{frame}
+ \frametitle{Continuous integration: services}
+ \begin{itemize}
+ \item \url{travis-ci.org}
+ \item \url{appveyor.com}
+ \item \url{shippable.com}
+ \item \url{codeship.com}
+ \item Work with github, bitbucket and gitlab
+ \end{itemize}
+\end{frame}
+
+\begin{frame}
+ \frametitle{Let us get started}
+ \begin{enumerate}
+ \item Commit code to local repo
+ \item Push to public github repo (say pytest-tutorial)
+ \item Visit \url{travis-ci.org}
+ \item Connect Travis to Github
+ \end{enumerate}
+\end{frame}
+
+\begin{frame}[fragile]
+ \frametitle{\texttt{.travis.yml}}
+ Create a \typ{.travis.yml} in your repo
+ \begin{lstlisting}
+language: python
+
+python:
+ - 2.7
+
+install:
+ - pip install -U pytest
+
+script:
+ - pytest
+\end{lstlisting}
+\end{frame}
+
+\begin{frame}
+ \frametitle{Starting travis}
+ \begin{itemize}
+ \item Push the \typ{.travis.yml}
+ \item Thats it!
+ \item Can add other versions of Python etc.
+ \item Useful to validate YAML: \url{www.yamllint.com/}
+ \end{itemize}
+\end{frame}
+
+
+\section{Coverage}
+
+\begin{frame}
+ \frametitle{Coverage of tests}
+ \begin{itemize}
+ \item Assess the amount of code that is covered by testing
+ \item \url{coverage.readthedocs.io}
+ \item \typ{pip install coverage}
+ \end{itemize}
+\end{frame}
+
+\begin{frame}[fragile]
+ \frametitle{Using coverage}
+ \begin{lstlisting}
+ $ coverage run -m pytest my_package
+ $ coverage report
+ $ coverage html
+ \end{lstlisting}%$
+\end{frame}
+
+\begin{frame}[fragile]
+ \frametitle{Narrowing the source to look at}
+ \begin{lstlisting}
+$ coverage run --source . \
+ -m pytest my_package
+$ coverage report
+$ coverage html
+ \end{lstlisting}%$
+\end{frame}
+
+\begin{frame}[fragile]
+ \frametitle{Using a \texttt{.coveragerc}}
+ Create a \typ{.coveragerc}
+ \begin{lstlisting}
+ [run]
+ source = .
+ branch = True
+ omit = */tests/*
+ \end{lstlisting}
+\end{frame}
+
+
+\section{Codecov}
+
+
+\section{Some advanced features}
+
+
+\end{document}