Author: maikroeder Date: Wed Mar 22 01:57:05 2006 New Revision: 2682 Modified: cookbook/trunk/recipe07.en.tex Log: Draft
Modified: cookbook/trunk/recipe07.en.tex ============================================================================== --- cookbook/trunk/recipe07.en.tex (original) +++ cookbook/trunk/recipe07.en.tex Wed Mar 22 01:57:05 2006 @@ -3,17 +3,252 @@ \include{macros} \status{draft} \author{Tarek Ziad�} +\translator{Maik R�der} \begin{document} \chapter*{Writing functional tests} \begin{problem} -Not translated in english yet. +Functional or "acceptance tests" are complementary to unit +tests: They are black boxes that make sure that the +functionality of the whole system, is working as it was +defined. + +These tests are only covering the use of the system from the +point of view of a normal user, in order to make sure that +all the expected functionalities are present and work as +expected. + +They are an infallible sign of quality for the end users, who +may even occasionally write or extend these tests themselves. + +In terms of proportion, a system should have approximately +2/3 of unit tests and 1/3 functional tests, given that the +latter are on a higher level. + +For web applications, these tests need a special execution +environment, which imitates a browser as best as possible, and +offers an API for lauching requests and study the response, +just like it would be done by FireFox or Internet Explorer. \end{problem} \begin{solution} -Not translated in english yet. + +\section*{Understanding how it works} + +By definition, a functional tests only concentrates on the +functionality of the application, and doesn't care for +implementation details, as opposed to unit tests. It is a +practical test as performed by an end user, who can for example +browse the menues of the application in order to test each +of them. He then uses a check list to make sure that all expected +functionality is there and works fine. The system is said to +be "validated" once all items in the check are verified to be +correct and in conformity with the specification. + +Functional tests for Python and of course for Zope, are done +by the means of test classes derived from unit test classes, or +in doctests. + +The only difference between unit tests and functional tests +is the interface between the system and the test writer. +The tester writer has to write the tests under the same +conditions that an end user would have at their disposal. + +\subsection*{Writing functional tests with classes} + +Zope provides a special module in the \code{zope.app.testing} +package called \code{functional}, whose classes inherit from +\code{unittest.TestCase}. The most commonly used class is +\code{BrowserTestCase}. + +\noindent It has the following methods: +\begin{itemize} +\item \code{getRootFolder()}: returns the Zope root folder. It's used +to prepare the site before analysing a publication. +\item \code{publish(path, basic=None)}: Returns the rendered object +behind the path in the form of a \code{Response} object. the +optional \code{basic} parameter has the form +\code{'login:password'}. The string 'mgr:mgrpw' can be used to +authenticate with manager rights. +\item \code{checkForBrokenLinks(body,path, basic=None)}: Checks for +broken links in the \code{body}, which is a string representing +the body of the response (fetched with \code{Response.getBody()}). +\code{path} gives the relative path that corresponds to the object +at the origin of the response. This parameter is needed by the +method in order to make sense of relative links in the site. +\end{itemize} + +The test utility augments the \code{Response} object with +methods for retrieving information easily, such as: + +\begin{itemize} +\item \code{getBody()}: returns the response body +\item \code{getOutput()}: returns the complete response (body and +headers) +\item \code{getStatus()}: returns the status +\item \code{getHeaders()}: for retrieving the headers +\end{itemize} + +In the following example, a folder is created under the +root folder, and its rendering is verified: + +\codetitle{Verification classof the rendering of a folder:} +\begin{Verbatim} +>>> import transaction +>>> from zope.app.testing import functional +>>> from buddydemo.buddy import BuddyFolder +>>> class FolderTest(functional.BrowserTestCase): +... def test_folder(self): +... root = self.getRootFolder() +... +... # create a Folder +... root['cool_folder'] = BuddyFolder() +... transaction.commit() +... +... # get the root content through the publisher +... response = self.publish('/cool_folder', basic='mgr:mgrpw') +... self.assert_('<title>Z3: cool_folder</title>' in +... response.getBody()) +\end{Verbatim} +\begin{codenotes} +\item After having validated the modification in the site +with the code \code{transaction.commit()}, the object is +available for the \code{publish} call. +\end{codenotes} + +The test is executed like a classic unit test. + +\codetitle{Starting the test:} +\begin{Verbatim} +>>> import unittest +>>> suite = unittest.makeSuite(FolderTest) +>>> test_runner = unittest.TextTestRunner() +>>> test_runner.run(suite) +<unittest._TextTestResult run=1 errors=0 failures=0> +\end{Verbatim} + +\subsection*{Functionaltests with doctests} + +Functional tests are always very high level tests, and come very +close to example code that could be part of a documentation. In +Python, the both the need for documentation and for testing can +be satisfied by the means of doctests. + +\subsubsection*{Understanding Python doctests} + +The idea of putting tests into Python doctests is quite intriguing: +The code is written in the docstrings as a series of interactions +on the Python prompt. + +\codetitle{Doctest example:} +\begin{Verbatim} +>>> def theTruth(): +... """ +... >>> theTruth() +... 'doctests rule' +... """ +... return 'doctests rule' +\end{Verbatim} + +The Python \code{doctest} module contains utilities for extracting +and execuring these tests. + +\codetitle{Execution of a doctest:} +\begin{Verbatim} +>>> import doctest +>>> doctest.run_docstring_examples(theTruth, globals(), verbose=True) +Finding tests in NoName +Trying: + theTruth() +Expecting: + 'doctests rule' +ok +\end{Verbatim} + +Once doctests are added to all modules, classes and methods, the +readability of the code may suffer. In order to avoid this code +obfuscation, it is recommended to group the functional tests +in a separate file, which is then "executed". This opens the +way for explaining the examples in more depth, and to follow +logical steps. In this case, we can speak of "agile documentation", +or "executable documentation". + +The documents can then be called by the test module. + +\codetitle{Agile document:} +\begin{verbatim} +Le module of truth +================== + +This document doesn't contain any functional tests, but +a unit test. + +It doesn't matter at all. + +The truth module contains the function `theTruth()`, which +can be called for showing a message. This method doesn't serve +any real purpose. + +Usage example:: + + >>> from truth import theTruth + >>> theTruth() + 'doctests rule' +\end{verbatim} + +\subsubsection*{Understanding functional doctests in Zope} + +In order to easily manipulate doctests in Zope, the +\code{zope.testbrowser} package offers the \code{Browser} class, +which allows the simulation of a browser and its functionalities. +By convention, this class is invoked with the URL starting with +\url{http://localhost}, followed by the path to the Zope server. + +This is how you can write a test to reach the page situated at the +root: +\begin{Verbatim} +>>> from zope.testbrowser.testing import Browser +>>> browser = Browser('http://localhost') +>>> browser.url +'http://localhost' +>>> browser.contents +'...<title>Z3: </title>...' +\end{Verbatim} +\begin{codenotes} +\item The \code{contents} property is used to fetch the content of +the page in the form of a string. +\item The '...' (or ellipsis) have a precise meaning in the doctests: +They stand for an arbitrary amount of text. In the above example, +they allow us to make sure that \code{contents} does contain the +given HTMLcode. +\end{codenotes} + +Besides \code{contents} and \code{url}, the main elements provided by +\code{Browser} are: +\begin{itemize} +\item \code{open(url)}: allows to open the given URL. +\item \code{isHtml}: is the content written in HTML ? +\item \code{title}: returns the title of the page. +\item \code{headers}: returns the Python object \code{httplib.HTTPMessage} +containing the headers. +\item \code{getLink(text)} : fetches the \code{Link} object for the link +given in \code{text}. This link can then be followed using the \code{click()} +method, whereby the new pageis loaded in the \code{Browser} instance. +\end{itemize} + +You will be able to learn about further elements offered by this +utility in some other recipes. + +\section*{Limitations of functional tests} + +Functional tests for the web are somewhat limited in what they can do if +compared to browers: It is impossible to test JavaScript contained in the +pages, because each browser comes with its own engine. For this, you need +to look at other technologies, like \textit{Selenium}, which runs the tests +inside a real browser. + \end{solution} \end{document} -- http://lists.nuxeo.com/mailman/listinfo/z3lab-checkins