Setting up git on a qnap 219P NAS device

November 24th, 2012

Installing git on Qnap

I needed to install git on my qnap 219P and 219P+. I found the existing howtos inconsistent and confusing so I thought this might come handy for people like me who want to setup a git repository on their qnap. Here is what I finally did…

Firmware Update

First I had to do a firmware update. At time of writing 3.7.3 was available. I downloaded, unpacked and uploaded the “TS-219_20120801-3.7.3.img” file to my qnap via the admin interface.

Setup Networking

I did this setup while I carried the qnap on a business trip. I had no network switch or dhcp server with me. Instead I wanted to connect the qnap directly via ethernet cable to my ubuntu laptop.

If you access the qnap through your home or office network and it already has IP and internet access you must not follow the steps for “Setup Networking”.

For installing packages on the qnap I had to share the wifi connection. The most convenient way for me was to set the method to “Shared to other computers” in network connections:

 

Once connected the qnap gets an IP address and also the laptop.

You can find out the one of the laptop:

# ifconfig

To find out the IP of the qnap is a little bit trickier:

# nmap -sP 10.42.0.*

Nmap will list <your qnap IP> which you will use for all steps. To access the admin GUI of the qnap type http:<your qnap IP>:8080

Enable admin account to use ssh

Be sure SSH is enabled. In the Administration interface, go to Network Services, then to Telnet / SSH. Be sure the box Allow SSH connection is checked. Note the port number in the adjacent text box, and change it if desired. If you changed anything, click the Apply button.

Now you are able to ssh into your qnap as admin user:
ssh admin@<your qnap IP>

Add IPKG

In the device’s Administration interface, select the ‘System Tools’ -> ‘QPKG’ page, click on ‘Get QPKG’ button.

QPKG provides a nice admin interface to install additional packages right from your qnaps admin GUI. Here is how the installed Optware IPKG package looks like:

Use IPKG to install git

Ssh into your qnap and install git:
# ssh admin@10.41.0.98

List the available packages relevant for git:
# ipkg list|grep git

Install git:
# ipkg install git

Add ssh daemon openssh

The default configuration for qnap only allows the admin user access via ssh. In order to login other users than admin one needs to install openssh.

I found it the easiest to simply add the openssh daemon to the already existing one. I just changed the existing ssh port configuration via the admin GUI to 222. This makes room for openssh to use port 22.

Use ssh to access your qnap:
# ssh admin@<your qnap IP> -p 222

Install the Openssh package using Optware:
# ipkg update
# ipkg install openssh

mount -t ext2 /dev/mtdblock5 /tmp/config

Just add “/opt/sbin/sshd &” to autorun.sh in /tmp/config and make it executable:
# chmod a+x /tmp/config/autorun.sh

Then reboot:
# reboot

Setup a git repository

I used the qnap admin GUI to create a “SHARE FOLDER” git.

Ssh into your qnap with the admin account:
# ssh git@<your qnap IP>

Now setup the new repository:
# cd /share/git/
# mkdir NEW.git
# cd NEW.git
# git –bare init

I also wanted to collect the ssh authorization key from my developer machine (from ~/.ssh/id_rsa.pub) so I do not have to provide a password each time I commit something to the repository:
# cd /share/git/
# mkdir .ssh
# touch .ssh/authorized_keys
# chmod 700 .ssh/
# chmod 600 .ssh/authorized_keys

Openssh is very concerned about permissions and ownership of files especially of the home directory. I had (in the qnap admin GUI) set advanced options / advanced folder permissions. Then I could set git as owner of the /share/MD0_DATA/git folder. After I reduced the permissions to 700 it all worked:
# chmod 700 /share/MD0_DATA/git

Now copy the public key from your development machine (this only works for single keys, if you have multiple you will have to add them one key per line.):
# scp ~/.ssh/id_rsa.pub git@<your qnap IP>:.ssh/authorized_keys

Setup git user account

Create user account “git” in the qnap admin GUI

Ssh into your qnap with the admin account:
# ssh admin@<your qnap IP>

Check that the home directory is correctly configured as /share/git:
# cat /etc/passwd

Using the new repository

You can for example clone the new repository from your developer machine:
# git clone git@<your qnap IP>:/share/git/NEW.git

Cleaning up

Now you do not want that the git users to open a terminal on the qnap.

Ssh into your qnap with the admin account:
# ssh admin@<your qnap IP>

Set git-shell for the git user in /etc/passwd:
admin:x:0:0:administrator,,,:/share/homes/admin:/bin/sh
guest:x:65534:65534:guest:/share/homes/guest:/bin/sh
git:x:500:100:Linux User,,,:/share/git:/opt/bin/git-shell
httpdusr:x:99:100:Apache httpd user:/tmp:/bin/sh

We want to make sure that terminal access is prohibited so I try it out:
# ssh git@<your qnap IP>
fatal: Interactive git shell is not enabled.
Connection to <your qnap IP> closed.

Debugging the openssh daemon

Ssh into your qnap with the admin account to the original sshd:
# ssh admin@<your qnap IP> -p 222

Stop openssh
# ps -ef | grep /opt/sbin/sshd
3572 admin 1048 S /opt/sbin/sshd
5735 admin 588 S grep /opt/sbin/sshd
# kill 3572

Run openssh in debug mode:
# /opt/sbin/sshd -ddd

Committing an existing repository

This scenario describes how to initialize a repository from an existing folder on your developer machine (make sure the repository exists on the qnap).

