Automation QA Testing Course Content

Three Different Ways on Cucumber Runner

 

Three Different Ways on Cucumber Runner



I have been working on Web automation projects. Generally, I prefer to use Cucumber due to providing behavior-driven development.
Every project needs a different approach for execution. We need a runner class for executing feature files, but which test framework is better to use with Cucumber? I have three different options, two with TestNG and one with JUnit. You can prefer as your project needs.
I’m listing from the simplest to the most complex ones.

  1. Cucumber Runner with JUnit
    The first example is being created by Cucumber-JUnit and JUnit dependencies. I had been using it for an API project because I didn’t need TestNG annotation to perform it.
package myTestRunners;
import io.cucumber.junit.Cucumber;
import io.cucumber.testng.CucumberOptions;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
@RunWith(Cucumber.class)
@CucumberOptions(
features = "src/test/resources/functionalTests",
glue= {"myStepDefinitions" , "myHooks"},
tags = "@chrome",
plugin = { "com.aventstack.extentreports.cucumber.adapter.ExtentCucumberAdapter:",
"timeline:test-output-thread/",
"rerun:src/test/resources/failedrerun.txt"},
monochrome = true,
publish = true
)
public class TestRunnerWithJunit {
@BeforeClass
void beforeClass() {
}
@AfterClass
void afterClass() {
}
} ============================================================================= 2) Cucumber Runner with TestNG (AbstractTestNGCucumberTests)
This example is being created by Cucumber-TestNG and TestNG dependencies. Using the advantages of TestNG, Test XML files can be created and feature files can be performed. We can execute more than one runner class by creating the XML file as concurrently.
package myTestRunners;

import io.cucumber.testng.AbstractTestNGCucumberTests;
import io.cucumber.testng.CucumberOptions;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;


@CucumberOptions(
        features = "src/test/resources/functionalTests",
        glue= {"myStepDefinitions" , "myHooks"},
        tags = "@chrome",
        plugin = { "com.aventstack.extentreports.cucumber.adapter.ExtentCucumberAdapter:",
                "timeline:test-output-thread/",
                "rerun:src/test/resources/failedrerun.txt"},
        monochrome = true,
        publish = true
)

public class TestRunner extends AbstractTestNGCucumberTests {

    @BeforeTest
    void beforeTest() {

    }

    @AfterTest
    void AfterTest() {

    }
}
===========================================================================
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite thread-count="2" name="Example Project" parallel="tests">

    <test name="TestRunner1">
        <classes>
            <class name="myTestRunners.TestRunner" />
        </classes>
    </test>
    <test name="TestRunner2">
        <classes>
            <class name="myTestRunners.TestRunner" />
        </classes>
    </test>

</suite>
=================================================================================
3) Cucumber Runner with TestNG (IRetryAnalyzer)
Sometimes one execution may not be enough for a test. IRetryAnalyzer provides repetitive execution. Now I’m working on Cucumber 6.11.0 and TestNG 7.5 versions. This runner class was created by using these versions. Please feel free to comment if you have any issues while applying your project! :)
package myTestRunners;

import io.cucumber.testng.*;
import org.testng.IRetryAnalyzer;
import org.testng.ITestResult;
import org.testng.annotations.*;
@CucumberOptions(
        features = "src/test/resources/functionalTests",
        glue= {"myStepDefinitions" , "myHooks"},
        tags = "@chrome",
        plugin = { "com.aventstack.extentreports.cucumber.adapter.ExtentCucumberAdapter:",
                "timeline:test-output-thread/",
                "rerun:src/test/resources/failedrerun.txt"},
        monochrome = true,
        publish = true
)
public class TestRunnerWithRetry implements IRetryAnalyzer {

    private TestNGCucumberRunner testNGCucumberRunner;
    private int count = 0;
    private static int maxTry = 3;

    @Override
    public boolean retry(ITestResult iTestResult) {
        if (!iTestResult.isSuccess()) {  ;
            if (count < maxTry) {
                count++;
                iTestResult.setStatus(ITestResult.FAILURE);
                return true;
            } else {
                iTestResult.setStatus(ITestResult.FAILURE);
            }
        } else {
            iTestResult.setStatus(ITestResult.SUCCESS);
        }
        return false;
    }

    @BeforeClass(alwaysRun = true)
    public void setUpClass() throws Exception {
        System.out.println("Before Scenario ****");
        testNGCucumberRunner = new TestNGCucumberRunner(this.getClass());
    }

    @Test(groups = "cucumber", description = "Runs Cucumber Scenarios",
            dataProvider = "scenarios",retryAnalyzer = TestRunnerWithRetry.class)
    public void scenario(PickleWrapper pickleEvent, FeatureWrapper cucumberFeature) {
        testNGCucumberRunner.runScenario(pickleEvent.getPickle());
    }

