summaryrefslogtreecommitdiff
path: root/TDD
diff options
context:
space:
mode:
Diffstat (limited to 'TDD')
-rw-r--r--TDD/images/fossee-logo.pngbin0 -> 13587 bytes
-rw-r--r--TDD/images/iitb-logo.pngbin0 -> 13756 bytes
-rw-r--r--TDD/math_utils/fibonacci.py7
-rw-r--r--TDD/math_utils/fibonacci_testcases.dat8
-rw-r--r--TDD/math_utils/test_fibonacci.py27
-rwxr-xr-xTDD/two_column.py84
-rw-r--r--TDD/using_python_framework_for_tdd/tdd2.tex (renamed from TDD/using_python_frameworks_for_TDD/slide.tex)232
-rwxr-xr-xTDD/using_python_framework_for_tdd/tdd2_script.rst198
8 files changed, 473 insertions, 83 deletions
diff --git a/TDD/images/fossee-logo.png b/TDD/images/fossee-logo.png
new file mode 100644
index 0000000..49d1797
--- /dev/null
+++ b/TDD/images/fossee-logo.png
Binary files differ
diff --git a/TDD/images/iitb-logo.png b/TDD/images/iitb-logo.png
new file mode 100644
index 0000000..38ec17e
--- /dev/null
+++ b/TDD/images/iitb-logo.png
Binary files differ
diff --git a/TDD/math_utils/fibonacci.py b/TDD/math_utils/fibonacci.py
new file mode 100644
index 0000000..0f454d6
--- /dev/null
+++ b/TDD/math_utils/fibonacci.py
@@ -0,0 +1,7 @@
+def fibonacci(n):
+ if n == 0:
+ return 0
+ elif n == 1:
+ return 1
+ else:
+ return fibonacci(n-1) + fibonacci(n-2)
diff --git a/TDD/math_utils/fibonacci_testcases.dat b/TDD/math_utils/fibonacci_testcases.dat
new file mode 100644
index 0000000..c5dae1a
--- /dev/null
+++ b/TDD/math_utils/fibonacci_testcases.dat
@@ -0,0 +1,8 @@
+0, 0
+1, 1
+2, 1
+3, 2
+4, 3
+5, 5
+6, 8
+7, 13
diff --git a/TDD/math_utils/test_fibonacci.py b/TDD/math_utils/test_fibonacci.py
new file mode 100644
index 0000000..ee3393f
--- /dev/null
+++ b/TDD/math_utils/test_fibonacci.py
@@ -0,0 +1,27 @@
+import fibonacci
+import unittest
+
+class TestFibonacciFunction(unittest.TestCase):
+
+ def setUp(self):
+ self.test_file = open('fibonacci_testcases.dat')
+ self.test_cases = []
+ for line in self.test_file:
+ values = line.split(', ')
+ n = int(values[0])
+ a = int(values[1])
+
+ self.test_cases.append([n, a])
+
+ def test_fibonacci(self):
+ for case in self.test_cases:
+ n = case[0]
+ a = case[1]
+ self.assertEqual(fibonacci.fibonacci(n),a)
+
+ def tearDown(self):
+ self.test_file.close()
+ del self.test_cases
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/TDD/two_column.py b/TDD/two_column.py
new file mode 100755
index 0000000..4134b2e
--- /dev/null
+++ b/TDD/two_column.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python
+# This script generates two column format from the script file.
+import re, optparse
+
+num = 0
+content = {}
+
+def parse_script(script):
+ """Parse the file, and get each cell's content into a dictionary."""
+ where = 'HEAD'
+ txt = ''
+ global num
+ for line in open(script):
+ if line.startswith('..'):
+ loc = re.findall(".. ((L|R)(\d+))", line)
+ if loc:
+ # Next cell has been found
+ # Save previous cell data
+ content[where] = txt
+
+ txt = ''
+ where = loc[0][0]
+ num = int(loc[0][2]) if int(loc[0][2]) > num else num
+ continue
+ else:
+ pass
+ txt += line
+ content[where] = txt # Saving the content of the last cell.
+
+def write_two_col(content, two_col):
+ """ Write the content to a file, in two column format."""
+ f = open(two_col, 'w')
+ f.write('%s' %content['HEAD'])
+
+ f.write("\n\n+%s+%s+\n" %('-'*82, '-'*82))
+ for i in range(1, num+1):
+ l = '%s%s' %('L', i)
+ r = '%s%s' %('R', i)
+
+ # Split each side text into individual lines
+ if l in content:
+ ltext = content[l].strip().splitlines()
+ else:
+ ltext = ['']
+ if r in content:
+ rtext = content[r].strip().splitlines()
+ else:
+ rtext = ['']
+
+ # Ensure that both sides have the same number of lines
+ ltext.extend(['']*(len(rtext) - len(ltext)))
+ rtext.extend(['']*(len(ltext) - len(rtext)))
+
+ # Write each of the lines in respective columns
+ for k in range(len(ltext)):
+ f.write("| %-80s | %-80s |\n" %(ltext[k], rtext[k]))
+
+ # Horizontal division
+ f.write("+%s+%s+\n" %('-'*82, '-'*82))
+
+ f.close()
+
+if __name__ == '__main__':
+ parser = optparse.OptionParser()
+ parser.add_option("-i", default="script.rst", dest="input",
+ help="Input file. 'script.rst' is used by default.")
+ parser.add_option("-o", default="script2col.rst", dest="output",
+ help="Output file. script2col.rst is used by default.")
+
+ parser.description = """Converts a script file into two column format."""
+
+ parser.epilog = \
+ """Make sure that you check the validity of the formatting of both
+the INPUT and the OUTPUT files, using rst2html. Also, Make sure that
+each line, in the INPUT file, is less than 80 chars long."""
+
+ (options, args) = parser.parse_args()
+
+ script, two_col = options.input, options.output
+
+ parse_script(script)
+ write_two_col(content, two_col)
+
+ print "Converted %s to 2 column format and saved as %s" %(script, two_col)
diff --git a/TDD/using_python_frameworks_for_TDD/slide.tex b/TDD/using_python_framework_for_tdd/tdd2.tex
index a6308c4..561f4a2 100644
--- a/TDD/using_python_frameworks_for_TDD/slide.tex
+++ b/TDD/using_python_framework_for_tdd/tdd2.tex
@@ -22,61 +22,98 @@ commentstyle=\color{red}\itshape, stringstyle=\color{green},
showstringspaces=false, keywordstyle=\color{blue}\bfseries}
\providecommand{\alert}[1]{\textbf{#1}}
-\title{Getting started with TDD}
+\title{SEES: Test Driven Development}
\author{FOSSEE}
-\institute{IIT Bombay}
\usetheme{Warsaw}\usecolortheme{default}\useoutertheme{infolines}\setbeamercovered{transparent}
-\AtBeginSection[]
-{
- \begin{frame}<beamer>
- \frametitle{Outline}
- \tableofcontents[currentsection]
- \end{frame}
-}
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
\begin{document}
-\begin{frame}
+\begin{frame}
\begin{center}
\vspace{12pt}
-\textcolor{blue}{\huge Using Python Testing Frameworks}
+\textcolor{blue}{\huge Test Driven Development \\Part II}
\end{center}
\vspace{18pt}
\begin{center}
\vspace{10pt}
-\includegraphics[scale=0.95]{../../images/fossee-logo.png}\\
+\includegraphics[scale=0.95]{../images/fossee-logo.png}\\
\vspace{5pt}
\scriptsize Developed by FOSSEE Team, IIT-Bombay. \\
\scriptsize Funded by National Mission on Education through ICT\\
\scriptsize MHRD,Govt. of India\\
-\includegraphics[scale=0.30]{../../images/iitb-logo.png}\\
+\includegraphics[scale=0.30]{../images/iitb-logo.png}\\
\end{center}
\end{frame}
-
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\section{Introduction}
\begin{frame}
\frametitle{Objectives}
- At the end of this section, you will be able to:
+ At the end of this tutorial, you will be able to,
\begin{itemize}
- \item Use doctests to test your Python code.
- \item Use unittests to test your Python code.
- \item Use the nose module to test your code.
+ \item Know what are persistent test cases.
+ \item Write doctest \& unittest for any given function.
+ \item Understand the use of nosetest.
+
\end{itemize}
-\end{frame}
+ \end{frame}
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\begin{frame}
\frametitle{Pre-requisite}
\label{sec-3}
-Spoken tutorial on Basic Python
+Spoken tutorial on -
\begin{itemize}
-\item Topic of tutorial here
+\item Test Driven Development -- Part I
\end{itemize}
\end{frame}
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
+\begin{frame}[fragile]
+ \frametitle{Persistent Test Cases}
+ \begin{itemize}
+ \item Tests should be pre-determined and written, before the code
+ \item Test Data is repeatedly used; Hence, saved in persistent
+ format
+ \item Let's save data for fibonacci tests in a text file.
+ \item The file shall have multiple lines of test data
+ \item Each line corresponds to a single test case
+ \item Each line consists of two comma separated values --
+ \begin{itemize}
+ \item First coloumn is the integer which has to be
+ passed to the function.
+ \item Second coloumn is the return value from the function.
+ \end{itemize}
+ \item Let us call our data file \texttt{fibonacci\_testcases.dat}
+ \end{itemize}
+\end{frame}
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\begin{frame}[fragile]
+ \frametitle{Modify \texttt{fibonacci.py}}
+\begin{lstlisting}
+if __name__ == '__main__':
+ for line in open('fibonacci_testcases.dat'):
+ values = line.split(', ')
+ n = int(values[0])
+ a = int(values[1])
+
+ tc = fibonacci(n)
+ if tc != a:
+ print "Failed for n=%d.\
+ Expected %d. Obtained %d instead."\
+ % (n, a, tc)
+ exit(1)
+
+ print "All tests passed!"
+\end{lstlisting}
+\end{frame}
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{Python Testing Frameworks}
\begin{frame}[fragile]
@@ -90,7 +127,7 @@ Spoken tutorial on Basic Python
\end{itemize}
\end{itemize}
\end{frame}
-
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\subsection{\texttt{doctest} module}
\begin{frame}[fragile]
@@ -104,32 +141,33 @@ Spoken tutorial on Basic Python
\item \texttt{doctest} module picks up all such interactive examples
\item Executes them and determines if the code runs, as documented
\end{itemize}
- Let's use the \texttt{doctest} module for our \texttt{gcd} function
+ Let's use the \texttt{doctest} module for our \texttt{fibonacci} function
\end{frame}
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\begin{frame}[fragile]
- \frametitle{doctest for \texttt{gcd.py}}
+ \frametitle{doctest for \texttt{fibonacci.py}}
\begin{tiny}
\begin{lstlisting}
-def gcd(a, b):
- """Returns the Greatest Common Divisor of the two integers
- passed as arguments.
+def fibonacci(n):
+ """Returns the nth fibonacci number.
Args:
- a: an integer
- b: another integer
-
- Returns: Greatest Common Divisor of a and b
-
- >>> gcd(48, 64)
- 16
- >>> gcd(44, 19)
- 1
+ n: an integer
+
+
+ >>> fibonacci(3)
+ 2
+ >>> fibonacci(4)
+ 3
"""
- if b == 0:
- return a
- return gcd(b, a%b)
+ if n == 0:
+ return 0
+ elif n == 1:
+ return 1
+ else:
+ return fibonacci(n-1) + fibonacci(n-2)
\end{lstlisting}
\end{tiny}
\begin{itemize}
@@ -137,9 +175,9 @@ def gcd(a, b):
\item Now we need to tell the \texttt{doctest} module to execute
\end{itemize}
\end{frame}
-
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\begin{frame}[fragile]
- \frametitle{doctest for \texttt{gcd.py} \ldots}
+ \frametitle{doctest for \texttt{fibonacci.py} \ldots}
\begin{lstlisting}
if __name__ == "__main__":
import doctest
@@ -152,18 +190,18 @@ if __name__ == "__main__":
\item Complains only when one or more tests fail.
\end{itemize}
\end{frame}
-
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\begin{frame}[fragile]
\frametitle{\texttt{doctest} -- Execution}
\begin{itemize}
- \item Run the doctests by running \texttt{gcd.py}
+ \item Run the doctests by running \texttt{fibonacci.py}
\begin{lstlisting}
-$ python gcd.py
+$ python fibonacci.py
\end{lstlisting} %$
\item All the tests pass, and doctest gives no output
\item For a more detailed report we can run with -v argument
\begin{lstlisting}
-$ python gcd.py -v
+$ python fibonacci.py -v
\end{lstlisting} %$
\item If the output contains a blank line, use \texttt{<BLANKLINE>}
\item To see a failing test case, replace \texttt{return a} with \texttt{b}
@@ -171,7 +209,7 @@ $ python gcd.py -v
\end{frame}
\subsection{\texttt{unittest} framework}
-
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\begin{frame}[fragile]
\frametitle{\texttt{unittest}}
\begin{itemize}
@@ -184,57 +222,43 @@ $ python gcd.py -v
\item Easily aggregate tests into collections and improved reporting
\end{itemize}
\end{frame}
-
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\begin{frame}[fragile]
- \frametitle{\texttt{unittest}}
- \begin{itemize}
- \item It won't be long, before we complain about the power of
- \texttt{doctest}
- \item \texttt{unittest} framework can efficiently automate tests
- \item Easily initialize code and data for executing the specific
- tests
- \item Cleanly shut them down once the tests are executed
- \item Easily aggregate tests into collections and improved reporting
- \end{itemize}
-\end{frame}
-
-\begin{frame}[fragile]
- \frametitle{\texttt{unittest}ing \texttt{gcd.py}}
+ \frametitle{\texttt{unittest}ing \texttt{fibonacci.py}}
\begin{itemize}
\item Subclass the \texttt{TestCase} class in \texttt{unittest}
\item Place all the test code as methods of this class
- \item Use the test cases present in \texttt{gcd\_testcases.dat}
- \item Place the code in \texttt{test\_gcd.py}
+ \item Use the test cases present in \texttt{fibonacci\_testcases.dat}
+ \item Place the code in \texttt{test\_fibonacci.py}
\end{itemize}
\end{frame}
-
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\begin{frame}[fragile,allowframebreaks]
- \frametitle{\texttt{test\_gcd.py}}
+ \frametitle{\texttt{test\_fibonacci.py}}
\small
\begin{lstlisting}
-import gcd
+import fibonacci
import unittest
-class TestGcdFunction(unittest.TestCase):
+class TestFibonacciFunction(unittest.TestCase):
def setUp(self):
- self.test_file = open('gcd_testcases.dat')
+ self.test_file = \
+ open('fibonacci_testcases.dat')
self.test_cases = []
for line in self.test_file:
values = line.split(', ')
- a = int(values[0])
- b = int(values[1])
- g = int(values[2])
-
- self.test_cases.append([a, b, g])
+ n = int(values[0])
+ a = int(values[1])
+
+ self.test_cases.append([n, a])
- def test_gcd(self):
+ def test_fibonacci(self):
for case in self.test_cases:
- a = case[0]
- b = case[1]
- g = case[2]
- self.assertEqual(gcd.gcd(a, b), g)
+ n = case[0]
+ a = case[1]
+ self.assertEqual(fibonacci.fibonacci(n),a)
def tearDown(self):
self.test_file.close()
@@ -244,22 +268,22 @@ if __name__ == '__main__':
unittest.main()
\end{lstlisting}
\end{frame}
-
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\begin{frame}[fragile]
- \frametitle{\texttt{test\_gcd.py}}
+ \frametitle{\texttt{test\_fibonacci.py}}
\begin{itemize}
\item \texttt{setUp} -- we read all the test data and store it in a
list
\item \texttt{tearDown} -- delete the data to free up memory and
close open file
- \item \texttt{test\_gcd} -- actual test code
+ \item \texttt{test\_fibonacci} -- actual test code
\item \texttt{assertEqual} -- compare actual result with expected one
\item Write documentation for above code.
\end{itemize}
\end{frame}
\section{\texttt{nose}}
-
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\begin{frame}[fragile]
\frametitle{\texttt{nose} tests}
\begin{itemize}
@@ -281,6 +305,47 @@ $ nosetests
\end{itemize}
\end{frame}
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\begin{frame}
+\frametitle{Summary}
+\label{sec-8}
+
+ In this tutorial, we have learnt to,
+
+
+\begin{itemize}
+\item Use of persistent test cases for better control.
+\item Undestand the use of doctest \& unittest.
+\item Understand the use of nosetest .
+
+\end{itemize}
+\end{frame}
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\begin{frame}[fragile]
+\frametitle{Evaluation}
+\label{sec-9}
+
+
+\begin{enumerate}
+\item ?
+\vspace{8pt}
+\item ?
+\end{enumerate}
+\end{frame}
+\begin{frame}
+\frametitle{Solutions}
+\label{sec-10}
+
+
+\begin{enumerate}
+\item
+\vspace{15pt}
+\item
+\end{enumerate}
+\end{frame}
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
\begin{frame}
\begin{block}{}
@@ -296,4 +361,5 @@ $ nosetests
\end{block}
\end{frame}
+
\end{document}
diff --git a/TDD/using_python_framework_for_tdd/tdd2_script.rst b/TDD/using_python_framework_for_tdd/tdd2_script.rst
new file mode 100755
index 0000000..85c96d5
--- /dev/null
+++ b/TDD/using_python_framework_for_tdd/tdd2_script.rst
@@ -0,0 +1,198 @@
+.. Objectives
+.. ----------
+
+ .. At the end of this tutorial, you will be able to:
+
+ .. 1. Know what is TDD.
+ .. 2. Understand the use of test cases.
+ .. 3. Write simple tests for a function.
+
+.. Prerequisites
+.. -------------
+
+.. 1. Test driven development - Part 1
+
+
+Script
+------
+
+.. L1
+
+{{{ Show the first slide containing title, name of the production
+team along with the logo of MHRD }}}
+
+.. R1
+
+Hello friends and Welcome to the tutorial on
+'Test driven development - Part 2'.
+
+.. L2
+
+{{{ Show slide with objectives }}}
+
+.. R2
+
+At the end of this tutorial, you will be able to,
+
+ 1. understand use of persistent test cases.
+ #. write doctest and unittest for any given function.
+ #. and understand the use of nosetest.
+
+.. L3
+
+{{{ Switch to the slide3, pre-requisite slide }}}
+
+.. R3
+
+Before beginning this tutorial,we would suggest you to complete the
+tutorial on "Test driven development-part 1".
+
+.. R4
+
+
+
+.. L4
+
+{{{ Switch to slide4 ,Persistent test cases}}}
+
+
+.. R5
+
+To illustrate TDD, lets take a simple program. Consider a
+function ``fibonacci``, that takes one argument and returns
+the nth number of ``fibonacci`` series.
+
+.. L5
+
+{{{ Switch to slide5, First test- fibonacci }}}
+
+.. R6
+
+To test ``fibonacci`` function, we need test
+cases.
+As shown in this slide,
+test cases are expected outputs for a given set of inputs.
+
+
+.. L6
+
+{{{ Switch to slide6, Test cases }}}
+
+.. R7
+
+The sample code for test cases is shown here. Observe that if
+any ``if`` statement is executed, test aborts after printing the
+error message.
+
+.. L7
+
+{{{ Switch to slide7, Test cases-Code }}}
+
+.. R8
+
+The ``fibonacci`` function is written just enough so that
+test can run.
+
+
+.. L8
+
+{{{ switch to slide8, Stubs }}}
+
+.. R9
+
+Combine the function and test cases and put them together in
+``fibonacci.py`` file.Add the test cases after name=main idiom.
+
+.. L9
+
+{{{ Switch to slide9, fibonacci.py }}}
+
+.. R10
+
+Lets run fibonacci.py by typing ``python fibonacci.py``.
+As we haven't written any meaningful code in our ``fibonacci``
+function, it fails immediately.
+Our next step is to write just minimum code to pass our tests.
+
+.. L10
+
+{{{ Run the fibonacci.py in terminal and show the error output.}}}
+::
+
+ python fibonacci.py
+
+.. R11
+
+Modify the fibonacci stub function with given code.
+Save and run it again as `` python fibonacci.py``.
+{{{ pause }}}
+Observe that, there will be no errors, as
+the test passes successfully.
+
+.. L11
+
+{{{ switch to slide-11, Euclidean Algorithm }}}
+Switch to terminal and modify fibonacci function in ``nano``
+editor and run.
+::
+
+ python fibonacci.py
+
+.. R12
+
+The same ``fibonacci`` function is modified to make it more readable
+and easy to understand using recursion.
+Pause this video here.Replace the ``fibonacci`` function with recursive one.
+Run the modified ``fibonacci.py`` file. The test should pass again
+without any errors.
+After successfully achieving this result, you can resume the video.
+
+.. L12
+
+{{{ Show slide12, Euclidean Algorithm- Recursive}}}
+
+
+.. R13
+
+This brings us to the end of the tutorial.In this tutorial,
+ we have learnt to,
+
+ 1. Undestand the basic steps involved in Test driven development.
+ #. Design a Test driven approach for a given ``fibonacci`` function.
+
+
+.. L13
+
+{{{ switch to slide-13,Summary }}}
+
+.. R14
+
+Here are some self assessment questions for you to solve
+ 1.
+
+ 2.
+
+.. L14
+
+{{{ switch to slide-14, Evaluation }}}
+
+.. R15
+
+And the answers are,
+ 1.
+
+ 2.
+
+.. L15
+
+{{{ switch to slide-15 ,Solutions}}}
+
+.. R16
+
+Hope you have enjoyed this tutorial and found it useful.
+Thank you!
+
+.. L16
+
+{{{ Switch to slide-16, Thankyou}}}
+