Developer Guide

This document addresses both test and infrastructure (SystemObject) developers. It begins with issues that are more relevant to test developers and then deals with issues that are relevant to SystemObject developers. However, it is strongly recommended that each developer will read both sections to understand better the relationship between infrastructure and test development.

Background readings:

Writing Tests

General

JSystem is based on JUnit and extends it. Each JSystem test is actually a JUnit test with additional services like the following:

Since JSystem extends JUnit, you should first learn about JUnit. The following is defined under JUnit:

JSystem Vs JUnit (highlights):

Example 1: basic test

01 package com.aqua.examples;
02 
03 import jsystem.extensions.analyzers.text.FindText;
04 import jsystem.framework.fixture.RootFixture;
05 import junit.framework.SystemTestCase;
06 
07 /**
08  * Example 1: basic test
09  */
10 public class MyTest extends SystemTestCase {
11   
12   public MyTest(){
13     setFixture(BasicFixture.class);
14     setTearDownFixture(RootFixture.class);
15   }
16   /**
17    * The SystemObject in this case is a linux 
18    * machine that is managed using cli/telnet.
19    */
20   Linux linux1 = null;
21 
22   public void setUp() throws Exception {
23     linux1 = (Linuxsystem.getSystemObject("linux");
24   }
25 
26   /**
27    * Execute ls command and analyze that the 
28    * string 'myFile' is shown in the results
29    @throws Exception 
30    
31    */
32   public void testLs() throws Exception {
33     report.step("Execute the ls command");
34     linux1.command("ls");
35 
36     report.step("Analyze the command output");
37     linux1.analyze(new FindText("myFile"));
38   }
39 }

What can we learn from the example?

  1. Every JSystem test should extend SystemTestCase (line 10).
  2. The system object the is managed in the example is a linux machine. the setUp method is used to init the object (line 22).
  3. The test is a method that start with the string test in our case 'testLs' (line 32).
  4. We uses the report object to add reports to the tests (lines 33,36). Those reports will be seen in the used reporters. See the reporter chapter for more information.
  5. The test include a SystemObject operation 'linux.command("ls")' (line 34). It will execute the ls command on the linux machine. The string result will be save for analyzing.
  6. The result can be analyze using variety of build-in analyzer (you can also write your own analyzers) (line 37). In this case the analyzer analyzes String input but it can be done with any object type. See the analyzer chapter for more information.

There are 3 types of steps in a test:

  1. reports - test step can generate reports. It's much recommended to use this service to document the progress of the test. It will then help in the analysis of the test execution.
  2. SystemObject operation - test step that manage the SUT (System Under Test). It can change it configuration or get information from it. Every SystemObject operation can build a result object for further analyzing. In Example 1, the result object is the cli return string.
  3. Analyzing - points in the test were the execution result being analyzed and compared with expected results. The analyzing is part of the SystemObject.

General guidelines

System and Sut objects

Most of the services given to a tests can be access using public members in the SystemTestCase object. One of those members/services is the SystemManager. The member name is 'system'. You can see a usage of the system service in example 1 line 23.

linux1 = (Linux) system.getSystemObject("linux");

The system service is responsible for few tasks:

Construct and initialize the SystemObjects

Every SUT can be composed from set of SystemObjects. One of the framework requirements is that test that was written for one SUT will be executed on different SUT without changing the test code. All the setup specific information should be placed in the SUT files. The SUT files are XML files that describe the SUT. Every SUT should have its SUT file.

In our case the SUT file should looks something like this:

<sut>
    <linux>
        <class>com.aqua.examples.Linux</class>
        <host>127.0.0.1</host>

        <username>root</username>
        <password>root</password>
    </linux>
</sut>

The SystemManagerImpl uses java reflection to create the linux object. If the Linux object defined setter method (a method that start with the set string) like setHost, it will be called with the XML tag value (in this case '127.0.0.1').

Object repository

The system service is a repository for SystemObjects. By default the SystemObject will be init only once by the first test that require the object. The second test will get it from the object repository. The SystemObject can be configured to TEST_LIFETIME and then it will be construct and init every test.

Object close

The system service responsible of closing the SystemObject. It register a shutdown hoke that will be executed on system exit.

SUT service

The 'sut' service is used to directly access the SUT file. The SUT file is an XML file. To access it we use the XPath techenology.

Following is an example for using the the 'sut' service:

     String password = sut.getValue("/sut/linux/password/text()")

If run on the above XML the password will be 'root'.

The SUT files should be located in the sut directory under the tests root directory. They should be managed as part of the tests code and be copied to the classes/sut directory as part of the tests build process (Eclipse do it automatically). The SUT file name to be used is saved in the jsystem.properties file (sutFile=xml4Test.xml). This file holds all the JSystem execution information. For more details about the jsystem.properties.

Reporting

Reporting is importuned for the scalability of the automation project. The reports should enable to analyze quickly the cause for test execution failure. It's recommended that before you start your automation project to have reporting convention that will be used in all your project tests.

Example 1 show the usage of the report service in line 33.

report.step("Execute the ls command");

report in general have few fields:

  1. title - the title of the report.
  2. message - the message of the report, will be seen in the html reporter as link from the title.
  3. status - the status of the report. The status have 3 states: PASS, FAIL and WARNING. In some of the report method the status is a boolean and can get true for PASS and false for FAIL.
  4. bold - if true the report will be marked as bold.

If report with fail status is submitted, the test will fail (even if no exception will be thrown).

Report step will be seen as bold report in the html reporter. In addition all the steps are collected and are part of SystemTestCase property, it can be seen in general reports and statistics.

Documentation

JSystem uses your test documentation in it's reports. Both the class documentation (lines 07-09) and the test documentation (lines 26-31) are taken and become part of the SystemTestCase properties and will be shown in the JSystem runner the html or other reporters. The documentation should be in Javadoc format.

Analyzers

Analyzers gives easy way to verify SystemObjects operations. The SystemObject operations are used to manage/get information from the SUT. Every such operation can take the result object (in the telnet example it will be a String object) and defined it as the object to be analyzed (using the setTestAgainstObject method). By doing so the analyzers can be used to analyze the object and compare it with some expected result. In example 1 lines 34, 37:

linux.command("ls");
linux.analyze(new FindText("myFile"));

we first execute telnet command (ls) that the output is taken and set as the test against object (it is done internally by the linux SystemObject), then we uses FindText to verify that the string 'myFile' is found in the result.

The analyzing process is responsable for generating success report if the analyze success or fail report if the analyzer fail. In case of a failure an exception will be thrown.

The analyzer interface define additional method that can be used:

analyze(AnalyzerParameter parameter, boolean silent, boolean throwException)

When silent is true the report will be seen only in case of a failure. When throwException is false the analyzing process will not throw exception in case of analyzer failer. isAnalyzeSuccess(AnalyzerParameter parameter) can be used to check if the analyze success. No report will be post and no exception will be thrown in case of failure.

JSystem comes with a variety of analyzers there are analyzers to analyze text, XML document and more. Analyzers are extendable and you can write your own analyzers.

Example 2: FindText analyzer

01 package jsystem.extensions.analyzers.text;
02 
03 import jsystem.framework.analyzer.AnalyzerParameterImpl;
04 
05 /**
06  * Find a text in a string. Support regular expressions.
07  
08  @author Guy Arieli
09  */
10 public class FindText extends AnalyzerParameterImpl {
11   String toFind;
12     /**
13      
14      @param toFind The string to find.
15      */
16     public FindText(String toFind){
17       this.toFind = toFind;
18     }
19 
20     public void analyze() {
21       String testText = (String)testAgainst;
22         String found = toFind;
23         status = (testText.indexOf(toFind>= 0);
24         if (status){
25             title = "The text:<" + toFind + "> was found";
26         else {
27             title = "The text: <" + toFind + "> wasn't found";
28         }
29         message =testText.replaceAll(toFind,"<b>" + found + "</b>");
30     }
31     
32     public Class getTestAgainstType(){
33       return String.class;
34     }
35 }

The FindText analyzer example show you how an analyzer can be implemented.

What can you learn from the example?

  1. Analyzers should extends AnalyzerParameterImpl (line 10).
  2. Analyzers should implement analyze method (line 20).
  3. The analyzer will work on the testAgains object. This object is set to the analyzer by the framework (line 21).
  4. The analyze method should set the value of the title (line 25). It will be used in the generate report.
  5. The analyze method should set the value of the message (line 29).
  6. And the status (line 23). The status will indicate the analyze result (true for success, false for fail).
  7. analyzer can implement getTestAgainstType method that return the type of object that is expected by the analyzer. In our case String (line 32).

Fixtures

Fixture represents a state of the SUT (System Under Test). Fixtures dill with the question, what can test assumes on the environment (SUT) it's executed on. Tests can be very easily executed independently. But when you start to group tests, it become complex. Test starts to fail because other tests change the SUT. You can find 2 traditional approaches:

  1. A test can't assume anything about the environment it's executed on. The test then first has to clean the SUT then create the required configurations and then execute the test.
  2. A test can assume that the previous test left the SUT in a known position.

In general the traditional approaches fails in few issues:

  1. You get longer tests execution time.
  2. It creates restrictions for tests execution order.
  3. Tests can’t be executed independently and need other test to be executed before they do.
  4. When one test fails it can affect the other tests. It doesn’t enable a reasonable recovery mechanism. So usually when one of the tests in the tests chain fails other tests will follow.
  5. You have more code and more code repetitions.

In the fixture approach the configuration (or setupping) of a test (or group of tests) is separated from the test itself. The framework is aware of the current configuration status of the SUT and will execute fixture change only if the next test to be executed requires deferent configuration then the current.

JSystem fixture module enables you to create a hierarchy (a tree) of fixtures.

Example 3: BasicFixture

01 package com.aqua.examples;
02 
03 import jsystem.framework.fixture.Fixture;
04 
05 public class BasicFixture extends Fixture {
06   Linux linux1 = null;
07 
08   public void setUp() {
09     linux1 = (Linuxsystem.getSystemObject("linux");
10     
11     linux1.command("cd /home/mydir");
12   }
13   
14   public void tearDown(){
15     linux1.command("cd /");
16   }
17   
18   public void failTearDown(){
19     linux1.reboot();
20     tearDown();
21   }
22 
23 }

To assign a fixture to test you should you should use setFixture and setTearDownFixture in the test constractor.

public MyTest(){
   setFixture(BasicFixture.class);
   setTearDownFixture(RootFixture.class);
}

What can you learn from the Fixture example?

  1. Fixture is a class that extend the Fixture class (line 05).
  2. In the fixture you can implement 3 method setUp, tearDown and failTearDown.
  3. The fixture can use all the services that available for SystemTestCase, like 'system' (line 09).
  4. To assign a fixture to a test you should use the setFixture in the test constractor.
  5. You can also use the setTearDownFixture. When using this option, in case of test execution fail the framework will navigate to the tear down fixture befor executing the next test. The tear-down will be done in the failTearDown pass.
  6. In the failTearDown you should use more agrisive ways to insure that the next test will be executed with no leftovers (lines 19-20).

Parameterization

JSystem support parameterization for tests. The test writer can define a set of parameters that can be changed by the test executor using the JSystem runner. It will enable the test executor to build a scenario were the same test run several times, each time with deferent parameters.

Example 4: parameters

01 package com.aqua.examples;
02 
03 import junit.framework.SystemTestCase;
04 
05 public class ParametersExampleTest extends SystemTestCase {
06     int packetSize = 64;
07     
08     // this is the actual test
09     public void testTrafficWithPacketSize (){
10         // the test code using the packetSize parameter
11     }
12     /**
13      * Set the test packet size
14      */
15     public void setPacketSize(int  size){
16         packetSize = size;
17      }
18 
19     // this is optional, if set will show the user 64 
20     // as the default packet size
21     
22      public int getPacketSize(){
23         return  packetSize;
24     }
25     // this is optional, if set will let the user to select 
26     // only from the list given.
27      
28     public int[] getPacketSizeOptions (){
29         return new int[] { 64 2565121024,  1518};
30     }
31 }

What can you learn from the Parameters example?

  1. Parameters can be added to any JSystem (SystemTestCase) or JUnit (TestCase) test case (line 05).
  2. Usually you will add a member to the test and create a setter and a getter methods (member line 06, setter line 15 and getter line 22).
  3. The parameterization feature support vereuse types of objects. In the example we can see a usge of int, you can use String, long, float, double and boolean as well.
  4. You can force the test executor to select from a preset of parameters by implementing getParamnameOptions. This method should return an array of optional values (line 28).
  5. The documentation that is written to the set method will be seen at the runner (lines 12-14).

When using the JSystem runner the test parameters are saved as part of the scenario.

Monitors

Monitors are processes (threads) that can be defined per test. The monitor will run during the test execution in deferent thread. The monitor can fail the test if it find a problem with the monitored object.

Example 5: monitor

01 package com.aqua.examples;
02 
03 import jsystem.framework.monitor.Monitor;
04 import jsystem.utils.MiscUtils;
05 
06 public class PingMonitor extends Monitor {
07   String host;
08   public PingMonitor(String host) {
09     super("PingMonitor_" + host);
10     this.host = host;
11   }
12 
13   public void run() {
14     while(true){
15       if(!MiscUtils.isPing(host)){
16         report.report("Fail to ping to host: " + host, false);
17         setFail(true);
18       }
19       try {
20         Thread.sleep(5000);
21       catch (InterruptedException ex){
22         return;
23       }
24     }
25   }
26 }

The monitor is activating during the test. To activate a monitor you should use the 'monitors' service.

   monitors.startMonitor(new PingMonitor(linux.getHost()));

What can you learn from the Monitor example?

  1. Monitor should extends the Monitor class (line 06).
  2. As it implement the Java Runnable the Monitor should implement a run method (line 13).
  3. Monitor can use JSystem services like 'report' and 'system' (line 16).
  4. Monitor can fail the test by submitting a fail report (line 16).
  5. On interrupt the monitor should exit (line 22).

SystemObject development

SystemObjects are the objects that should be managed as part of the SUT (SystemUnderTest). It can be a Linux server that is managed using Telnet, it can be network equipment that managed using SNMP or SSH or it can be hardware testing device that is manged using GPIB or TCL. The number of object to manage and there complexity is defined by your testing needs. The way to connect to the required objects is changing from object to object:

The SystemObject you are developing should hide the connectivity issues and expose the business logic or the functionality of the object. The SystemObject is a midel-layer that should do the following:

  1. Hide the complexity of the object connectivity.
  2. Expose an API that 'speak' in the languge of the QA engineer.
  3. Take care of reporting.
  4. And error handeling (throw exception in case of faluir).
  5. Support result analysis.

When you designing SystemObject I found it very usefull to start from writing a test example that will use the SystemObject. The big question should be how should the tests looks like.

Example 6: SystemObject

01 package com.aqua.examples;
02 
03 import jsystem.framework.system.SystemObjectImpl;
04 
05 public class Linux extends SystemObjectImpl {
06   Cli cli;
07   String host;
08   String username;
09   String password;
10   
11   public void init() throws Exception{
12     super.init();
13     cli = new Cli(host, username, password);
14   }
15   public void command(String commandthrows Exception{
16     String result = null;
17     try {
18       result = cli.command(command);
19       report.report(getName() " command: " + command,
20           result,
21           true);
22       setTestAgainsObject(result);
23     catch (Exception e){
24       report.report(getName() " command fail", e);
25       throw e;
26     }
27   }
28   public void close(){
29     cli.close();
30   }
31   public String getHost() {
32     return host;
33   }
34   public void setHost(String host) {
35     this.host = host;
36   }
37   public String getPassword() {
38     return password;
39   }
40   public void setPassword(String password) {
41     this.password = password;
42   }
43   public String getUsername() {
44     return username;
45   }
46   public void setUsername(String username) {
47     this.username = username;
48   }
49 }

What can we learn from the SystemObject example

  1. Your SystemObject should extends SystemObjectImpl (line 05).
  2. A SystemObject should have a default constractor with no argument (as the framework constract it using java reflection).
  3. SystemObject can implement init and close methods, init will be called after the object constraction, and close on object close (lines 11, 28).
  4. If a set method is defined in the object and a tag is found in the SUT XML, the set method will be called with the tag value. For example you can find the setHost (line 34) method and in the XML you can find a host tag. The value of the host tag (in this case '127.0.0.1') will be passed to the setHost method. It is all done as part of the super.init() (line 12).
  5. The command method is the SystemObject operation. You can find the execution of the command (line 18). The report in case of success (line 19). The setTestAgainstObject is taking the command result and set it as the object to analyze (line 22). In case of execution failer there is a fail report (line 24) and exception throw (line 25).

Lifetime

One of the SystemObject attribures is its lifetime. There are 2 option in this case:

  1. the default is PERMANENT_LIFETIME - on the first request for the object it's created, the next test that will ask for it will get the old object. All the test in the scenario/suite will use the same object.
  2. TEST_LIFETIME - after the test execution the object is close. The next test will get new constacted object.

Hirarchy

You can create and hirarchy of SystemObject by creating a public member of SystemObject inside a parent SystemObject. In the SUT XML you should create an internal tag that will represent the internal object. You can use the getParrent method in the child object to get its parent.

Properties

Every SystemObject tag in the SUT XML (even once without set method) can be access using the getProperty method. The parameter is the tag name.