Tuesday, June 28, 2016

Test Automation Architecture


Following three repos have been used to facilitate test automation for WSO2 products. Each component will describe in this post later.
  1. Test Automation Framework (git repo link - carbon-platform-integration)
  2. Carbon Automation Test Utils (git repo link - carbon-platform-integration-utils)
  3. Collection of all test suites and a test runner (git repo link - carbon-platform-automated-test-suite)

Test Automation Framework components

Architecture - AutomationFramework (2).png

Components related to TestNG is marked in dark-red colour

Automation framework engine

  • Automation Context builder - Process automation.xml given though test module and make all configurations available through Automation Context.

  • TestNG extension Executor -  Responsible for execution of internal/external extension classes in various states of the TestNG listeners.

Pluggable utilities to the test execution (TestNG)

There are several interfaces that allow you to modify TestNG's behaviour. These interfaces are broadly called "TestNG Listeners".  Test Automation Framework supports the execution of internal/external extension classes in various states of the TestNG listeners. Users can define the class paths in the automation.xml file under the desired TestNG listener states
Automation Framework uses Java reflection to execute classes. Users are expected to use the interface provided by the framework to develop external extension classes to be executed in the test. There are 5 interfaces provided by TAF for different TestNG listeners. Those interfaces have specific methods defined explicitly to be inline with the corresponding TestNG listeners. The interfaces are:
  • ExecutionListenerExtension
  • ReportListenerExtension
  • SuiteListenerExtension
  • TestListenerExtension
  • TransformListenerExtension

Automation Framework Common Extensions (Java)

This module consists of a set of default pluggable modules common to the platform. These pluggable modules can be executed in the test execution registering those in the test configuration file (automation.xml). TAF provides common modules such as the
  • CarbonServerExtension -  For server startup, shutdown operations of carbon server. And coverage    generation is also a part of this class.  
  • Axis2ServerExtension -  For axis2 server startup and shutdown. Facilitate backend for integration tests.
Also extensions module contain classes facilitate third party framework integration
  • SeleniumBrowserManager - Creates Selenium WebDriver instance based on the given browser configuration at automation.xml
  • JmeterTestRunner - Executes jmeter test scripts in headless mode and inject result to surefire test result reports.
Users can also add any platform wide common modules into the Automation Framework Extensions. Test specific pluggable modules should be kept in the test module side and those modules can also be used for the tests by registering it in the automation configuration file.

Automation Framework Utils (Java)

Utility components that provide frequently used functionality, such as sending SOAP and REST requests to a known endpoint or monitoring a port to determine whether it's available. You can add the Utils module to your test case to get the functionality you need. Some sample utilities are:
  • TCPMon Listener
  • FTP Server Manager
  • SFTP Server Manager
  • SimpleHTTPServer
  • ActiveMQ Server
  • Axis2 clients
  • JsonClients
  • MutualSSLClient
  • HTTPClient
  • HTTPURLConnectionClient
  • Wire message monitor
  • Proxy Server
  • Tomcat Server
  • Simple Web Server (To facilitate content type testing)
  • FileReader, XMLFileReader
  • WireMonitor
  • Concurrent request Generator
  • Database Manager (DAO)

Test Framework Unit Tests - Unit test classes to verify context builder and utility classes, depends on TestNG for unit testing.

Carbon Automation Test Utils


Carbon Platform Utils -  AutomationFramework.png


Common Framework Tests


There are integration test scenarios common to all WSO2 products. Implementing tests for those common scenarios in each product might introduce test duplication and test management difficulties. Thus, a set of common tests are introduced under the framework utilities module which can be used directly by extending the test classes.
e.g
  1. DistributionValidationTest
  2. JaggeryServerTest
  3. OSGIServerBundleStatusTest
  4. ServerStartupBaseTest

Common Admin Clients


This module consists of set of admin clients to invoke backend admin services together with supportive utility methods which are common for WSO2 product platform.
e.g
  • ServerAminClient
  • LogViewerAdminClient
  • UserManagementClient
  • SecurityAdminServiceClient

Common Test Utils


