\documentclass[12pt,presentation]{beamer} \usepackage[utf8]{inputenc} \usepackage[T1]{fontenc} \usepackage{fixltx2e} \usepackage{graphicx} \usepackage{longtable} \usepackage{float} \usepackage{wrapfig} \usepackage{soul} \usepackage{textcomp} \usepackage{marvosym} \usepackage{wasysym} \usepackage{latexsym} \usepackage{amssymb} \usepackage{hyperref} \tolerance=1000 \usepackage[english]{babel} \usepackage{ae,aecompl} \usepackage{mathpazo,courier,euler} \usepackage[scaled=.95]{helvet} \usepackage{listings} \lstset{language=Python, basicstyle=\ttfamily\bfseries, commentstyle=\color{red}\itshape, stringstyle=\color{green}, showstringspaces=false, keywordstyle=\color{blue}\bfseries} \providecommand{\alert}[1]{\textbf{#1}} \title{SEES: Test Driven Development} \author{FOSSEE} \usetheme{Warsaw}\usecolortheme{default}\useoutertheme{infolines}\setbeamercovered{transparent} \AtBeginSection[] { \begin{frame} \frametitle{Outline} \tableofcontents[currentsection] \end{frame} } \begin{document} \maketitle \begin{frame} \frametitle{Outline} \setcounter{tocdepth}{3} \tableofcontents \end{frame} \section{Introduction} \begin{frame} \frametitle{Objectives} At the end of this section, you will be able to: \begin{itemize} \item Write your code using the TDD paradigm. \item Use the nose module to test your code. \end{itemize} \end{frame} \begin{frame} \frametitle{What is TDD?} 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{First Test} \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} \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 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] \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} \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 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{Python Testing Frameworks} \begin{frame}[fragile] \frametitle{Python Testing Frameworks} \begin{itemize} \item Testing frameworks essentially, ease the job of the user \item Python provides two frameworks for testing code \begin{itemize} \item \texttt{unittest} framework \item \texttt{doctest} module \end{itemize} \item \texttt{nose} is a package to help test \end{itemize} \end{frame} \subsection{\texttt{nose}} \begin{frame}[fragile] \frametitle{\texttt{nose} tests} \begin{itemize} \item It is not easy to organize, choose and run tests scattered across multiple files. \item \texttt{nose} module aggregate these tests automatically \item Can aggregate \texttt{unittests} and \texttt{doctests} \item Allows us to pick and choose which tests to run \item Helps output the test-results and aggregate them in various formats \item Not part of the Python distribution itself \begin{lstlisting} $ apt-get install python-nose \end{lstlisting} %$ \item Run the following command in the top level directory \begin{lstlisting} $ nosetests \end{lstlisting} %$ \end{itemize} \end{frame} \end{document}