    @DataProvider
    public Object[][] scenarios() {
        return testNGCucumberRunner.provideScenarios();
    }

    @AfterClass(alwaysRun = true)
    public void tearDownClass() {
        System.out.println("After Scenario ****");
        testNGCucumberRunner.finish();
    }
}
============================================================================
Resources: Features example:
https://courgette-testing.com/bdd

Karate Framework

 Karate Framework:



In this article, we’ll get to know API Testing with Karate Framework and go over the sample project.

With the popularity of BDD (Behaviour Driven Development), using the gherkin style in the automation project makes sense because offering development that everyone can understand has many benefits and provides a quick process for the team. Let’s explain the benefits of this by comparing Karate with Rest Assured Framework.

Comparison of Karate with Rest-Assured Framework

Firstly I started to experience Rest-Assured Framework for it but after exploring the Karate Framework it happened to be my favorite one. Because building a structure is very important to create a project that is suitable for growth. Most of the time it’s a cost for the team to understand and build the project. Unlike Rest-Assured, The utilization of Karate has proven to be cost-effective. Karate already creates ready-to-use gherkin steps for almost every action with API. We can think of it as a collaborative project for each different API project.

I won’t go into much detail about Rest-Assured but if you want to compare them, I’ll be adding projects for the same API created with Karate and Rest-Assured.

After comparing these frameworks in general, Let’s look at how we can use the Karate framework.

Set up Environment

Karate Framework supports JDK 1.8 and higher, so I use JDK 11.

You can find below the pom.xml example. I used ‘maven-archetype-quickstart’ when I started my project. You can select this archetype from IDE.


The file has 3 dependencies, one of them is JUnit which I added to manage parallel execution for test cases, and others are used for Karate.

<?xml version="1.0" encoding="UTF-8"?>