Frequently used test utility classes which depends on platform dependencies.
e.g
  • SecureAxis2ServiceClient
  • TestCoverageReportUtil - (To merge coverage reports)
  • ServerConfiurationManager (To change carbon configuration files)
  • LoginLogoutClient

Common Extensions


Consists of Pluggable classes allowing to plug additional extensions to the test execution flow. Frequently used test framework extension classes which are available by default, these classes use platform dependencies.

e.g UserPopulatorExtension - For user population/deletion operations

Platform Automated Test Suite


Builds a distribution containing all integration and platform test jars released with each product. It contains an ant based test executor to run the test cases in each test jar file. This ant script is based on TestNG ant task which can be used as a test case runner. The ant script contains a mail task which can be configured to send out a notification mail upon test execution. The PATS distribution can be used to run the set of tests on a pre-configured product cluster/distribution.


PATS - AutomationFramework.png

Monday, January 11, 2016

Jacoco Code Coverage Generation with WSO2 Products



JaCoCo is an open source toolkit for measuring and reporting Java code coverage. JaCoCo is distributed under the terms of the Eclipse Public License. It was developed as a a eplacement for EMMA under the umbrella of the EclEmma eclipse project, and is currently the only byte code coverage tool that works with Java 8.

JaCoCo instruments the bytecode while running the code. To do this it runs as a Java agent,and can be configured to store the collected data in a file, or send it via TCP. Files from multiple runs or code parts can be merged easily. The main motivation to move away from EMMA and go with Jacoco due to fact that Jacoco supports for Java 7 and Java 8. 

To enable class instrumentation jacoco agent is attached to carbon server before the server startup. All jar files need to be included for coverage generation is read from instrumentation.txt file which should be available in test module resource directory. And jar file patterns to exclude from instrumentation should be specified in filters.txt in the same resource location. These jar list files will be used in instrumentation and coverage report generation phases.


To introduce Jacoco based code coverage generation to WSO2 products, you can following the following steps below.


  1. Include jacoco agent as a dependency to root pom. And add this dependency into your sub test modules.
        
          Root pom
           
 <dependency> <groupId>org.jacoco</groupId> <artifactId>org.jacoco.agent</artifactId> <version>${jacoco.agent.version}</version> </dependency> <jacoco.agent.version>0.7.4.201502262128</jacoco.agent.version>
          
          Sub test module pom


<dependency> <groupId>org.jacoco</groupId> <artifactId>org.jacoco.agent</artifactId> </dependency>


  1. Edit your test module pom and replace emma copy dependency execution configuration with below (if emma copy dependency is already exists)       
  
<execution>
    <id>copy-jacoco-dependencies</id>
    <phase>compile</phase>
    <goals>
        <goal>copy-dependencies</goal>
    </goals>
    <configuration>
        <outputDirectory>${project.build.directory}/jacoco</outputDirectory>
        <includeTypes>jar</includeTypes>
        <includeArtifactIds>org.jacoco.agent</includeArtifactIds>
    </configuration>
</execution>
   
   
  
  1. Remove following system properties from surefire plugin. If those properties are already exits.


<emma.home>${basedir}/target/emma</emma.home> <emma.output>${basedir}/target/emma</emma.output>
        


  1. Add following system properties to surefire configuration element.  Note that you need to keep instrumentation and filter txt files at src/test/resources location of your test module.           


${basedir}/src/test/resources/instrumentation.txt
${basedir}/src/test/resources/filters.txt


  • Remove emma.properties from your test module if it alredy exists at src/test/resources.


  • Sample instrumentation file is as follows. This will include all jars matching the pattern for on fly bytecode instrumentation and jars matching will be included only for coverage report generation as well.


Note that you need to use same instrumentation and filter files throughout all test modules.
         