… on your developer machine:
# cd myproject
# git init
# git add .
# git commit -m ‘initial commit’
# git remote add origin git@<your qnap IP>:/share/git/NEW.git
# git push origin master

More Information

You can install IPKG package by QPKG and then install git by IPKG.

http://wiki.qnap.com/wiki/Install_Optware_IPKG

http://wiki.qnap.com/wiki/How_to_SSH_into_your_QNAP_device

http://wiki.qnap.com/wiki/How_To_Replace_SSH_Daemon_With_OpenSSH

now install git via IPKG:

http://www.wonko.de/2010/04/set-up-git-on-synology-nas.html

cool intro on linux:

http://www.corvidworks.com/articles/self-hosted-remote-git-repositories

issues on permissions and directory / file ownership:

http://unix.stackexchange.com/questions/4484/ssh-prompts-for-password-despite-ssh-copy-id

Agile testing in the large @ PyCon DE 2012

November 1st, 2012

Monday 29.10.2012 I had the great opportunity to run a 3 hour tutorial at PyCon DE on “Agile testing in the large”. I released a preliminary version of my book “The Hitchhiker’s Guide to Test Automation” as a handout.

I gave a stripped down version of the tutorial as conference talk (the video comes with German audio only, sorry).

You can find the slides here:
http://www.testing-software.org/wp-content/uploads/PyConDE2012-Mark_Fink-AGILE_TESTING_IN_THE_LARGE.pdf

The ajaxdemo sample I used you can find here:
http://github.com/markfink/ajaxdemo

I had tons of fun at this great conference and I am looking forward to meet you all again next year in Cologne.

Take care,
Mark

 

 

Debugging an AngularJS application

July 28th, 2012

I have spent the better half of the day on AngularJS IRC channel in order to fix my sample ajax application:
https://github.com/markfink/ajaxdemo

Meanwhile a debugging tool is available for canary developer tools:
http://blog.angularjs.org/2012/07/introducing-angularjs-batarang.html

I found a useful video on youtube on how to use the batarang, too:
http://www.youtube.com/watch?v=q-7mhcHXSfM

The batarang looks almost like a Klingon Bat’leth and installation proves to be a little bit scary at first. But then the batarang debugging tools proves to be really helpful.

long weekend -> lots of new stuff…

May 20th, 2012

A new file editor… I have been unhappy with the file editors I am using for a long time. That is probably the reason that almost every time I plan to spent a significant amount of time on no job related topics I check if there are new file editors available, first. A few month before I learned about Geany and Ninja-IDE, both having great potential, but… This time I checked out Sublime Text 2. ST2 has lots of cool features like it is fast, available on WIN/Linux/Mac, has a Plugin-Mechanism where Plugins are written in Python \o/, it is compatible to Textmate for themes, modes etc. I have been using it for almost a week now and so far I like it a lot.

The plan for the weekend was to familiarize with Google Closure-Tools, specifically Closure-Library and Soy Templates. I started by setting up a working environment (Plovr is helpful here), reading “Closure – The Definitive Guide” and implementing a simple Todo app. Unfortunately Soy templates do not integrate well with Closure-Library. I previously checked out YUI, JQuery UI and GWT but did not like any of them very much either. I broadened my search.

I know Misko Hevery from his excellent presentations on testable Javascript (“Clean Code Taks”) and I found out that he is now involved in Angular.js what he calls a Javascript framework with testing in mind. Since I experienced in my day job that untestable presentation layers are a significant issue I got hocked up. I took one of the sample applications and exchanged the PHP backend with a restful webservice based on mongodb. I hope I will find some more time during the next weeks to continue this fun excercise. Here is what I got so far:
https://github.com/markfink/ajaxdemo

code retreat

April 1st, 2012

I currently work for Kuehne & Nagel. We do agile development and follow “Clean Code”. They invited me to take part on a company sponsored code retreat last Saturday. So I stayed the weekend in Hamburg to hone my programming skills and to spend more time with my new coworkers. Thanks for the great opportunity!

Here some background info on the code retreat format

I never took a deeper look into Conways Game of Life before, so I was astonished about how much complexity can emerge from such a small set of simple rules (think fractals).

We did TDD and pair programming with focus on different aspects. Something I wanted to get going was to do TDD Javascript development. We used JsTestDriver for that and figured out how to do it.

On rule of a code retreat is to start each 45 min round from scratch with a new partner. So we could not finish the exercise. So for today I planned to complete the exercise with a running version.

You can find my code on github
Your browser does not support the canvas element.

PyCon DE talk on “FOSS Ajax Performance Test Harness” was recorded

October 29th, 2011

The Pycon DE 2011 team did an awesome job in recording all the conference talks. The quality is extraordinary.

Note: Narrative is in German.


Slides:

Mark_Fink_Ajax_PerfTest_Harness_v1.0

 

Ajaxrunner

September 19th, 2011

Pycon UK 2011 is comming up this weekend. I will give a talk on “Open source tool harness for perfomance testing AJAX applications”. I will explain how I think existing open source test tools can be used to performance test your AJAX applications. I have been working on some clue code which I call Ajaxrunner. I would love to get some feedback from you.

Three components of Ajaxrunner:

  • Analyze (extract information from existing data and make it available)
  • Record & Script (to prepare testcases)
  • Runtime environment and Admin to execute your performance tests

I will demo all three parts during the talk. Video on Record & Script with Selenium-IDE:

This is the first time I talk publicly about Ajaxrunner. I will continue to work towards its first release over the next weeks.

Functional Test Automation with Selenium