<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">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.example</groupId>
  <artifactId>Grocery-Karate-API-Automation</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>
  <name>Grocery-Karate-API-Automation</name>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>11</java.version>
    <maven.compiler.version>3.8.1</maven.compiler.version>
    <maven.surefire.version>2.22.2</maven.surefire.version>
    <karate.version>1.2.0</karate.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>com.intuit.karate</groupId>
      <artifactId>karate-junit5</artifactId>
      <version>${karate.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>com.intuit.karate</groupId>
      <artifactId>karate-apache</artifactId>
      <version>0.9.6</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <testResources>
      <testResource>
        <directory>src/test/java</directory>
      </testResource>
    </testResources>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>${maven.compiler.version}</version>
          <configuration>
            <encoding>UTF-8</encoding>
            <source>${java.version}</source>
            <target>${java.version}</target>
            <compilerArgument>-Werror</compilerArgument>
          </configuration>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>${maven.surefire.version}</version>
          <configuration>
            <argLine>-Dfile.encoding=UTF-8</argLine>
            <includes>
              <include>**/*TestRunner*.java</include>
            </includes>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>
As a second action, to manage environment configuration and the other ones such as base URL, API key, etc., we should create a karate-config.js file into the src/test/java path. Be sure this file is in the correct path otherwise, you can not use the configuration that into the karate-config.js
function fn() {
    var env = karate.env; // get system property 'karate.env'
    karate.log('karate.env system property was:', env);
    if (!env) {
        env = 'dev';
    }
    var config = {
        env: env,
        baseUrl: 'https://petstore.swagger.io'
    }
    if (env == 'dev') {
        // customize
        // e.g. config.foo = 'bar';
    } else if (env == 'e2e') {
        // customize
    }
    return config;
}

Now we’ve completed everything we need to start writing a script!

Let’s look at the examples with GET/POST/UPDATE/DELETE requests with Karate. After creating a sample feature file into the src/test/java path, you can write example requests like below.

There are the most basic GET and POST request examples. If you write a baseUrl, it reads from the karate config file automatically.

And also, you can verify the response with several types of match methods. (exact match, contains, present, etc.)

Feature: Grocery API

  Background: The Request Body Configuration
      # Set a configuration for the payload
    * url baseUrl

  Scenario: Get All Products from Grocery
    Given header Content-Type = 'application/json'
    And path '/allGrocery'
    When method get
    Then status 200
    And match response.data[*].id == '#present'
    And match response.data[*].price == '#present'
    And match response.data[*].name == '#present'
    And match response.data[*].stock == '#present'

  Scenario Outline: Get Grocery Details with a name
    Given header Content-Type = 'application/json'
    And path '/allGrocery/<name>'
    When method get
    Then status 200
    And match response.data[0].name == "<name>"
    And match response.data[0].id == '#present'
    And match response.data[0].price == '#present'
    And match response.data[0].stock == '#present'

    Examples:
      | name   |
      | apple  |
      | grapes |

  Scenario: Add a new product to the Grocery Basket
    * def jsonBody =
    """
{
    "id": 4,
    "name": "string",
    "price": 12.3,
    "stock": 3
}
 """
    Given header Content-Type = 'application/json'
    And path '/add'
    And request jsonBody
    When method post
    Then status 201
    And response.message == "success"

As you can see above, you can define the body payload in the scenario on the feature file and also you can create a JSON file and read it in the feature file.

On the other hand, Karate supports getting value from created java classes into the feature file. Compared with Rest Assured Framework, this feature makes the best difference in readability. You can find an example related to this.

{
  "id": 0,
  "category": {
    "id": 0,
    "name": "string"
  },
  "name": "hola",
  "photoUrls": [
    "string"
  ],
  "tags": [
    {
      "id": 0,
      "name": "string"
    }
  ],
  "status": "available"
}
pet.json
Feature: Create A new Pet

  Background: The Request Body Configuration
      # Set a configuration for the payload
    * url baseUrl
    * def requestPayload = read('classpath:payload/pet.json') #read the json file
    * set requestPayload.id = Java.type('utils.TestDataCreator').getID() #get value from java class
    * set requestPayload.category.id = Java.type('utils.TestDataCreator').getID() #get value from java class
    * set requestPayload.category.name = Java.type('utils.TestDataCreator').getDogCategoryName() #get value from java class
    * set requestPayload.name = Java.type('utils.TestDataCreator').getDogName() #get value from java class
    * set requestPayload.photoUrls[0] = Java.type('utils.TestDataCreator').getFileName() #get value from java class
    * set requestPayload.tags[0].name = requestPayload.name #get value from java class
    * set requestPayload.status = Java.type('utils.TestDataCreator').getStatus()[0] #get value from java class

  Scenario: Add a new pet to the store
    Given header Content-Type = 'application/json'
    And path '/v2/pet'
    And request requestPayload
    When method post
    Then status 200
    And match response.id == '#present'
    And match $.name == requestPayload.name
    And match $.category.name == requestPayload.category.name
    And match $.status == requestPayload.status
    # Find the pet by ID
    Given header Content-Type = 'application/json'
    And path '/v2/pet/' + response.id
    When method get
    Then status 200
    And match $.id == requestPayload.id
    And match $.category.id == requestPayload.category.id
    And match $.category.name == requestPayload.category.name
    And match $.name == requestPayload.name
Example of the post request

Lastly, I’m adding the example of the delete and update requests.

Feature: Petstore 

  Background: The Request Body Configuration
    # Set a configuration for the payload
    * url baseUrl
    * def requestPayload = read('classpath:payload/pet.json')
    * set requestPayload.id = Java.type('utils.TestDataCreator').getID()
    * set requestPayload.category.id = Java.type('utils.TestDataCreator').getID()
    * set requestPayload.category.name = Java.type('utils.TestDataCreator').getDogCategoryName()
    * set requestPayload.name = Java.type('utils.TestDataCreator').getDogName()
    * set requestPayload.photoUrls[0] = Java.type('utils.TestDataCreator').getFileName()
    * set requestPayload.tags[0].name = requestPayload.name
    * set requestPayload.status = Java.type('utils.TestDataCreator').getStatus()[0]

  Scenario: Delete a pet
    # Create a new pet as the precondition
    Given header Content-Type = 'application/json'
    And path '/v2/pet'
    And request requestPayload
    When method post
    Then status 200
    # Delete the pet from the store
    Given header Content-Type = 'application/json'
    And path '/v2/pet/' + requestPayload.id
    When method delete
    Then status 200
    And match $.code == 200

  Scenario: Updates the pet in the store
    # Create a new pet as the precondition
    Given header Content-Type = 'application/json'
    And path '/v2/pet'
    And request requestPayload
    When method post
    Then status 200
    # Create a new pet name & status for updating them.
    * def newName = Java.type('utils.TestDataCreator').getCatName()
    * def newStatus = Java.type('utils.TestDataCreator').getStatus()[2]
    # Send the update request
    Given header Content-Type = 'application/x-www-form-urlencoded'
    And  path '/v2/pet/' + requestPayload.id
    And form field name = newName
    And form field status = newStatus
    When method post
    Then status 200
    * print response
Example of the post request

Configuration Parallel Testing

Configuration parallel testing is quite easy. We only need the feature file path and JUnit that we added already into the pom.xml.

You can configure how many test cases should be executed simultaneously.

The parallel() method manages how many cases are running at the same time. I preferred 4 cases for my project but we can increase the amount. It doesn’t have any limit.

package runner;
import com.intuit.karate.Results;
import com.intuit.karate.junit5.Karate;
import static org.junit.Assert.*;
import org.junit.Test;


public class TestRunner {

   @Test
    public void testParallel() {
       Results results = Karate.run("src/test/java/features").parallel(4);
       assertTrue(results.getErrorMessages(), results.getFailCount() == 0);
   }

}
TestRunner.java

Summary Report

Karate Framework generates the summary report automatically after running the test case. When all case execution is completed, the HTML report URL is created as follows.



Other Features and References

Karate Framework not only supports JSON as a format but also works on SOAP requests as XML. To give a wide assertion method structure, provide readable and ready-to-use request & response methods, and also retry configurations, this framework meets all the developer’s expectations.

I’m really glad to share my own experience with Karate. If you want also to use Karate for API testing, you can benefit from the main documentation from KarateLabs and also my example projects. Please feel free to ask me anything about it.

Finally, I’m pleased to be a part of the Insider and very excited about the work we will do in the future. If you would like to learn how we manage QA Process at Insider, you can read this article.


STACK VS HEAP

DIFFERENCE  STACK VS HEAP


Stack Memory in Java

Stack Memory in Java is used for static memory allocation and the execution of a thread. It contains primitive values that are specific to a method and references to objects referred from the method that are in a heap.

Access to this memory is in Last-In-First-Out (LIFO) order. Whenever we call a new method, a new block is created on top of the stack which contains values specific to that method, like primitive variables and references to objects.

When the method finishes execution, its corresponding stack frame is flushed, the flow goes back to the calling method, and space becomes available for the next method.

Key Features of Stack Memory

Some other features of stack memory include:

  • It grows and shrinks as new methods are called and returned, respectively.
  • Variables inside the stack exist only as long as the method that created them is running.
  • It’s automatically allocated and deallocated when the method finishes execution.
  • If this memory is full, Java throws java.lang.StackOverFlowError.
  • Access to this memory is fast when compared to heap memory.
  • This memory is threadsafe, as each thread operates in its own stack.

Heap Space in Java

Heap space is used for the dynamic memory allocation of Java objects and JRE classes at runtime. New objects are always created in heap space, and the references to these objects are stored in stack memory.

These objects have global access and we can access them from anywhere in the application.

We can break this memory model down into smaller parts, called generations, which are:

  1. Young Generation – this is where all new objects are allocated and aged. A minor Garbage collection occurs when this fills up.
  2. Old or Tenured Generation – this is where long surviving objects are stored. When objects are stored in the Young Generation, a threshold for the object’s age is set, and when that threshold is reached, the object is moved to the old generation.
  3. Permanent Generation – this consists of JVM metadata for the runtime classes and application methods.

Key Features of Java Heap MemorSome other features of heap space include:

  • It’s accessed via complex memory management techniques that include the Young Generation, Old or Tenured Generation, and Permanent Generation.
  • If heap space is full, Java throws java.lang.OutOfMemoryError.
  • Access to this memory is comparatively slower than stack memory
  • This memory, in contrast to stack, isn’t automatically deallocated. It needs Garbage Collector to free up unused objects so as to keep the efficiency of the memory usage.
  • Unlike stack, a heap isn’t threadsafe and needs to be guarded by properly synchronizing the code.

How does it work:

      Every time you create an instance of a class, some memory gets allocated on Heap to store that object and return a reference pointer to the start of that block of memory. This reference pointer comes in the form of a unique number represented in hexadecimal format, and as an integer it is stored on the Stack, so when we need to access that object on Heap, we find its reference on Stack which points to objects location on Heap, which is then accessed by that reference.

Example:





What is happening behind the wall(the scheme is showed below):

  1. When JVM find main() method, the Stack frame will be created. After we create an instance of class Example, which means, that memory will be allocated on Heap to store the object and its address will be stored on Stack in form of a pointer.
  2. When method fun1() is called, one more Stack frame will be created. local_var1 and its value will be stored on it, as it is local primitive variable. 
  3. Method fun2() is called. New Stack frame created and as with main() method memory allocation for object o will happen on Heap and pointer will be returned and saved on Stack.
  4. Method fun3() is called. Parameter obj is saved on Stack as it is pointer to an object on Heap, local_var2 and its value is saved on Stack as well. Memory allocation for string will happen on the Heap in String Pool in Java.
  5.  After all GC will be invoked and memory will be released.