org.wso2.carbon.aarservices*
org.wso2.carbon.addressing*
org.wso2.carbon.application.deployer*
org.wso2.carbon.application.deployer.webapp*
org.wso2.carbon.authenticator.proxy*
org.wso2.carbon.bam.service.data.publisher*
org.wso2.carbon.caching*
org.wso2.carbon.jaxws.webapp.deployer*
org.wso2.carbon.redirector.servlet*
org.wso2.carbon.repomanager.axis2*
org.wso2.carbon.service.mgt*
org.wso2.carbon.springservices*
org.wso2.carbon.statistics*
org.wso2.carbon.throttle*
org.wso2.carbon.tomcat*


  • Sample filters.txt file is given below. All jar names matching the file patterns defined in filters.txt file will be ignored from coverage generation. Note that you cannot filter out specific class files from coverage generation. You can only exclude jar files matching the given pattern.


-*.stub*
-*.stub_
-org.eclipse.*
-*.equinox.*
-samples.*
-*.log4j*
-*.axis2*
-*.ui*
-*.tenant*
-*.stratos*
-*.eventing*
-*transports*
-org.wso2.carbon.mediation.statistics*
-*startup*


  • Coverage report will be generated at the end of the test module execution. Coverage data dump into $basedir/target/jacoco/coverage directory at JVM exit. And at the time of report generation, jacoco coverage data files are merged back to a single file (jacoco-data-merge.exec) and report will be generated using this merged data file.


  • You need to enable coverage in automation.xml if it is already disabled.
           
<coverage>true</coverage>


 
  • Carbon server extension class has been moved to carbon-platform-integration repository. Hence you need to use org.wso2.carbon.automation.extensions.servers.carbonserver.CarbonServerExtension class instead of old class. To use the new class edit automation.xml and set the class name as below.


<platformExecutionManager> <extentionClasses> <class> <name>org.wso2.carbon.automation.extensions.servers.carbonserver.CarbonServerExtension</name> <!--<parameter name="portOffSet" value="2" /> <parameter name="cmdArg" value="debug 5005" />--> </class> <class> <name>org.wso2.carbon.integration.common.extensions.usermgt.UserPopulateExtension</name> </class> </extentionClasses> </platformExecutionManager>
                   
            
            
                  org.wso2.carbon.integration.common.extensions.usermgt.UserPopulateExtension
             
         
  • You will find following log entries in console with required details to view the report once coverage generation completed. Available report formats are XML, HTML and CSV.
     
INFO  [org.wso2.carbon.automation.extensions.servers.carbonserver.CarbonServerManager] - Jacoco coverage dump file path : /Users/krishantha/git/wso2/product-as/modules/integration/tests-integration/tests/target/jacoco.exec
INFO  [org.wso2.carbon.automation.extensions.servers.carbonserver.CarbonServerManager] - Jacoco class file path : /Users/xxx/product-as/modules/integration/tests-integration/tests/target/wso2as-6.0.0-NAPSHOT/repository/components/plugins
INFO  [org.wso2.carbon.automation.extensions.servers.carbonserver.CarbonServerManager] - Jacoco coverage report path : /Users/krishantha/git/wso2/product-as/modules/integration/tests-integration/tests/target/jacoco/coverage


Merge Coverage report of multi module test projects (Optional)



To merge coverage reports of multiple modules, you need to introduce new module at the end of all modules execution. This mould should be executed at the end of test module execution. As a convention let's name this modules as “reports”. In the reports module, create a new pom file and add below content. Make sure to modify the parent and pom configurations according to your product group and artifact naming conventions.


Also you need to add instrumentation and filters text files and set the patch correctly in arguments section of exec maven plugin. Note that you need to use same instrumentation and filter files throughout all test modules and report module.