July 16th, 2011

Currently functional test automation is rarely applied to its full potential, therefore often delivering poor and inadequate results. Consequently functional test automation is often abandoned.

Test automation can play an important role in software development, significantly improving quality and allowing shorter development cycles and time to market. I propose an approach for efficiently automating functional tests with DSLs that are readable for domain experts (requirement owners) and encourage re-use. This leads to better software quality thanks to the knowledge input from the domain experts into the test suite. Re-usable building blocks lead to lower maintenance efforts for the test suite.

Introduction

Testing is essential to software quality. The success of service oriented companies heavily depends on the quality of their software. It is still not clear in what way to apply functional test automation in order to significantly improve software quality.

Here I will describe an approach for how to successfully implement functional test automation which in comparison to the traditional approach significantly reduces the cost of ownership of a test suite. The approach mitigates the risk of fast evolving software through the use of maintainable functional test automation.

This paper will identify and describe the problems of functional test automation and propose an approach for how to address the problems.

My contributions:

  • My approach significantly reduces the total cost of ownership of an automated functional test suite.
  • I have been experiencing in practice that domain experts are not involved in testing and, what is worse, domain specific tests are neither documented nor executed. I show how this approach involves the domain experts in the functional test automation.
  • I analysed existing approaches that could support DSL Modelling. I show how to use the existing knowledge and leverage it in order to successfully jump-start a test automation initiative.
  • I demonstrate how to implement this approach in Python.
  • I use an up-to-date example throughout the whole paper to show the utilization of the various aspects of my approach.

The Problem

Software experts agree that testing is essential to software quality. As with software engineering, strategies vary on how to do it. When arguing about the right testing strategy the discussion circles around the following contexts:

  • What level of application testing is appropriate? Should we focus on unit testing, functional testing, GUI testing, integration testing?
  • How do we determine if the testing is finished? How do we decide if the product can be shipped/ released?
  • How often do we test? Every release, every month, every week, every day, or even after each change to the source code?
  • What tools do we use?
  • What methodologies should we follow?
  • What amount of tests should we automate?
  • Who performs the testing? Who owns the testing discipline or process?

Test automation does not solve or even address all of the questions above but it should definitely be considered in your testing strategy. The biggest benefit of automated testing is that once the tests are implemented you can run them as often as your hardware allows at no additional cost. Also, automated tests are reproducible whereas manual tests, in many cases, are not. On the other hand, test automation itself is another expensive software development project. An automated test suite must be maintained to be effective, which is expensive, as well. For example GUI level test automation is typically created with the capture and replay method. If the GUI changes substantially the test cases need to be re-recorded. In an environment that evolves rapidly this can be hard to achieve. As an analogy to software-refactoring, in software development the test suite should be refactored to encourage reuse and the DRY (Don’t Repeat Yourself) pattern.

The following diagram shows the information flows in a typical software development process for service oriented industries such as telecommunications.

Information Flow within the Software Development Process

The diagram gives an overview of the information flows between the business units within a company. The technical business units provide services like software development, end-to-end-testing and systems operation. The business department understands the markets in which the company operates and defines product strategies. The people in the business department define the requirements for software development and are the domain experts. They work closely with analysts from the technical development unit who capture and analyse the requirements. Experienced analysts can also be seen as domain experts. Domain experts frequently do not define domain specific requirements because, by nature, these seem trivial or too intuitive to specify. An example for such implicit domain knowledge is what products are placed in what channels. Instead they focus entirely on specifying requirements for the computer system. The problem I want to point to is that testers may lack domain specific knowledge, which in turn impacts the test results. As a rule, testers do not get in touch with domain experts because they work for different business units within the company. The test analysts and testers base their test case documentation on analysis and development artefacts such as requirements, analysis and specifications. If requirements have been based on computer system requirements, as opposed to requirements based on domain knowledge, then these artefacts will not contain domain specific knowledge. This means domain specific tests are not performed. As a consequence this can only mean that you must involve the domain experts in test initiatives. Leaving the cultural issues out of the equation for a moment, this is still difficult because domain experts are often lacking application knowledge, test knowledge and technical background. To be successful in domain specific testing you must involve the domain experts and bridge the technological gap in order to capture the domain specific knowledge you need for the test cases.

Automated testing has a lot of potential and is very promising but currently is not delivering all possible results because it makes the maintenance and domain specific knowledge questions more difficult to answer.

If properly done an automated functional test suite can:

  • Reduce cost and time of regression testing.
  • Increase quality by freeing testers to focus on value added activities such as exploratory testing.
  • Increase quality by faster feedback to developers.
  • Capture and implement domain specific tests.
  • Make requirements explicit by giving developers executable acceptance tests.

Domain Specific Languages (DSL) to the rescue

A Domain Specific Language (DSL), as opposed to a General Purpose Language (GPL), is a programming language tailored specifically to an application domain; rather than being for general purpose, it captures precisely the domain’s semantics. Popular examples for DSLs are HTML (used for document markup) and VHDL (used to describe electronic circuits). DSLs allow the concise description of an applications logic reducing the semantic distance between the problem and the program.

Testing tools and frameworks address a broad range of application domains (broad focus). This is natural because the tool vendor or developer wants to see his tool applied in a broad range of applications. For instance the Selenium test automation tool can be used to test a web shop application and the same tool can be used to test a social community web application. The tester or test automation developer is at the other extreme; he has a very narrow functional focus on the complexity of his application. He needs tooling that fits as well as possible in order to be able to succeed with the initiative and gain efficiency. He will probably start a new framework to exactly address his requirements. That is one of the reasons why there are so many frameworks available.

