Funittest

  Funittest is a developers framework for rapidly writing functional
  tests.
  
  Funittest currently has a focus on testing web sites, in particular
  Plone sites, but it could be used to write functional tests
  for any type of application. The main motivation for the development
  if Funittest is to take functional testing to a higer level.

Physical Models

  On the lowest level, Funittest makes use of Selenium Remote Control, 
  which directly talks to the browser, and controls it like a normal user
  would.
 
  Check out Selenium RC here:
   
  http://www.openqa.org/selenium-rc/
 
  If you want to use Funittest for another low-level functional testing
  framework, you only need to reimplement the low-level physical object
  models, leaving all higher-level models unchanged.

  The following is an example of a physical object model for Selenium
  Remote Control:

    class Application:
    
      def login(self, user):
        interpreter.type("__ac_name", user['username'])
        interpreter.type("__ac_password", user['password'])
        interpreter.clickAndWait("submit")
 
  Using a physical object model method like the "login" method above
  is done through the following call sequence:

    physical.cmfplone.application.login(user)

  The elements in this call sequence :

  1. physical: The login method is being a physical method, we need to
     start our call seauence with the physical model. 
  2. cmfplone: There can be different products delivering a login
     method, so we need to use its name next in the call sequence
  3. application: The login method is grouped together with other
     verbs in the "application" cluster, which we need to specify next.
  4. login: The final element of the call sequence is the login
     verb itself, which is called with the user as an argument: "login(user)"

  There are good reasons to have such a long call sequence:

  1. Easy refactoring by search and replace
  2. No need to import anything, modules are imported dynamically
  3. Test different versions of a product transparently

  Let's elaborate on each of these reasons:

  1. Easy Refactoring

  Defining a good vocabulary is a challenging task. Renaming method names
  or models is very common in the early stages of development as you learn
  more and more about the problem domain.

  For example, if you want to know where the physical object model
  method "login" is used in Funittest, or any other model, just do a
  grep like this:

  >> grep -R physical.cmfplone.application.login *

  If you need to rename the login method to, say "login_user", you 
  can easily use a systematic search and replace. 

  Instructions for keeping up with a change of a model name from
  "application" to "app" could be announced like this:

    "There is an API change for the physical object model
     called 'application', which has been renamed to 'app'. Please
     make a search for 'physical.cmfplone.application.' and replace it
     with 'physical.cmfplone.app.'."

  This kind of API change would be much harder to follow if all of the
  users of Funittest used different import statements. 

  The fact that this kind of search and replace is easy to do, and error
  prone, encourages refactoring, which is the cornerstone of building up
  a high quality model step by step as you learn more about the problem
  domain.

  2. No imports

  In funittest, you only need to import the top level models. Everything
  that is contained in the models is accessed dynamically, and models
  are loaded on the fly.

  Just import the physical model and all the rest is taken care of:

    from funittest import physical
    physical.cmfplone.application.login(user)

  3. Transparent choice of version

  Lets count the ways you could do an import for access to the login
  method if you had to import the application cluster:

    from funittest.lib import plone214.cmfplone.physical.application
    plone214.cmfplone.physical.application.login(user)
    from funittest.lib.plone214 import cmfplone.physical.application
    cmfplone.physical.application.login(user)
    from funittest.lib.plone214.cmfplone import physical.application
    physical.application.login(user)
    from funittest.lib.plone214.cmfplone.physical import application
    application.login(user)

  In any of these cases, you would be fixing the Plone version that
  you import the application cluster from. There is no way around this
  limitation using imports.

  Funittest needs to be able to choose the model version on the fly,
  and it does so by choosing the model version transparently:

  Use the implementation for the current Plone version:

    physical.cmfplone.application.login(user)
  
  Use the implementation for Plone version 2.1.4:
  
    load_product_version("cmfplone", "2.1.4")
    physical.cmfplone.application.login(user)

  Use the implementation for Plone version 2.5.0

    load_product_version("cmfplone", "2.5.0")
    physical.cmfplone.application.login(user)

  As you can see, the call is always the same, not matter what the Plone
  version is:

    physical.cmfplone.application.login(user)