project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <parent>
        <groupId>org.wso2.appserver</groupId>
        <artifactId>test-integration-appSever</artifactId>
        <version>5.3.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>

    <modelVersion>4.0.0</modelVersion>
    <name>WSO2 AppServer - Integration Test Report Module</name>
    <artifactId>org.wso2.appserver.integration.tests.reports</artifactId>
    <packaging>jar</packaging>

    <profiles>
        <profile>
            <id>with-tests</id>
            <activation>
                <activeByDefault>true</activeByDefault>
                <property><name>!maven.test.skip</name></property>
            </activation>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.codehaus.mojo</groupId>
                        <artifactId>exec-maven-plugin</artifactId>
                        <version>1.2.1</version>
                        <executions>
                            <execution>
                                <id>my-exec</id>
                                <phase>install</phase>
                                <goals>
                                    <goal>exec</goal>
                                </goals>
                            </execution>
                        </executions>
                        <configuration>
                            <executable>java</executable>
                            <arguments>
                                <argument>-Dbasedir=${basedir}</argument>
                                <argument>
                                    -Dcarbon.zip=${basedir}/../../../distribution/target/wso2as-${project.version}.zip
                                </argument>
                                <argument>-Dinstr.file=${basedir}/instrumentation.txt</argument>
                                <argument>-Dfilters.file=${basedir}/filters.txt</argument>
                                <argument>-classpath</argument>
                                <classpath/>
                                <argument>
                                    org.wso2.carbon.automation.engine.frameworkutils.TestCoverageGenerator
                                </argument>
                            </arguments>
                            <workingDirectory>${basedir}/target</workingDirectory>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
        <profile>
            <id>without-tests</id>
            <activation>
                <activeByDefault>true</activeByDefault>
                <property><name>maven.test.skip</name></property>
            </activation>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.codehaus.mojo</groupId>
                        <artifactId>exec-maven-plugin</artifactId>
                        <version>1.2.1</version>
                        <executions>
                            <execution>
                                <id>my-exec</id>
                                <phase>install</phase>
                                <goals>
                                    <goal>exec</goal>
                                </goals>
                            </execution>
                        </executions>
                        <configuration>
                            <skip>true</skip>
                            <executable>java</executable>
                            <arguments>
                                <argument>-Dbasedir=${basedir}</argument>
                                <argument>
                                    -Dcarbon.zip=${basedir}/../../../distribution/target/wso2as-${project.version}.zip
                                </argument>
                                <argument>-Dinstr.file=${basedir}/instrumentation.txt</argument>
                                <argument>-Dfilters.file=${basedir}/filters.txt</argument>
                                <argument>-classpath</argument>
                                <classpath/>
                                <argument>
                                    org.wso2.carbon.automation.engine.frameworkutils.TestCoverageGenerator
                                </argument>
                            </arguments>
                            <workingDirectory>${basedir}/target</workingDirectory>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>

    <dependencies>
        <dependency>
            <groupId>org.wso2.carbon.automation</groupId>
            <artifactId>org.wso2.carbon.automation.engine</artifactId>
            <scope>compile</scope>
        </dependency>
    </dependencies>


</project>




The generate coverage report can be found at - $basedir/target/jacoco/coverage/index.html and reports available in XML and CSV formats as well.

Reports module file structure



Reports module should look like below as applying all changes.


Screen Shot 2015-05-13 at 9.01.17 PM.png


Class include and exclude patterns



'*' matches zero or more characters
'?' matches one character.
Examples:
"**\*.class" matches all .class files/dirs in a directory tree.
"test\a??.java" matches all files/dirs which start with an 'a', then two more characters and then ".java", in a directory called test.
"**" matches everything in a directory tree.
"**\test\**\XYZ*" matches all files/dirs which start with "XYZ" and where there is a parent directory called test (e.g. "abc\test\def\ghi\XYZ123").
Case sensitivity may be turned off if necessary. By default, it is turned on.


e.g  **/*AddressingInFaultHandler*.*


Troubleshooting


If you got following exception while generating reports. The cause of the issue would be having different asm dependency in your classpath. This exception means that JaCoCo sees the wrong version of the ASM
library. To avoid the exception, need to exclude ams dependency appropriately. To do that, get the maven dependency:tree and exclude the dependency.


Caused by: java.lang.IncompatibleClassChangeError: class org.jacoco.core.internal.flow.ClassProbesVisitor has interface org.objectweb.asm.ClassVisitor as super class
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:800)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:449)
at java.net.URLClassLoader.access$100(URLClassLoader.java:71)
at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
at org.wso2.carbon.automation.engine.frameworkutils.ReportGenerator.analyzeStructure(ReportGenerator.java:144)
at org.wso2.carbon.automation.engine.frameworkutils.ReportGenerator.create(ReportGenerator.java:77)
at


<exclusion> <groupId>asm</groupId> <artifactId>asm</artifactId> </exclusion>