Besides writing a new framework there is another possibility to address the needs of the test automation developer. You can customize an existing framework so that it fits your needs. This can be done in two ways. The first and obvious way is to modify or extend the existing tool. Unfortunately this has its own problems, like the fact that the source code is not available for proprietary tools. Therefore updates, to new tool versions are problematic especially for customized proprietary solutions. The other alternative is to perform the customization within a layer on top of the existing tool. In this case you can totally shield away the complexity of the underlying testing tool. In the extreme, you can later if necessary exchange the underlying framework or tool you are using, without changing your test cases. In this variant the possibilities to extend functionality are limited. The positive aspects of the layering approach outweigh the negative therefore we will use this approach for this paper. The following diagram shows the different software layers within our testing approach.

Layers of the Test Automation Implementation

DSLs are by definition special purpose languages:

  • Concrete expressions of domain knowledge (captured in human readable form, not buried into system source code).
  • Direct involvement of the domain experts. The DSL format can often be designed in a way that matches the format typically used by domain experts. This results in keeping experts in a very tight software life cycle loop.

As explained in the last section, domain experts are often lacking technical background, but it is necessary to involve them in order to get domain specific testing right. The DSL you and I are going to build has its focus on the application domain and hides the complexity of the testing framework away. Since no testing framework knowledge is necessary this brings test automation in reach of application domain experts. With a little help they will be able to review and comment on test cases and draft outlines of new test cases soon. The goal of a functional testing DSL is to be as human readable as possible and to abstract the complexity of the underlying testing tools. The following example test case for the openstreetmap should give you an intuitive idea of what we want to achieve.

browse http://www.openstreetmap.org

search Von-Gumppenberg-Strasse, Schmiechen

pan right

zoom in

Another test automation problem we want to address with the functional testing DSL is the expensive maintenance of the test suites. The DSL commands enforce reuse and reduce code duplication. The decomposition of the test cases into commands makes it easier to identify where to apply changes and simplify the changes as well. The DSL will enable maintenance of the test suite without the necessity of re-recording test cases after each change. This will significantly reduce maintenance costs and the cost of ownership of the automated test suite.

DSL Modelling

The term DSL modelling is rarely used in literature. Most of the scientific papers on DSL use the terms DSL design and development. Most authors focus on the topic of implementing a DSL. They discuss differences, pros and cons of internal and external DSLs and describe implementation related patterns in great detail. Unfortunately the practice oriented reader who wants to use a DSL to solve a concrete problem is left alone when it comes to DSL modelling. In order to address the tasks necessary to model a specific application domain I will use the term DSL Modelling to distinguish it from the tasks necessary to implement a DSL, which I will address as DSL Implementation (the next section).

DSL modelling consists of the analysis of the problem domain, refinement and design, and the methods used to drive this process. A prerequisite for the design of a DSL is a detailed analysis and structuring of the application domain. Graphical Feature Diagrams [3] are part of the FODA methodology and have been proposed to organize the dependencies between such features, and to document variabilities. In DSL modelling it is important to capture variabilities of your application domain because variability is the key factor in identifying complexity in your application domain. The following diagram shows such a feature diagram for the application domain of our example.

Feature Diagram for our Web Application Test example

The feature diagram helps us to document and drive the DSL modelling. Potential sources of features are:

  • Existing and potential stakeholders.
  • Domain experts and domain literature.
  • Existing systems and Documentation.
  • Existing analysis and design models (Use-case models, Object-models, etc.).
  • Models created during development (Entity-Relationship-Model, Class-Diagram, etc.).

For DSL modelling in the area of test automation we should use the following additional sources to look for features and vocabulary of domain specific knowledge:

  • Product descriptions targeted to the customer.
  • Any other sources that describe the behaviour of the system in the terms used by the domain expert (change requests, trouble tickets, bug reports).
  • Descriptions of manual test cases of the area to be automated.

Strategies to identify features from the Czarnecki book [1]:

  • Look for important domain terminology that implies variability, for example, checking account vs. savings account.
  • Examine domain concepts for different sources of variability, for example, different stakeholders, client programs, settings, contexts, environments, aspects, and so on. In other words, investigate what different sets of requirements mean for different domain aspects.
  • Use feature starter sets to start the analysis. A feature starter set consists of a set of aspects for modelling. Some combinations of aspects are more appropriate for a given domain than for another. For example, authentication, security, transactions, logging, etc.
  • Look for features at any point in the development. As mentioned before, we have high-level system requirement features, architectural features, subsystem and component features, and implementation features. Thus, we have to maintain and update feature models during the entire development cycle. We may identify all kinds of features by investigating variability in use case, analysis, design and implementation models.
  • Identify more features than you initially intend to implement. This is a useful strategy which allows us to create room for growth.

Steps in feature modelling from the Czarnecki book [1]:

  1. Record similarities between instances, that is, common features, for example, all accounts have an account number.
  2. Record differences between instances, that is, variability, for example, some accounts are checking accounts and others are savings accounts
  3. Organize features in feature diagrams. Organize them into hierarchies and classify them as mandatory, alternative, optional.
  4. Analyse feature combinations and interactions. We may find certain combinations to be invalid.
  5. Record all the additional information regarding features such as short semantic descriptions, rationales for each feature, stakeholders and client programs interested in each feature, examples of systems with a given feature. Document constraints, default dependency rules, availability sites, binding sites, binding modes, open/closed attributes, and priorities.

