diff options
author | Prabhu Ramachandran | 2017-02-22 14:56:35 +0530 |
---|---|---|
committer | Prabhu Ramachandran | 2017-02-22 14:56:35 +0530 |
commit | d5c20e445b193fad5cb4339f00e11212fdd39c0a (patch) | |
tree | 94425bf5693fd7797fe128678614b4de364b0df9 | |
parent | ba725c5af9ac1c7064211589ff561fb79f3853b4 (diff) | |
download | python-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.md | 53 | ||||
-rw-r--r-- | testing/code/gcd.py | 9 | ||||
-rw-r--r-- | testing/code/test_class.py | 11 | ||||
-rw-r--r-- | testing/code/test_fixtures.py | 51 | ||||
-rw-r--r-- | testing/code/test_gcd.py | 21 | ||||
-rw-r--r-- | testing/code/test_list_gitrepo.py | 46 | ||||
-rw-r--r-- | testing/macros.tex | 88 | ||||
-rw-r--r-- | testing/pytest.tex | 669 |
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} |