Logical Models

  The next level of abstraction above the physical model are the logical
  functional models, which contain methods for all user actions, 
  grouped by functionality. The Logical Functional Model hides 
  implementation details in a Physical Object Model that is concerned 
  with interacting with the user interface. Different Logical Functional
  Models can share a common library of user interface methods.

  They typically use the lower level physical object models as you can
  see in the following example of a logical functional model called 
  "Application":

    from funittest import physical
    class Application:    
      def login(self, user):
        physical.cmfplone.application.login(user)
  
  It is important is to define all of the possible user actions early
  on when defining use cases and functional tests. It is not important
  to already have an implementation for them

Verification
  
  Funittest supports the concept of loosely coupled comprehensive 
  verification. Verification is done before and after the call to
  a logical Functional Model method. Expected state can be compared
  to actual state, and verification can take place all of the time,
  not just in an isolated functional test case.

Test automation stack
  
  Funittest takes over some of the concepts of the test automation
  stack, explained in the Braidy Tester Blog:
  
  http://blogs.msdn.com/micahel/archive/2005/05/04/FromAccountantToScientist.aspx
  
  Some aspects of Funittest are diverging from the ideas of the Braidy
  tester, and also the implementation details can diverge. The general
  ideas still apply, so if you want to dive more into the concepts behind,
  read the whole Braidy Tester blog, and if you are inspired enough, I
  invite you to propose enhancements for Funittest. 

Data Providers

  Funittest gets example data from so called data providers, which define
  a data structure for example data and also contain the data itself. 
  There is currently no generation of test data, test data is just moved
  around in a simple dictionary. This simplicity is wanted for the moment,
  and proposals of a different implementation are welcome.

  Implementation of Execution Behavior is rudimentary in Funittest, meaning
  that different implementations of a method are chosen by random from a list
  of possible methods implementing the execution behavior. If you have an 
  idea about how to get this done elegantly using Python decorators, let me know. 

Funittest

  There are two application areas for Funittest.
  
  1. Write functional tests for a web application
  
  2. Remote control a web application

  Funittest is very friendly to the programmer in that it allows the
  execution of functional tests in three modes
  
  1. Dry run mode, simulating the execution of all functional tests.
     Usually takes seconds, and allows you to find errors in your Python
     code instantly.
  
  2. Debug mode, running all functional tests using a browser, but pausing
     in case of an exception. This allows the programmer to debug the
     state of the browser.
  
  3. Run all functional tests automatically.  

  Enjoy!
  
Documentation
=============

Funittest comes with complete documentation, which you can consult
by opening the file "doc/index.html" in a browser. The recommended way to
view the documentation is by launching the Crunchy Frog client using 

cd funittest/doc
python crunchy.py

Crunchy Frog will open a browser showing the index.html file in the
docs Folder. You can follow the link to the Funittest tutorials and
execute Python code interactively. The advantage is that you don't
need to copy and paste any Python code to a Python interpreter.
The result of the executed commands are shown inside the browser
below the code example windows.

Please make sure to have elementtree installed (See below for
the dependencies).
  
Dependencies
============

Required Products
------------------

* Tested with Python 2.4.x, but should work with earlier and newer versions of Python

* ElementTree library for Python is needed for Crunchy Frog tutorials in the "docs" folder

  http://effbot.org/zone/element-index.htm

* Selenium RC 0.9.1 (selenium-remote-control-0.9.1-SNAPSHOT.zip 27-Dec-2006 01:49  8.8M):

  http://release.openqa.org/selenium-remote-control/nightly/

* JRE (Java Runtime Environment) version 1.5 or higher is needed in order
  to run Selenium RC.
  
* Firefox 2.x

Installation
============
  
Installing Funittest
--------------------

* Make it available in your Python module path or copy it to the
  Lib/site-packages folder

--

Experience is what you get when you were expecting something else.