Start with steps 1 and 2 in the form of a brainstorming session by writing down as many features as you can. Then try to cluster them and organize them into feature hierarchies while identifying the kinds of variability involved (i.e. alternative, optional etc.). Finally, refine the feature diagrams by checking different combinations of the variable features, adding new features, and writing down additional constraints. Maintain and update the initial feature models during the rest of the development cycle. You may also start new diagrams at any point during the development.

The feature diagram is very helpful during domain analysis. The DSL modelling for our openstreetmap example is not yet finished. In general it is unclear how to proceed once a feature diagram exists [3]. I think in software engineering there is no “formula” which can be generically applied on how to implement a piece of software from a design model. Like in software engineering there are engineering skills necessary to transfer a feature diagram into a DSL. One thing that definitely helps to bridge this gap is the decision on how to implement the DSL in the first place. How to implement a DSL in Python is covered in the next section.

DSL Implementation in Python

Two common variants exist for DSL implementation. A DSL can be implemented as internal DSL or external DSL. The terms internal DSL and external DSL have been coined by Martin Fowler [8]. An external DSL is a “independent” programming language. The external DSL is implemented like a general purpose programming language. Some kind of parser tool or framework is used to interpret or compile the external DSL based on a grammar to the target platform.

On the other hand, a DSL can be implemented as an internal DSL. This is also known as the piggyback pattern. The internal DSL is written like a normal program in the target programming language. The syntactic features of the target language are used to make the program more human readable. The programmer uses indentation and naming of the methods and variables to make the program read like sentences in a natural language. All the existing infrastructure of the target language like a parser, interpreter or compiler are used. It is also possible to extend or limit the features of the target language if necessary. The effort to create an internal DSL is usually smaller compared to creating an external DSL. Sometimes it also comes in handy to have the underlying power of the target language at hand. On the other hand the syntax of the internal DSL is limited by the syntax of the target language.

Internal DSLs are often implemented by use of Method Chaining. With method chaining it is easy to implement a DSL even in system programming languages like C++ and Java. The following code fragment shows the implementation of a test case as internal DSL in Python.

from osm_dsl import Browser

Browser("http://www.openstreetmap.org/") \
    .click_view() \
    .search("Von-Gumppenberg-Strasse, Schmiechen") \
    .pan_right() \
    .zoom_in()

Compared to natural English language this is strictly formatted and still has a lot of parentheses. Note that the verification steps for the automated test case will be built into the DSL commands and will not be visible in the test case description.

from selenium import selenium
import unittest, time, re

class Browser(unittest.TestCase):
    def __init__(self, website):
        self.setUp(website)

    def zoom_in(self, amount):
        for i in range(amount):
            self.selenium.click("OpenLayers.Control \
 .PanZoomBar_6_zoomin_innerImage")
            time.sleep(1)
        return self

    def pan_right(self, amount):
        for i in range(amount):
            self.selenium.click("OpenLayers.Control \
 .PanZoomBar_6_panright_innerImage")
            time.sleep(1)
        return self
    ...

The above code sample shows the implementation of the commands “zoom_in” and “pan_right” for the internal DSL. In order to follow the method chaining pattern all methods in the example return a reference to the class instance.

The implementation of the functional testing DSL as an internal DSL is a valid option but in my opinion the resulting language is still not at all like natural language, and therefore not suitable for interaction with the domain experts. Alternatively the implementation as external DSL means additional effort for modelling and maintaining the grammar of the external DSL. It is clear that, when modelling a DSL for functional test automation, the language would change a couple of times in the beginning in order to adapt to the application domain, and to make it as human readable as possible. Changes to the DSL grammar would mean additional overhead when implementing an external DSL. I could not find a way to improve the readability of the internal DSL implementation in Python to an acceptable level. While looking for a way to improve the readability of the internal DSL, I had an idea for implementing an external DSL in such a way that the benefits of the internal DSL can be captured while having all the syntactic possibilities of an external DSL at hand. In fact the implementations of commands for the external DSL and internal DSL are almost identical in this approach. This means no additional effort for the implementation of the commands. The best thing is that during the start phase when you work with three or four commands, these can be modified without the need to change the grammar of the external DSL.

When automating test cases I always have a look at the manual test cases first. Manual test cases should be described in a way that a relatively inexperienced tester, who has read the application user guide, can execute the test case and verify the results. I think DSLs for test automation usually start small with about three to five implemented commands. These commands are used to formulate a couple of test cases. I have to adjust the commands a couple of times during this process until they perfectly fit my needs. The language grows step by step along the project. During maintenance of the test suite it will often be necessary to adjust the implementation of the commands due to changes in the GUI of the system under test.

The following sample shows a test case formulated in the external DSL. The test case is formulated in natural language which allows us to involve domain experts in the test automation project. The test case describes two different scenarios for location search. The first scenario is the search in the View tab which does not require the user to log in. The second scenario tests the location search in the Edit tab which requires a login.

Story Search Location uses osm_dsl

Scenario Search Location in View tab:
    browse http://www.openstreetmap.org
    search Von-Gumppenberg-Strasse, Schmiechen
    pan right
    zoom in

Scenario Search Location in Edit tab:
    browse http://www.openstreetmap.org
    click Edit
    login user mark identified by test1234
    search Von-Gumppenberg-Strasse, Schmiechen
    pan right
    zoom in
    logout

