Repository: authorjapps/zerocode
Pull request #90 (2018-06-09 16:10:42)

ISSUE-88 # multi load for production like load generation


Patch:
diff --git a/README.md b/README.md
index 379e25e2..3308da4f 100644
--- a/README.md
+++ b/README.md
@@ -21,11 +21,11 @@ Keep things simple, Zerocode alleviates the need of writing `code` for test asse
 [![Download HelloWorld](https://img.shields.io/badge/download-examples-blue.svg)](https://github.com/authorjapps/zerocode-hello-world/archive/master.zip) 
 [![Hex.pm](https://img.shields.io/hexpm/l/plug.svg)](https://github.com/authorjapps/zerocode/blob/master/LICENSE) 
 [![Code Coverage](https://img.shields.io/badge/coverage-80%25-brightgreen.svg)](https://github.com/authorjapps/zerocode/blob/master/img/code_coverage/code_coverage_granular.png)
-[![GitHub issue comments](https://img.shields.io/github/issues/detail/comments/badges/shields/979.svg)](https://github.com/authorjapps/zerocode/issues) 
 [![zerocode REST API Automation](https://img.shields.io/badge/REST%20API-automation-green.svg)](https://github.com/authorjapps/zerocode-hello-world) 
 [![zerocode SOAP Testing Automation API Automation](https://img.shields.io/badge/SOAP%20testing-automation-blue.svg)](https://github.com/authorjapps/zerocode/issues/28) 
 [![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg)](https://gitter.im/zerocode-testing/help-and-usage)
 [![codeproject](https://img.shields.io/badge/codeproject-zerocode-blue.svg)](https://www.codeproject.com/search.aspx?q=zerocode&x=0&y=0&sbo=kw)
+[![Performance Testing](https://img.shields.io/badge/performance-testing-ff69b4.svg)](https://github.com/authorjapps/zerocode/wiki/Load-or-Performance-Testing-(IDE-based))
 
 
 Supported testing frameworks:
@@ -46,6 +46,10 @@ Zerocode helps you to design better Test Cases for your business features, maint
 
 Testing becomes an easy and effortless job due to the **simplicity** nature of JSON and the native support by popular IDEs e.g. Eclipse /IntelliJ /NetBeans etc with no extra plugin. Your tests will not be cumbersome and complex anymore. Zerocode makes your tests independent, complete and structured and easily maintainable by the team or the new comers. It enables you to write your `API End Point Tests`, `Consumer Contract Tests` and `End to End Tests` etc, at the **speed** of writing **JUnit** tests.
 
+- Browse or clone `contract tests` examples from [here](https://github.com/authorjapps/consumer-contract-tests)
+- Browse or clone `performance tests`(load, stress) examples [here]()
+
+
 [More >>](https://github.com/authorjapps/zerocode/wiki)
 
 Who uses Zerocode?
diff --git a/src/main/java/org/jsmart/zerocode/core/report/ZeroCodeReportGeneratorImpl.java b/src/main/java/org/jsmart/zerocode/core/report/ZeroCodeReportGeneratorImpl.java
index 923d8b2b..119e4cc9 100644
--- a/src/main/java/org/jsmart/zerocode/core/report/ZeroCodeReportGeneratorImpl.java
+++ b/src/main/java/org/jsmart/zerocode/core/report/ZeroCodeReportGeneratorImpl.java
@@ -224,8 +224,8 @@ public class ZeroCodeReportGeneratorImpl implements ZeroCodeReportGenerator {
                 .addColumn("stepLoop", CsvSchema.ColumnType.NUMBER)
                 .addColumn("correlationId")
                 .addColumn("requestTimeStamp")
-                .addColumn("responseTimeStamp")
                 .addColumn("responseDelayMilliSec", CsvSchema.ColumnType.NUMBER)
+                .addColumn("responseTimeStamp")
                 .addColumn("result")
                 .build();
 
@@ -264,8 +264,8 @@ public class ZeroCodeReportGeneratorImpl implements ZeroCodeReportGenerator {
                     thisResult.getSteps().forEach(thisStep -> {
                         csvFileBuilder.stepLoop(thisStep.getLoop());
                         csvFileBuilder.stepName(thisStep.getName());
-                        csvFileBuilder.correlationId(thisStep.getCorrelationId()); //<-- in case of searching in the log file
-                        csvFileBuilder.result(thisStep.getResult()); //<-- passed or failed
+                        csvFileBuilder.correlationId(thisStep.getCorrelationId());
+                        csvFileBuilder.result(thisStep.getResult());
                         csvFileBuilder.requestTimeStamp(thisStep.getRequestTimeStamp().toString());
                         csvFileBuilder.responseTimeStamp(thisStep.getResponseTimeStamp().toString());
                         csvFileBuilder.responseDelayMilliSec(thisStep.getResponseDelay());
@@ -335,7 +335,6 @@ public class ZeroCodeReportGeneratorImpl implements ZeroCodeReportGenerator {
             ofNullable(files).orElseThrow(() -> new RuntimeException("Somehow the '" + reportsFolder + "' has got no files."));
 
         } catch (Exception e) {
-            e.printStackTrace();
             final String message = "\n----------------------------------------------------------------------------------------\n" +
                     "Somehow the '" + reportsFolder + "' is not present or has no report JSON files. \n" +
                     "Possible reasons- \n" +
@@ -347,7 +346,7 @@ public class ZeroCodeReportGeneratorImpl implements ZeroCodeReportGenerator {
                     "----------------------------------------------------------------------------------------\n";
 
             // TODO- Can Suppress as this error as this is only related to report. It doesn't hurt or affect the tests at all.
-            throw new RuntimeException(message);
+            throw new RuntimeException(message + e);
         }
 
     }
diff --git a/src/main/java/org/jsmart/zerocode/core/runner/parallel/LoadProcessor.java b/src/main/java/org/jsmart/zerocode/core/runner/parallel/LoadProcessor.java
index 3f063fbc..d42d3693 100644
--- a/src/main/java/org/jsmart/zerocode/core/runner/parallel/LoadProcessor.java
+++ b/src/main/java/org/jsmart/zerocode/core/runner/parallel/LoadProcessor.java
@@ -67,4 +67,21 @@ public class LoadProcessor {
             }
         };
     }
+
+    public boolean processMultiLoad() {
+        executorServiceRunner.runRunnablesMulti();
+
+        LOGGER.info(
+                "\n------------------------------------"
+                        + "\n   >> Total load test count:" + (failedCounter.get() + passedCounter.get())
+                        + "\n   >> Passed count:" + passedCounter.get()
+                        + "\n   >> Failed count:" + failedCounter.get()
+                        + "\n------------------------------------");
+
+        if(failedCounter.get() > 0){
+            return failed;
+        }
+
+        return passed;
+    }
 }
diff --git a/src/main/java/org/jsmart/zerocode/core/runner/parallel/ZeroCodeLoadRunner.java b/src/main/java/org/jsmart/zerocode/core/runner/parallel/ZeroCodeLoadRunner.java
index 1f8ded40..78be6169 100644
--- a/src/main/java/org/jsmart/zerocode/core/runner/parallel/ZeroCodeLoadRunner.java
+++ b/src/main/java/org/jsmart/zerocode/core/runner/parallel/ZeroCodeLoadRunner.java
@@ -98,6 +98,7 @@ public class ZeroCodeLoadRunner extends ParentRunner<TestMapping> {
             errMessage = "Mapped test method `" + methodMapping.testMethod() + "`" + errMessage;
             methodMapping.testClass().getMethod(methodMapping.testMethod());
         } catch (NoSuchMethodException e) {
+            LOGGER.error(errMessage);
             throw new RuntimeException(errMessage + e);
         }
     }
diff --git a/src/main/java/org/jsmart/zerocode/core/runner/parallel/ZeroCodeMultiLoadRunner.java b/src/main/java/org/jsmart/zerocode/core/runner/parallel/ZeroCodeMultiLoadRunner.java
index 51a8b6e5..5acbbb01 100644
--- a/src/main/java/org/jsmart/zerocode/core/runner/parallel/ZeroCodeMultiLoadRunner.java
+++ b/src/main/java/org/jsmart/zerocode/core/runner/parallel/ZeroCodeMultiLoadRunner.java
@@ -1,11 +1,8 @@
 package org.jsmart.zerocode.core.runner.parallel;
 
+import org.jsmart.zerocode.core.domain.LoadWith;
 import org.jsmart.zerocode.core.domain.TestMapping;
-import org.jsmart.zerocode.core.domain.TestMappings;
 import org.junit.runner.Description;
-import org.junit.runner.JUnitCore;
-import org.junit.runner.Request;
-import org.junit.runner.Result;
 import org.junit.runner.notification.Failure;
 import org.junit.runner.notification.RunNotifier;
 import org.junit.runners.ParentRunner;
@@ -13,44 +10,68 @@ import org.junit.runners.model.InitializationError;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
 
-// TODO-- wip...
-public class ZeroCodeMultiLoadRunner extends ParentRunner<TestMapping> {
+import static org.junit.runner.Description.createTestDescription;
+
+public class ZeroCodeMultiLoadRunner extends ParentRunner<TestMapping[]> {
     private static final Logger LOGGER = LoggerFactory.getLogger(ZeroCodeMultiLoadRunner.class);
+    public static final String LOAD_LABEL = "<<Multi-Load>>";
 
     private final Class<?> testClass;
+    private LoadProcessor loadProcessor;
+    private String loadPropertiesFile;
+    private Description testDescription;
 
     public ZeroCodeMultiLoadRunner(Class<?> testClass) throws InitializationError {
         super(testClass);
         this.testClass = testClass;
+        this.loadPropertiesFile = validateAndGetLoadPropertiesFile();
+        loadProcessor = new LoadProcessor(loadPropertiesFile);
     }
 
     @Override
-    protected List<TestMapping> getChildren() {
+    protected List<TestMapping[]> getChildren() {
         validateAnnotationPresence();
-        return Arrays.asList(testClass.getAnnotationsByType(TestMapping.class));
+        validateTestMethod();
+        return getTestMappingsArrayAsSingleElementList();
+    }
+
+    private List<TestMapping[]> getTestMappingsArrayAsSingleElementList() {
+        TestMapping[] testMappings = testClass.getAnnotationsByType(TestMapping.class);
+        List<TestMapping[]> testMappingsSingleElementList = new ArrayList<>();
+        testMappingsSingleElementList.add(testMappings);
+        return testMappingsSingleElementList;
     }
 
     @Override
-    protected Description describeChild(TestMapping child) {
-        return Description.createTestDescription(testClass, child.testMethod());
+    protected Description describeChild(TestMapping[] childArrayElement) {
+        String multiLoadLabel = createMultiLoadLabel(childArrayElement);
+        System.out.println("### label = " + multiLoadLabel + "\n");
+        this.testDescription = createTestDescription(testClass, LOAD_LABEL + multiLoadLabel);
+        return testDescription;
     }
 
     @Override
-    protected void runChild(TestMapping child, RunNotifier notifier) {
-        final Description description = Description.createTestDescription(testClass, child.testMethod());
-        notifier.fireTestStarted(description);
+    protected void runChild(TestMapping[] childArrayElement, RunNotifier notifier) {
+        notifier.fireTestStarted(testDescription);
+
+        Arrays.stream(childArrayElement).forEach(thisChild -> {
+            loadProcessor.addTest(thisChild.testClass(), thisChild.testMethod());
+        });
 
-        Result result = (new JUnitCore()).run(Request.method(child.testClass(), child.testMethod()));
+        boolean hasFailed = loadProcessor.processMultiLoad();
 
-        if(!result.wasSuccessful()){
-            String failureMessage = testClass.getName() + "." + child.testMethod() + " Failed";
+        if (hasFailed) {
+            String failureMessage = testClass.getName() + " with load/stress test(s): " + testDescription + " have Failed";
             LOGGER.error(failureMessage + ". See target/logs -or- junit granular failure report(csv) -or- fuzzy search and filter report(html) for details");
-            notifier.fireTestFailure(new Failure(description, new RuntimeException(failureMessage)));
+            notifier.fireTestFailure(new Failure(testDescription, new RuntimeException(failureMessage)));
         }
-        notifier.fireTestFinished(description);
+        notifier.fireTestFinished(testDescription);
     }
 
     @Override
@@ -58,12 +79,46 @@ public class ZeroCodeMultiLoadRunner extends ParentRunner<TestMapping> {
         super.run(notifier);
     }
 
+    private String validateAndGetLoadPropertiesFile() {
+        LoadWith loadWithAnno = testClass.getAnnotation(LoadWith.class);
+        if (loadWithAnno == null) {
+            throw new RuntimeException("Ah! You missed to put the @LoadWith(...) on the load-generating test class >> "
+                    + testClass.getName());
+        }
+
+        return loadWithAnno.value();
+    }
+
     private void validateAnnotationPresence() {
-        TestMappings testMappings = testClass.getAnnotation(TestMappings.class);
-        TestMapping[] methodMappings = testClass.getAnnotationsByType(TestMapping.class);
-        if (testMappings == null || methodMappings == null) {
-            throw new RuntimeException("Ah! You missed to put the @TestMappings or @TestMapping");
+        TestMapping[] testMappings = testClass.getAnnotationsByType(TestMapping.class);
+
+        if (testMappings.length == 0) {
+            throw new RuntimeException("Ah! You missed to put the @TestMapping on the load-generating test class >>"
+                    + testClass.getName() + ".");
+
         }
     }
 
+    private void validateTestMethod() {
+        TestMapping[] testMappings = testClass.getAnnotationsByType(TestMapping.class);
+        Arrays.stream(testMappings).forEach(methodMapping -> {
+            String errMessage = " was invalid, please re-check and pick the correct test method to load.";
+            try {
+                errMessage = "Mapped test method `" + methodMapping.testMethod() + "`" + errMessage;
+                methodMapping.testClass().getMethod(methodMapping.testMethod());
+            } catch (NoSuchMethodException e) {
+                LOGGER.error(errMessage);
+                throw new RuntimeException(errMessage + e);
+            }
+        });
+    }
+
+    private String createMultiLoadLabel(TestMapping[] childArrayElement) {
+        AtomicInteger counter = new AtomicInteger(0);
+        return Arrays.stream(childArrayElement)
+                .map(thisChild ->  "\n" + counter.incrementAndGet() + ")"
+                        + thisChild.testClass().getSimpleName()
+                        + "." + thisChild.testMethod())
+                .collect(Collectors.joining(",")) + "\n";
+    }
 }
diff --git a/src/main/java/org/jsmart/zerocode/parallel/ExecutorServiceRunner.java b/src/main/java/org/jsmart/zerocode/parallel/ExecutorServiceRunner.java
index 81f5dc08..6e2dd8f4 100644
--- a/src/main/java/org/jsmart/zerocode/parallel/ExecutorServiceRunner.java
+++ b/src/main/java/org/jsmart/zerocode/parallel/ExecutorServiceRunner.java
@@ -12,6 +12,7 @@ import java.util.Properties;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 
 import static java.lang.Double.valueOf;
@@ -105,6 +106,52 @@ public class ExecutorServiceRunner {
         }
     }
 
+    public void runRunnablesMulti() {
+        if (runnables == null || runnables.size() == 0) {
+            throw new RuntimeException("No runnable(s) was found to run. You can add one or more runnables using 'addRunnable(Runnable runnable)'");
+        }
+
+        ExecutorService executorService = newFixedThreadPool(numberOfThreads);
+
+        try {
+            final AtomicInteger functionIndex = new AtomicInteger();
+
+            for (int i = 0; i < loopCount; i++) {
+                for (int j = 0; j < numberOfThreads; j++) {
+                    try {
+                        LOGGER.info("Waiting for the next test flight to adjust the overall ramp up time, " +
+                                "waiting time in the transit now = " + delayBetweenTwoThreadsInMilliSecs);
+                        sleep(delayBetweenTwoThreadsInMilliSecs.longValue());
+                    } catch (InterruptedException e) {
+                        throw new RuntimeException(e);
+                    }
+
+                    LOGGER.info(Thread.currentThread().getName() + " Executor - *Start... Time = " + now());
+
+                    executorService.execute(runnables.get(functionIndex.getAndIncrement()));
+
+                    LOGGER.info(Thread.currentThread().getName() + " Executor - *Finished Time = " + now());
+
+                    if(functionIndex.get() == runnables.size()){
+                        functionIndex.set(0);
+                    }
+                }
+
+            }
+        } catch (Exception interruptEx) {
+            throw new RuntimeException(interruptEx);
+        } finally {
+            executorService.shutdown();
+            while (!executorService.isTerminated()) {
+                // --------------------------------------
+                // wait for all tasks to finish execution
+                // --------------------------------------
+                //LOGGER.info("Still waiting for all threads to complete execution...");
+            }
+            LOGGER.info("**Finished executing all threads**");
+        }
+    }
+
     public void runCallables() {
         runCallableFutures();
     }


Test patch:
diff --git a/src/main/java/org/jsmart/zerocode/core/engine/listener/ZeroCodeTestReportListener.java b/src/main/java/org/jsmart/zerocode/core/engine/listener/ZeroCodeTestReportListener.java
index 22fd2530..ce40a5d6 100644
--- a/src/main/java/org/jsmart/zerocode/core/engine/listener/ZeroCodeTestReportListener.java
+++ b/src/main/java/org/jsmart/zerocode/core/engine/listener/ZeroCodeTestReportListener.java
@@ -42,6 +42,8 @@ public class ZeroCodeTestReportListener extends RunListener {
 
         generateChartsAndReports();
 
+        LOGGER.info("\n\n### For more usage details please read the README file or click the url >>> https://github.com/authorjapps/zerocode#zerocode\n\n");
+
     }
 
     private void generateChartsAndReports() {
diff --git a/src/test/java/org/jsmart/zerocode/core/runner/parallel/ZeroCodeLoadRunnerTest.java b/src/test/java/org/jsmart/zerocode/core/runner/parallel/ZeroCodeLoadRunnerTest.java
index 9496ef91..c103a6af 100644
--- a/src/test/java/org/jsmart/zerocode/core/runner/parallel/ZeroCodeLoadRunnerTest.java
+++ b/src/test/java/org/jsmart/zerocode/core/runner/parallel/ZeroCodeLoadRunnerTest.java
@@ -8,6 +8,7 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.junit.runner.RunWith;
+import org.junit.runners.model.InitializationError;
 
 import java.util.List;
 
diff --git a/src/test/java/org/jsmart/zerocode/core/runner/parallel/ZeroCodeMultiLoadRunnerTest.java b/src/test/java/org/jsmart/zerocode/core/runner/parallel/ZeroCodeMultiLoadRunnerTest.java
index bbf810ee..4ea2654e 100644
--- a/src/test/java/org/jsmart/zerocode/core/runner/parallel/ZeroCodeMultiLoadRunnerTest.java
+++ b/src/test/java/org/jsmart/zerocode/core/runner/parallel/ZeroCodeMultiLoadRunnerTest.java
@@ -5,7 +5,9 @@ import org.jsmart.zerocode.core.domain.TestMapping;
 import org.jsmart.zerocode.core.domain.TestMappings;
 import org.jsmart.zerocode.parallel.restful.JunitRestTestSample;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 import org.junit.runner.RunWith;
 
 import java.util.List;
@@ -13,9 +15,16 @@ import java.util.List;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.Is.is;
 
-// TODO- wip
 public class ZeroCodeMultiLoadRunnerTest {
 
+    @Rule
+    public ExpectedException expectedException = ExpectedException.none();
+
+    ZeroCodeMultiLoadRunner zeroCodePackageGroupRunner;
+    ZeroCodeMultiLoadRunner zeroCodePackageRunner;
+    ZeroCodeMultiLoadRunner runnerNoTestMapping;
+    ZeroCodeMultiLoadRunner runnerMultiTestMapping;
+    ZeroCodeMultiLoadRunner runnerInvalidTestMapping;
 
     @LoadWith("load_config_test.properties")
     @TestMappings({
@@ -23,42 +32,94 @@ public class ZeroCodeMultiLoadRunnerTest {
             @TestMapping(testClass = JunitRestTestSample.class, testMethod = "testGetCallToHome_fail"),
     })
     @RunWith(ZeroCodeMultiLoadRunner.class)
-    public class LoadRestEndPointRunnerTest { //note "public" is necessary
+    public class ExampleRunnerWithGroupTest { //note "public" is necessary
 
     }
 
-    ZeroCodeMultiLoadRunner zeroCodePackageRunner;
+    @LoadWith("load_config_test.properties")
+    @TestMapping(testClass = JunitRestTestSample.class, testMethod = "testGetCallToHome_pass")
+    @RunWith(ZeroCodeMultiLoadRunner.class)
+    public class ExampleRunnerTest {
+
+    }
+
+    // @TestMapping not present to check exception
+    @LoadWith("load_config_test.properties")
+    @RunWith(ZeroCodeMultiLoadRunner.class)
+    public class ExampleRunnerWithoutMappingTest {
+    }
+
+    @LoadWith("load_config_test.properties")
+    @TestMapping(testClass = JunitRestTestSample.class, testMethod = "testGetCallToHome_pass")
+    @TestMapping(testClass = JunitRestTestSample.class, testMethod = "testGetCallToHome_fail")
+    @RunWith(ZeroCodeMultiLoadRunner.class)
+    public class ExampleRunnerMultiMappingTest {
+    }
+
+    @LoadWith("load_config_test.properties")
+    @TestMapping(testClass = JunitRestTestSample.class, testMethod = "dodgyAndInvalidTestMethod")
+    @RunWith(ZeroCodeMultiLoadRunner.class)
+    public class ExampleRunnerInvalidMethodTest {
+    }
 
     @Before
     public void initializeRunner() throws Exception {
-        zeroCodePackageRunner = new ZeroCodeMultiLoadRunner(LoadRestEndPointRunnerTest.class);
+        zeroCodePackageGroupRunner = new ZeroCodeMultiLoadRunner(ExampleRunnerWithGroupTest.class);
+        zeroCodePackageRunner = new ZeroCodeMultiLoadRunner(ExampleRunnerTest.class);
+        runnerNoTestMapping = new ZeroCodeMultiLoadRunner(ExampleRunnerWithoutMappingTest.class);
+        runnerMultiTestMapping = new ZeroCodeMultiLoadRunner(ExampleRunnerMultiMappingTest.class);
+        runnerInvalidTestMapping = new ZeroCodeMultiLoadRunner(ExampleRunnerInvalidMethodTest.class);
     }
 
     @Test
-    public void testPair_size() {
-        List<TestMapping> children = zeroCodePackageRunner.getChildren();
-        assertThat(children.size(), is(2));
+    public void testPair_sizeTwo() {
+        List<TestMapping[]> children = zeroCodePackageGroupRunner.getChildren();
+        assertThat(children.size(), is(1));
+        assertThat(children.get(0).length, is(2));
+    }
+
+    @Test
+    public void testPair_multiMappingWithoutGrouping() {
+        List<TestMapping[]> children = runnerMultiTestMapping.getChildren();
+        assertThat(children.size(), is(1));
+        assertThat(children.get(0).length, is(2));
+    }
+
+    @Test
+    public void testPair_annotaionSingle() {
+        TestMapping mapping = ExampleRunnerTest.class.getAnnotation(TestMapping.class);
+        assertThat(mapping.testMethod(), is("testGetCallToHome_pass"));
+        assertThat(mapping.testClass().getName(), is((JunitRestTestSample.class.getName())));
     }
 
     @Test
-    public void testPair_annotaionMulti() {
-        TestMapping[] mappings = LoadRestEndPointRunnerTest.class.getAnnotationsByType(TestMapping.class);
-        assertThat(mappings[0].testMethod(), is("testGetCallToHome_pass"));
-        assertThat(mappings[0].testClass().getName(), is((JunitRestTestSample.class.getName())));
+    public void testPair_size() {
+        List<TestMapping[]> children = zeroCodePackageRunner.getChildren();
+        assertThat(children.size(), is(1));
     }
 
     @Test
-    public void testPair_classVsMethodMap() {
-        List<TestMapping> children = zeroCodePackageRunner.getChildren();
-        TestMapping[] testMappings = LoadRestEndPointRunnerTest.class.getAnnotationsByType(TestMapping.class);
-        assertThat(children.get(0).testMethod(), is("testGetCallToHome_pass"));
-        assertThat(children.get(0).testClass().getName(), is((JunitRestTestSample.class.getName())));
+    public void testPair_singleClassVsMethodMap() {
+        List<TestMapping[]> children = zeroCodePackageRunner.getChildren();
+        assertThat(children.get(0)[0].testMethod(), is("testGetCallToHome_pass"));
+        assertThat(children.get(0)[0].testClass().getName(), is((JunitRestTestSample.class.getName())));
     }
 
     @Test
     public void testLoad_properties() {
-        LoadWith loadWithFile = LoadRestEndPointRunnerTest.class.getAnnotation(LoadWith.class);
+        LoadWith loadWithFile = ExampleRunnerTest.class.getAnnotation(LoadWith.class);
         assertThat(loadWithFile.value(), is("load_config_test.properties"));
     }
 
+    @Test
+    public void test_noMappingException() {
+        expectedException.expectMessage("Ah! You missed to put the @TestMapping on the load-generating test class");
+        List<TestMapping[]> children = runnerNoTestMapping.getChildren();
+    }
+
+    @Test
+    public void testPair_invalidTestMethodMappingException() {
+        expectedException.expectMessage("Mapped test method `dodgyAndInvalidTestMethod` was invalid, please re-check and pick");
+        List<TestMapping[]> children = runnerInvalidTestMapping.getChildren();
+    }
 }
\ No newline at end of file
diff --git a/src/test/java/org/jsmart/zerocode/parallel/TestGitHubApi.java b/src/test/java/org/jsmart/zerocode/parallel/TestGitHubApi.java
index 04baeb87..ca34f91b 100644
--- a/src/test/java/org/jsmart/zerocode/parallel/TestGitHubApi.java
+++ b/src/test/java/org/jsmart/zerocode/parallel/TestGitHubApi.java
@@ -3,6 +3,7 @@ package org.jsmart.zerocode.parallel;
 import org.jsmart.zerocode.core.domain.JsonTestCase;
 import org.jsmart.zerocode.core.domain.TargetEnv;
 import org.jsmart.zerocode.core.runner.ZeroCodeUnitRunner;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -10,6 +11,7 @@ import org.junit.runner.RunWith;
 @RunWith(ZeroCodeUnitRunner.class)
 public class TestGitHubApi {
 
+    @Ignore("Locally passes, but fails in Travis CI due to rate limiting issue of GitHub")
     @Test
     @JsonTestCase("load/github_get_api_sample_test.json")
     public void testGitHubApi_get() throws Exception {
diff --git a/src/test/java/org/jsmart/zerocode/parallel/restful/LoadRestEndPointMultiRunnerGroupTest.java b/src/test/java/org/jsmart/zerocode/parallel/restful/LoadRestEndPointMultiRunnerGroupTest.java
new file mode 100644
index 00000000..cc6a2ada
--- /dev/null
+++ b/src/test/java/org/jsmart/zerocode/parallel/restful/LoadRestEndPointMultiRunnerGroupTest.java
@@ -0,0 +1,19 @@
+package org.jsmart.zerocode.parallel.restful;
+
+import org.jsmart.zerocode.core.domain.LoadWith;
+import org.jsmart.zerocode.core.domain.TestMapping;
+import org.jsmart.zerocode.core.domain.TestMappings;
+import org.jsmart.zerocode.core.runner.parallel.ZeroCodeMultiLoadRunner;
+import org.junit.runner.RunWith;
+
+
+@LoadWith("load_config_test.properties")
+@TestMappings({
+        @TestMapping(testClass = JunitRestTestSample.class, testMethod = "testGetCallToHome_pass"),
+        @TestMapping(testClass = JunitRestTestSample.class, testMethod = "testGetCallToHome_pass"),
+//        @TestMapping(testClass = JunitRestTestSample.class, testMethod = "testGetCallToHome_fail")
+})
+@RunWith(ZeroCodeMultiLoadRunner.class)
+public class LoadRestEndPointMultiRunnerGroupTest {
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/jsmart/zerocode/parallel/restful/LoadRestEndPointMultiRunnerTest.java b/src/test/java/org/jsmart/zerocode/parallel/restful/LoadRestEndPointMultiRunnerTest.java
index b605153b..b0dc00df 100644
--- a/src/test/java/org/jsmart/zerocode/parallel/restful/LoadRestEndPointMultiRunnerTest.java
+++ b/src/test/java/org/jsmart/zerocode/parallel/restful/LoadRestEndPointMultiRunnerTest.java
@@ -2,21 +2,15 @@ package org.jsmart.zerocode.parallel.restful;
 
 import org.jsmart.zerocode.core.domain.LoadWith;
 import org.jsmart.zerocode.core.domain.TestMapping;
-import org.jsmart.zerocode.core.runner.parallel.ZeroCodeLoadRunner;
 import org.jsmart.zerocode.core.runner.parallel.ZeroCodeMultiLoadRunner;
 import org.junit.runner.RunWith;
 
 
-//@TestMappings({
-//        @TestMapping(testClass = JunitRestTestSample.class, testMethod = "testGetCallToHome_pass"),
-////        @TestMapping(testClass = JunitRestTestSample.class, testMethod = "testGetCallToHome_fail"),
-//})
-//@RunWith(ZeroCodeMultiLoadRunner.class)
-
-//@LoadWith("load_config_test.properties")
-//@TestMapping(testClass = JunitRestTestSample.class, testMethod = "testGetCallToHome_pass")
+@LoadWith("load_config_test.properties")
+@TestMapping(testClass = JunitRestTestSample.class, testMethod = "testGetCallToHome_pass")
+@TestMapping(testClass = JunitRestTestSample.class, testMethod = "testGetCallToHome_pass")
 //@TestMapping(testClass = JunitRestTestSample.class, testMethod = "testGetCallToHome_fail")
-//@RunWith(ZeroCodeMultiLoadRunner.class)
-//public class LoadRestEndPointMultiRunnerTest {
-//
-//}
\ No newline at end of file
+@RunWith(ZeroCodeMultiLoadRunner.class)
+public class LoadRestEndPointMultiRunnerTest {
+
+}
\ No newline at end of file
diff --git a/src/test/resources/load_config_test.properties b/src/test/resources/load_config_test.properties
index 8a0132d3..e1b4679a 100755
--- a/src/test/resources/load_config_test.properties
+++ b/src/test/resources/load_config_test.properties
@@ -2,7 +2,9 @@
 # to simulate a load test with 5 concurrent users then you need to enter 5 as the value for this property. A high end
 # machine will be able to spawn more number of threads. To keep the consistent(or nearly consistent) gap between the
 # threads, adjust this number with 'ramp.up.period.in.seconds' and the actual response time of the API end point.
-number.of.threads=2
+# For "@RunWith(ZeroCodeMultiLoadRunner.class)", set this to at least equal to number of "@TestMapping" or more,
+# otherwise there wont be enough threads to run each test.
+number.of.threads=3
 
 # It indicates the time taken to create all of the threads needed to fork the requests. If you set 10 seconds as the
 # ramp-up period for 5 threads then the framework will take  10 seconds to create those 5 threads, i.e. each thread
@@ -10,7 +12,7 @@ number.of.threads=2
 # at once at the same time. Note- If you want to fire more threads/user-requests in less ramp up time e.g. 5 threads
 # in 2secs(or 5 threads in 1 sec), then, use '@UseHttpClient(SslTrustHttpClient.class)' as this closes the connection
 # before making the next connection.
-ramp.up.period.in.seconds=2
+ramp.up.period.in.seconds=3
 
 # By specifying its value framework gets to know that how many times the test(s), i.e. the number of requests will be
 # repeated per every 'ramp.up.period.in.seconds'.