Next we will take a look at the Selenium tools. For the recording of the raw tests I use the Selenium IDE. The Selenium IDE is a Firefox plugin used to record clicks on a webpage, to add wait conditions, and verification steps. We export a captured test case as a Python script. We can use this script later as a basis for the implementation of the DSL commands. We use another tool, the Selenium Server, for execution of the test cases. The Selenium Server provides the test profile for the web browser, starts and ends the web browser, and handles all the communication of our test suite with the web browser. I cover the usage of Selenium in more detail in an article “Functional testing of web applications with Selenium” on my website [5].

The following table shows a Selenium test case with all the technical details like wait conditions and verification steps.

Selenium test case with all wait conditions

In Selenium IDE it is possible to export the recorded test case into a Python script which looks like the following:

from selenium import selenium
import unittest, time, re

class NewOsmTest(unittest.TestCase):
    def setUp(self):
        self.verificationErrors = []
        self.selenium = selenium("localhost", 4444, "*chrome",
            "http://www.openstreetmap.org/")
        self.selenium.start()

    def test_new_osm_test_case(self):
        sel = self.selenium
        sel.open("/")
        sel.type("query", "Von-Gumppenberg-Strasse, Schmiechen")
        sel.click("commit")
        for i in range(60):
            try:
                if sel.is_element_present("permalinkanchor"): break
            except: pass
            time.sleep(1)
        else: self.fail("time out")
        sel.click("OpenLayers.Control.PanZoomBar_6_panright_innerImage")
        sel.click("OpenLayers.Control.PanZoomBar_6_zoomin_innerImage")
        try: self.failUnless(sel.is_element_present("loginanchor"))
        except AssertionError, e: self.verificationErrors.append(str(e))
        sel.click("loginanchor")
        for i in range(60):
            try:
                if sel.is_element_present("//form[@action='/login']"):
                    break
            except: pass
            time.sleep(1)
        else: self.fail("time out")
        try: self.failUnless(sel.is_element_present("loginanchor"))
        except AssertionError, e: self.verificationErrors.append(str(e))
        try: self.failUnless(sel.is_element_present("user_email"))
        except AssertionError, e: self.verificationErrors.append(str(e))
        try: self.failUnless(sel.is_element_present("user_password"))
        except AssertionError, e: self.verificationErrors.append(str(e))
        sel.type("user_email", "markfink")
        sel.type("user_password", "test1234")
        try: self.failUnless(
            sel.is_element_present("//form[@action='/login']"))
        except AssertionError, e: self.verificationErrors.append(str(e))
        sel.click("commit")
        for i in range(60):
            try:
                if sel.is_element_present("link=markfink"): break
            except: pass
            time.sleep(1)
        else: self.fail("time out")
        try: self.failUnless(sel.is_element_present("link=markfink"))
        except AssertionError, e: self.verificationErrors.append(str(e))
        sel.click("editanchor")
        sel.click("logoutanchor")

    def tearDown(self):
        self.selenium.stop()
        self.assertEqual([], self.verificationErrors)

if __name__ == "__main__":
    unittest.main()

It is relatively easy to identify which parts of the script implement certain aspects (search, zoom, login, …) of the test case. It also takes a little while to get used to reading the captured test cases. After a while you will slice the scripts into reusable commands in no time. Sometimes test cases that worked before, or which worked for another browser, will fail. This happens due to the asynchronous behavior of AJAX implementations. If this happens you must simply add another wait condition. It is better to add a waitForElement condition than a sleep() for a fixed time interval. Due to the fact that some browsers execute Javascript faster than others you must use waitFor conditions for synchronization to occur properly. After you slice the script into parts and form them into DSL commands, take some time to add additional verification steps. The DSL commands will be reused and you do not need to rework the verification steps of a command later if was done correctly the first time.

After some tweaking of the implementation of your external DSL commands would look something like that:

from selenium import selenium

@dsl('search (\w*)')
def search(query):
    sel.open("/")
    sel.type("query", query)
    sel.click("commit")
    for i in range(60):
        try:
            if sel.is_element_present("permalinkanchor"): break
        except: pass
        time.sleep(1)
    else: fail("time out")

@dsl('zoom in (\w*)')
def zoom_in(amount=1):
    for i in range(amount):
        sel.click("OpenLayers.Control.PanZoomBar_6_zoomin_innerImage")

@dsl('pan right (\w*)')
def pan_right(amount=1):
    for i in range(amount):
        sel.click("OpenLayers.Control.PanZoomBar_6_panright_innerImage")

@dsl('login user (\w+) identified by (\w+)')
def login(user_email, user_password):
    try: failUnless(sel.is_element_present("loginanchor"))
    except AssertionError, e: self.verificationErrors.append(str(e))
    sel.click("loginanchor")
    for i in range(60):
        try:
            if sel.is_element_present("//form[@action='/login']"): break
        except: pass
        time.sleep(1)
    else: fail("time out")
    try: failUnless(sel.is_element_present("loginanchor"))
    except AssertionError, e: verificationErrors.append(str(e))
    try: failUnless(sel.is_element_present("user_email"))
    except AssertionError, e: verificationErrors.append(str(e))
    try: failUnless(sel.is_element_present("user_password"))
    except AssertionError, e: verificationErrors.append(str(e))
    sel.type("user_email", user_email)
    sel.type("user_password", user_password)
    try: failUnless(sel.is_element_present("//form[@action='/login']"))
    except AssertionError, e: verificationErrors.append(str(e))
    sel.click("commit")
    for i in range(60):
        try:
            if sel.is_element_present("link="+user_email): break
        except: pass
        time.sleep(1)
    else: fail("time out")
    try: failUnless(sel.is_element_present("link="+user_email))
    except AssertionError, e: verificationErrors.append(str(e))
...

Note that the functions in the above source code block which form the DSL commands are modified by the @dsl decorator. The @dsl decorator links a regular expression statement to the function signature. This regular expression is used during execution of a test case scenario to identify which DSL command will be executed. The grammar an other infrastructure of the functional testing DSL is implemented as a Python nose unit test plugin (open source) [6].

I think this is really worth the effort. With a little bit of additional coding it is possible to break up the capture and replay tests into reusable DSL commands that can be easily reused to extend the test suite with more new test cases. The amount of work to fix timing issues and the time for writing additional verification steps pays back when you formulate new test cases based on these commands.

Now we have finished the implementation of our openstreetmap test case as an external DSL. The test case in this form is much more readable than the original Selenium test case (see the test case in the tabular form above). I would go so far as to say that domain experts, after a minimal amount of training, will be able to review test cases in this format. After a little practice, domain experts will probably be able to sketch new test cases that can than be automated incredibly quickly. Of course this depends heavily on the working environment. I would guess in a start-up-company environment domain experts would be motivated to support writing test cases in this way because of the high quality of the resulting software product.

Conclusions and further work

The only drawback of this approach is that special knowledge is necessary to model the DSL. It requires you to do some analysis of your application domain. A Feature Diagram can help you with this. Read the documentation on incidents, tickets, bug reports, or whatever they are named in your organization in order to learn the language of your business. Talk to business analysts and read the description of manual test cases – if they exist – in the area of your application domain. Start small and iterate. This means literally select just enough DSL commands to implement one test case. Implement another test case with the same commands and add additional commands as you go along. Do not hesitate to change the language to make it as human readable as possible. Collect early feedback from your domain experts. Avoid the capture and replay of test cases as much as possible. They are not easy to maintain and collecting too many of them will render your test suite useless. Instead break up the captured tests into reusable DSL commands.

If you know of a open source Python application that needs functional testing please let me know. I would be interested in executing functional test automation in a community project.


[1] Krzysztof Czarnecki, Ulrich W. Eisenecker, 2000. Generative Programming – Methods, Tools, and Applications. Addison-Wesley.

[2] Z. R. Dai, 2006. An Approach to Model-Driven Testing – Functional and Real-Time Testing with UML 2.0, U2TP and TTCN-3. Fraunhofer Publications

[3] Arie van Deursen, Paul Klint, 2001. Domain-Specific Language Design Requires Feature Descriptions. Journal of Computing and Information Technology.

[4] Eric Evans, 2004. Domain-Driven Design – Tackling Complexity in the Heart of Software. Addison-Wesley.

[5] Mark Fink, 2008. Functional testing of web applications with Selenium. http://www.testing-software.org/cast/Webapp-Testing/Selenium.html.

[6] Mark Fink, 2009. tdsl – A Functional Testing DSL. http://bitbucket.org/markfink/tdsl/.

[7] Fitnesse acceptance testing framework. http://fitnesse.org/.

[8] Martin Fowler, 2008. Method Chaining. http://www.martinfowler.com/dslwip/.

[9] Dan North, 2006. Introducing BDD, http://dannorth.net/introducing-bdd/.

Functional testing of web applications with Selenium

July 16th, 2011

Many tools for automated web testing are protocol drivers. These tools drive the tests through the HTTP protocol. This is in many cases suitable to test the server side of the application. For applications which heavily use Javascript this approach can not be taken. In the extreme as we see it with current AJAX applications today the html page is only loaded once by the web client and after that all communication with the server is handled by Javascript completely. On top of that it is often required that those application supports multiple browsers. Because today browser implementations still handle Javascript and the DOM differently a protocol driver is relatively useless for testing and a application driver is needed.

Selenium is a test tool which drives the test of a web application in the web browser. Selenium supports various browsers (IE, Firefox, Safari and most other browsers) on various platforms. The open source initiative around Selenium was started in 2004 by Jason Huggins.

Selenium is the type of tool you need in order to test the application in the browser in the way manual testers would. JavaScript and the DOM are covered by your Selenium tests.

Selenium Core

Selenium Core is at heart a set of JavaScript code that executes tests which are contained in a HTMl table. Those tests life within the browser. The browser sees a test page which contains and drives your application. This is a simple concept but it is very effective and portable (to all the platforms mentioned above) at the same time.

Selenium IDE

Writing web application tests by hand without tool support is exhausting and almost impossible at lager scale. The tooling support you need is Selenium IDE. Selenium IDE is a Firefox extension for recording tests. It records all user interaction while browsing your application and you just need to add your verification steps while doing so.

The complete list of available selenium commands is available at Selenium Core TestRunner reference page:

http://www.openqa.org/selenium-core/testrunner.html

The following screenshot shows the Selenium IDE in action. A test case has been recorded. To resolve timing issues with execution of the JavaScript I had to insert a waitForElementPresent condition.

Record a test with Selenium IDE

Selenium IDE saves the testcase into a html table:

HTML table contains test case

It is really just a plain HTML file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head profile="http://selenium-ide.openqa.org/profiles/test-case">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="selenium.base" href="" />
<title>openstreetmap</title>
</head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><td rowspan="1" colspan="3">openstreetmap</td></tr>
</thead><tbody>
<tr>
  <td>open</td>
  <td>/</td>
  <td></td>
</tr>
<tr>
  <td>click</td>
  <td>commit</td>
  <td></td>
</tr>
<tr>
  <td>type</td>
  <td>query</td>
  <td>Schmiechen</td>
</tr>
<tr>
  <td>click</td>
  <td>commit</td>
  <td></td>
</tr>
<tr>
  <td>waitForElementPresent</td>
  <td>link=Schmiechen, Bayern</td>
  <td></td>
</tr>
<tr>
  <td>click</td>
  <td>link=Schmiechen, Bayern</td>
  <td></td>
</tr>
<tr>
  <td>click</td>
  <td>link=Close</td>
  <td></td>
</tr>
</tbody></table>
</body>
</html>

In Selenium IDE select “File|Export Test Case as…|Python – Selenium RC” to export the test case to Python. You get the following Python script:

from selenium import selenium
import unittest, time, re

class NewTest(unittest.TestCase):
    def setUp(self):
        self.verificationErrors = []
        self.selenium = selenium("localhost", 4444, "*firefox", "http://www.openstreetmap.org/")
        self.selenium.start()

    def test_new(self):
        sel = self.selenium
        sel.open("/")
        sel.click("query")
        sel.type("query", "Schmiechen")
        sel.click("commit")
        for i in range(60):
            try:
                if sel.is_element_present("link=Schmiechen, Bayern"): break
            except: pass
            time.sleep(1)
        else: self.fail("time out")
        sel.click("link=Schmiechen, Bayern")
        sel.click("link=Close")

    def tearDown(self):
        self.selenium.stop()
        self.assertEqual([], self.verificationErrors)

if __name__ == "__main__":
    unittest.main()

Selenium Remote Control (RC)

The Selenium RC provides a proxy server in order to treat the web application in a way that the browser thinks that the Selenium Core JavaScript files have the same origin as the web application. As a consequence the “same origin policy” of the browser is not violated.

Run the server part of Selenium RC with “java -jar selenium-server.jar”. This is often also called Selenium Server.

Now we can easily execute the test case:

Execute the test case

It takes additional overhead (~25 sec) to prepare the profile and launch the browser.

Selenium Grid

Selenium Grid is a platform to support parallel execution of test cases. The grid consists of multiple machines each running its own Selenium Server. Through parallel execution the completion time of automated web application testing can be drastically reduced.

You will find more information on Selenium Grid and “Web Testing That Doesn’t Take Hours!”:

http://selenium-grid.openqa.org/

Installation

Selenium IDE is a Firefox plugin. Open Firefox and select “Tools|add-ons|Get Add-ons”. Search for Selenium IDE and select “Add to Firefox”.

Everything else you usually need is in Selenium RC. Get the latest snapshot from:

http://selenium-rc.openqa.org/download.html

At time of writing the latest snapshot resolved some issues with Firefox 3 on Ubuntu and OS X. Unpack the “selenium-python-client-driver” somewhere on your PYTHONPATH. You also need the selenium-server.jar to run the server part of Selenium RC.

Working with legacy code

July 16th, 2011

When working in QA or Test Automation you are much more likely to be confronted with a legacy application with hundreds of thousands of code lines with missing documentation and test cases than finding well documented one with high test coverage and beautiful code. In many cases the legacy code has to be re-factored to improve testability. Therefore one critical skill is to be able to work with legacy code.

When I start reading source code I start from a birds eye perspective. I first want to know how big the project is I am looking at.

Loc comes in handy in this situation to get a first general impression about the scale of the code base.

Measuring Lines Of Code (LOC)

sudo apt-get install sloccount
cd sloccount openerp-server-5.0.0_rc3

SLOC        Directory       SLOC-by-Language (Sorted)
58241   addons-extra    python=50644,php=7434,sh=163
53168   addons          python=53126,php=42
22288   server          python=22287,sh=1
15599   client          python=15599
12334   web             python=12205,sh=129
72      top_dir         python=72

Totals grouped by language (dominant language first):
python:      153933 (95.20%)
php:           7476 (4.62%)
sh:             293 (0.18%)

Measuring complexity (McCabe)

Another very useful code metric in the situation described above is the McCabe complexity metric. This tool helps you to identify the most complex code. The complex areas need the most attention when it comes to quality assurance measures (documentation, testing, etc.). These areas usually contain the most defects, too.

#manually extract tarball
PyMetrics.py program.py

Understanding the code structure using Callgraphs

Almost every time I am approaching a code base unknown to me I am looking for a certain functionality which I am particularly interested in. As soon as I understand the functionality I go to the next one and so on until I understand everything I need to. To identify relevant parts in the code a call graph proved to be very handy. The graph “lists” all the relevant modules and functions and shows in which order they are called. I always use the call graph as a “map” that helps me to navigate the code base unknown to me.

Installation of the pycallgraph tool (in Ubuntu)

apt-get install pycallgraph

Instead of starting your program with your python interpreter you just use pycallgraph to execute your script. For example run the scotch recording proxy within the pycallgraph tool:

pycallgraph run-recording-proxy -i scotch.* -e *.*

Call graphs get big very fast because the program usually calls a lot of library functions. For that reason I excluded everything except the scotch module from the diagram (-i and -e options), which I included.

Pycallgraph of the scotch recording proxy

Printing the callgraph

Callgraphs are usually big especially if you are working with a non trivial module. To be able to print those callgraphs in case you do not have a plotter the program Dia (Diagrams, UML, etc.) comes in very handy. To install it on a Ubuntu box just type:

sudo apt-get install dia

Dia helps you to split huge visualizations graphics into multiple pages that you then print separately.