How to handle Java exceptions in clean code? – Part 2

In my previous post, I explained the different types of exceptions in Java and left two questions for this post!

Which exception to use?

Java documentation and Uncle Bob’s book (Clean Code) were my references when preparing this tutorial. I noticed a slight difference between what is recommended in each!

Java Documentation

While reading the Java documentation, you sense a preference for Checked Exceptions. The documentation states that the usage of UnChecked Exceptions should be restricted to the case where crashing the system is intentional if an exception occurs. In other cases where recovery is still possible, Checked Exceptions should be used.

Robert Martin’s opinion

Uncle Bob has a different opinion!  He argues that although Checked Exceptions might have some benefits and can be useful in some special cases like writing critical libraries, they are not a necessity to have a robust software.

Breaking the ‘Open/Closed Principle‘ and ‘Encapsulation‘ are the main two reasons that make using Checked Exceptions a bad idea!

Let’s see how!

In the below example, the MainBookReader.main method is calling the method MainBookReader.readJsonObject to get the book’s description from a JSON file. And since readJsonObject throws an exception we had to add throws FileNotFoundException clause to the signature of the main method and the interface JsonLoader!

import java.io.FileNotFoundException;

public class MainBookReader {
    public static void main(String[] args) throws FileNotFoundException {
        BookJsonLoader jsonLoader = new BookJsonLoader();
        Book bookFromJson = jsonLoader.readJsonObject("books.json");
        System.out.println(bookFromJson.name());
    }
}
import com.google.gson.Gson;
import java.io.FileNotFoundException;
import java.io.FileReader;

public class BookJsonLoader implements JsonLoader {
    public Book readJsonObject(String fileName) throws FileNotFoundException {
        FileReader jsonFile = new FileReader(fileName);
        Gson gson = new Gson();
        return transformToBook(gson.fromJson(jsonFile, JsonBook.class));
    }

    private Book transformToBook(JsonBook jsonBook) {
        return new Book(jsonBook.getName());
    }
}
import java.io.FileNotFoundException;

public interface JsonLoader {
    Book readJsonObject(String fileName) throws FileNotFoundException;
}

Adding the throws clause to most of our methods means two things!

  1. Our top methods & classes had to know a lot about the lower methods & classes!
  2. If any change is to be applied to the lower methods, this change has to be replicated to all above methods!

This is a violation of the ‘Open/Closed Principle‘ and ‘Encapsulation‘!

Is there a better way?

No matter how careful we are, things can still go wrong. Thus we still need to deal the exceptions when they occur! The alternative to the code above is writing clean code!

In this section, I will refactor the above code to make the code look better, although it might involve more code!

Wrap the Exception

The first step is to wrap the exception in one place! In our case, we can replace the throws clause with a try-catch!

In the catch clause, I’m throwing a JsonFileNotFoundException (UncheckedException) that I have created based on the needs of this system (i.e. providing an informative message to the user).

public class BookJsonLoader implements JsonLoader {
    public Book readJsonObject(String fileName){
        try {
            FileReader jsonFile = new FileReader(fileName);
            Gson gson = new Gson();
            return transformToBook(gson.fromJson(jsonFile, JsonBook.class));
        } catch (FileNotFoundException e) {
            throw new JsonFileNotFoundException("Can't find the file " + fileName + ". Please make sure it exists!", e);
        }
    }

    private Book transformToBook(JsonBook jsonBook) {
        return new Book(jsonBook.getName());
    }
}

The benefits of this refactoring are:

  • Remove all ‘throws clauses.’
  • Hide the logic and information of our class from the outer classes
  • Send an informative message to the user in case the file was not found
  • Minimize the dependency (remove imports from other classes)

Don’t return null

In some cases, developers don’t want to throw an UnCheckedException, so they tend to return a ‘null’ instead! That is a bad practice because it will eventually result in a NullPointerException later.

So, what to do? The answer is simple, return a SPECIAL CASE!

The below class ‘MissingBook‘ is our special case!

public class MissingBook extends Book {

    private MissingBook(String name) {
        super(name);
    }

    public static MissingBook aMissingBook() {
        return new MissingBook("MISSING BOOK");
    }
}

This allows us to return an instance of the MissingBook in the catch instead of throwing an exception!

public class BookJsonLoader implements JsonLoader {
    public Book readJsonObject(String fileName){
        try {
            FileReader jsonFile = new FileReader(fileName);
            Gson gson = new Gson();
            return transformToBook(gson.fromJson(jsonFile, JsonBook.class));
        } catch (FileNotFoundException e) {
            return MissingBook.aMissingBook();
        }
    }

    private Book transformToBook(JsonBook jsonBook) {
        return new Book(jsonBook.getName());
    }
}

Don’t pass null

In your code, you shouldn’t pass null as parameters and instead use the Special Cases as before. But, it gets harder to prevent your clients from passing nulls thus you might need to assert some function parameters before proceeding with your code execution!

Finally

Doing the refactoring for the simple example above might look like over-engineering! But, it becomes beneficial when writing more complex code!

I think that the excessive usage of CheckedExceptions will pollute the code and thus should be avoided! For me, the benefits of CheckedExceptions can still be achieved through:

  1. Following the rules of Clean Code
  2. Increase test coverage of the code by following the TDD practice! With the use of TDD, all the edge cases should be covered and thus eliminating the possibility of having unexpected exceptions during execution!

To understand better the importance and how to write clean code, it is highly recommended that you read the book Clean Code by Robert Martin!

References:

  1. Clean Code: A Handbook of Agile Software Craftsmanship
  2. Unchecked Exceptions — The Controversy
  3. Java Exceptions
  4. The Clean Code Blog

How to handle Java exceptions in clean code? – Part 1

Reading the chapter ‘Error Handling‘ from the book ‘Clean Code‘ made me think whether developers follow the clean code rules when writing production code. It also appeared to me that the difference between the Java exception types might not clear be for some!

The purpose of this short tutorial is to clarify those two points. And to make it clearer, I will be dividing it into two posts. In the first one, I will explain the different types of Java exceptions. Whereas in the second one, I will show how to write clean exception code!

Java Exceptions

In Java, the class Throwable is at the top of the class hierarchy of all exceptions. The classes Error and Exception directly extend Throwable. All the subclasses of Error and Exception are grouped into two types ‘Checked Exceptions‘ and ‘UnChecked Exceptions‘as displayed in the image below.

javaexceptiondiagram

What is the difference between Checked and UnChecked Exceptions?

UnChecked Exceptions

UnChecked Exceptions extend the classes ‘Error‘ or ‘RuntimeException‘. In this case, developers don’t have to worry about catching or handling the exceptions at compile time as the compiler won’t report any error. But, as they are not caught, those exceptions may result in complete failure of the application when thrown during execution.

Below are two examples of UnChecked Exceptions!

1. Runtime Exception

The below method simply divides two numbers:

private static int divide(int numerator, int denominator) {
    return numerator / denominator;
}

The code is fine and will compile with no errors! So what is the problem?

The problem might occur at runtime if we try to call the method while passing zero as the denominator. Since this exception is not caught, our system will crash throwing the below arithmetic exception:

Exception in thread "main" java.lang.ArithmeticException: / by zero
 at MainUnCheckedException.divide(MainUnCheckedException.java:7) ...
2. Error

The below method compiles, but since I missed writing the base cases calling the method will crash the system and throw a StackOverflowError (as shown below) will throw an Error!

private static int fibonacci(int number) {
    return fibonacci(number - 1) + fibonacci(number - 2);
}
Exception in thread "main" java.lang.StackOverflowError
 at MainUnCheckedException.fibonacci(MainUnCheckedException.java:10)...

Checked Exceptions

On the other hand, all other classes extending the ‘Exception‘ class are considered to be Checked Exceptions. In this case, the code will not compile if the developer doesn’t explicitly handle the exception.

This adds a level of security to your code, as you are forced to specify how your code should behave when an exception occurs and thus decreases the chance of having an unrecoverable failure in the system.

Let’s see an example!

public class MainCheckedException {
    public static void main(String[] args) {
        FileInputStream fileInputStream = new FileInputStream("foo.txt");
    }
}

Error:(6, 43) java: unreported exception java.io.FileNotFoundException;
must be caught or declared to be thrown

In the above code, we are trying to read a file ‘foo.txt’, but our compiler complains that we need to handle the FileNotFoundException.

Solving this compilation error can be done in two ways:

  1. Try Catch: The first solution requires surrounding the code with a try-catch.
    private static void readFileTryCatch() {
        try {
            FileInputStream fooFile = new FileInputStream("foo.txt");
        } catch (FileNotFoundException e) {
            System.out.println(e.getMessage());
        }
    }

    Even if the file was not there, this code would not break thanks to the try-catch. As shown in the below code and output, the system will print out the exception and continue execution!

    public static void main(String[] args) {
        readFileTryCatch();
        System.out.println("Done");
    }
    ------------Output------------
    foo.txt (No such file or directory)
    Done
  2. Throw an exception: The other solution is adding a ‘throws FileNotFoundException‘ to the method signature.
    private static void readFileThrow() throws FileNotFoundException {
        new FileInputStream("foo.txt");
    }

    Using this solution will propagate handling the exception to the calling methods as illustrated in the two options below:
    Option 1: Adding throws to all methods signatures 

    public static void main(String[] args) throws FileNotFoundException {
        readFileThrow();
        System.out.println("Done");
    }
    ------------Output------------
    Exception in thread "main" java.io.FileNotFoundException: foo.txt (No such file or directory)
    at java.io.FileInputStream.open0(Native Method) ...

    Option 2: Catching the exception in one of the methods in the call stack

    public static void main(String[] args)  {
        try {
            readFileThrow();
        } catch (FileNotFoundException e) {
            System.out.println(e.getMessage());
        }
        System.out.println("Done");
    }
    ------------Output------------
    foo.txt (No such file or directory)
    Done

 

I hope this post gave you a better understanding of Java exceptions! In the 2nd part, I will be sharing more details on how to properly write exceptions in clean code and which of the two exception types to use!

Coding dojo… One year later

Last week’s coding dojo session was a special one, not only because we brought a cake 🙂 but because it marked the first anniversary of those sessions at Murex Beirut. One year ago, I wrote a post sharing my experience on how we started this activity. Today, I am writing this post to share what has changed during this year.

Widening the scope

After twenty-eight sessions of practicing TDD, we decided it was time to adjust the scope a bit! Frequent attendees grasped TDD pretty well and suggested that we might benefit from those sessions to learn new technologies and practices.

After a brainstorming session, we agreed to label the session by one of four themes: Algorithms & TDD, New Languages, Kata & Workshops, and New Projects. We thought that by applying those themes we would increase the technical knowledge of all attendees, involve volunteers in the session preparation thus improving their presentations skills, and probably attract wider audience

Themes

Algorithms & TDD

Algorithms.png

We are all aligned that our primary purpose of this activity is practicing TDD, but in the case of complex algorithms using TDD is not always the optimal way (sudoku for example). Thus, we decided that in such cases we first would agree on the full algorithm then implement the code and write the sufficient tests to cover the different cases!

Lately, we’ve been using CodinGame to solve complex puzzles and algorithms, as the tests are already written, and they have an excellent graphical simulation of the puzzle execution.

New Languages

7Languages7Weeks

Learning new programming languages is one of the best approaches to improving someone’s programming skills; especially, when it includes learning different programming models (OO, functional, procedural, etc.) For those sessions, a volunteer learns the language alone and then presents it to the group in any form he/she prefers.

So far, we have learned two languages Scala and Ruby! For those sessions, we are using the book Seven Languages in Seven Weeks as our learning material reference and CodinGame as an exercising platform.

Kata & Workshops

JavaAgent.jpg

We defined kata as a presentation or short talk on a particular subject given by someone knowledgeable on that topic. A workshop, on the other hand, is a collaborative session where all take part of experimenting something new.

So far, we’ve had five of those sessions:

  1. A workshop on Git.
  2. Two kata sessions on machine learning.
  3. One presentation on Java agent and one on Android development.

Design patterns, development processes (Agile, lean, pair programming, etc.) and tools (docker, Gradle) are some of the candidates to be presented in next sessions.

New Projects

This is probably the hardest theme! The idea here is to implement an internal tool or app that can benefit other employees. Contributors to this theme will benefit from practicing TDD on a big scale project and learning new tools and technologies.

At this point, we are preparing a short list of proposals to get approval on one!  Hopefully, we can kickstart it soon 🙂

In Numbers

We initially started with thirty-four registered participants, but that number dropped to fourteen. Honestly, I think this isn’t a bad number!

The below graphs show the distribution of all the forty-seven sessions we’ve had so far. The graphs are grouped by theme and topic / language.

By Theme:
By Topic / Language:

More to Come

Thanks to the participants’ commitment and their eagerness to learn and improve their technical skills we celebrated the first anniversary of the coding dojo sessions. We will keep running those sessions, and we will keep improving them as we go! I hope that in one year from now, I will be posting another blog to share what we have done 🙂

Newcomers’ Training Program

Recently, I was in charge of training two fresh-graduate newcomers to our department. My mission was to prepare a two weeks program to ease the integration process with their teams.

After a short brainstorming, I decided to break the training into the following seven topics:

  1. Agile Practices: Their first assignment was getting acquainted with the Agile methodologies (mainly XP our working process). For that, I asked them to read a couple of chapters from two books “The Art of Agile Development” and “Extreme Programming Explained.”
  2. Dev Tools: Configuring some dev tools on their machines was the second step. This involved the installation and configuration of Java, IntelliJ, Maven and Perforce. Some of those tools such as Perforce and Maven were relatively new to them; so they took some time to learn more about it.
  3. TDDBy now, they were ready to write some code! And what would be better than following TDD to do that? Most of our teams started adopting TDD, thus coaching newcomers on TDD for simple dev problems is a must! For that purpose I picked the following two problems:
    • Mars Rover: This might be an easy problem, but I find it well suited to practice TDD especially for TDD newbies as it has a lot of cases to be covered by tests.
    • Coffee Machine: The beauty of this problem, is that it simulates what happens in the life cycle of an agile project, such as:
      • Defining new requirements at the start of each iteration
      • Writing the minimum code to implement the required features
      • Continuous code refactoring
      • Write the sufficient tests at each iteration
  4. Design Pattern and Code Refactoring: The two problems above may not be complex and can be solved in a short time, but the solution wasn’t the primary purpose rather it was introducing new concepts and practices to them. To make sure this purpose was achieved, I was performing multiple code review sessions during each iteration and suggesting enhancement at each time. This process elongated the time for each iteration, but it was worth. Some of the concepts I focused on were:
    • Test coverage
    • Builder pattern
    • Visitor pattern
    • Factory pattern
    • Bad and good code practices
    • Mocking
  5. Maven: They used Maven to build the code they wrote previously, but it was only maven’s basic commands. At this phase of the training, I asked them to dig deeper into maven to have a better understanding how it works; mainly focusing on:
    • Phases of build lifecycle
    • Dependency management
    • Plugins
    • Local and remote repositories
  6. SCM: Whether it is Git or Perforce, there are a couple of must know operations for any developer to be part of a development team. As a practice on those operations, they simulated a real dev cycle scenario by:
    • Sharing a common working directory on Perforce
    • Creating branches
    • Merging/Integrating changes
    • Resolving conflicts
  7. Continuous Integration (CI)As fresh graduates, the continuous integration was a new concept for them. Whereas for us, it is an essential process of our development cycle. It wasn’t possible to use an existing Jenkins instance to perform their testing; thus they executed the below steps:
    • Download and configure Jenkins locally on their machines
    • Submit their code to Perforce
    • Add a new job that syncs, compile code and execute the tests

 

I noticed the benefit of this training from the emails they sent me at the end of the program. They detailed what they learned and most importantly they were able to highlight the advantages of those practices and tools.

I hope you find this post helpful for your next newcomers’ training!

 

Using H2 In-Memory to test your DAL

How should we test the Data Access Layer code?

Many developers ask that question. Similar to the other layers of your system, it should be fully tested to prevent unexpected and random behavior in production. There are many ways to achieve that, among of which are mocks or in-memory database.

The problem with mocks is that instead of testing the validity of your SQL queries (syntax and execution), you will only be testing the validity of the system’s flow. On the other hand, using the in-memory database will validate both! That is why I prefer it over mocking. But, you should keep in mind that the SQL syntax might differ from one database engine to another.

In this blog, I will be giving an example of using “H2 In-Memory” in unit tests. The code of this example is available on my GitHub account.

H2 In-Memory in Action

So, let us see H2 in action 🙂

For the sake of this blog, I will assume we want to test three SQL operations on the table “Members” having the following model:

MEMBERS
ID      | NAME        |
INTEGER | VARCHAR(64) |

The SQL operations to be covered in this blog are:

  1. Create Table
  2. Insert (batch of prepared statements)
  3. Select *

Code Explanation

The example will be based on five files (pom.xml, Member.java, SqlRepository.java, H2Repository.java and H2MembersRepositoryTest). In this section, I will give a brief explanation of each file.

Pom.xml (Maven Dependencies):

First, let us modify our pom file (as shown below) to make our project depend on two projects:

  1. com.h2database: Using this dependency, Maven will take care of downloading the h2 jar file we will be referencing in our tests.
  2. Junit: a unit testing framework for Java
<dependencies>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>1.4.191</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
    </dependency>
</dependencies>

Member.java:

This class represents a Member record. It consists of a factory method that returns an instance of the Member class and two getter methods to return the values of the Id and Name fields.

package dal;

import java.util.Objects;

public final class Member {
    private final int id;
    private final String name;

    private Member(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public static Member aMember(int id, String name) {
        return new Member(id, name);
    }

    public int id() {
        return id;
    }

    public String name() {
        return name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Member member = (Member) o;
        return id == member.id &&
                Objects.equals(name, member.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name);
    }
}

SqlRepository.java

In this class, we establish a connection to our database. What is important in this class is that we pass the connection string as a parameter to the constructor thus making our code unbounded to any specific database engine (MySql, H2, Sybase, Oracle, etc.). This will make writing our tests much easier!

The class consists of:

  1. Constructor: initializes an instance of SQL Connection using the connection string passed as parameter
  2. Three public methods:
    1. createTable: executes an update query to create the”MEMBERS” table.
    2. allMemebers: executes a select query and returns the found records in a list of Members.
    3. insertMembers: takes a list of “Member” as a parameter and inserts the values into the “MEMBERS” table.
package dal;

import dal.Member;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class SqlRepository {

    protected final Connection connection;
    private static final String CREATE_MEMBERS = "CREATE TABLE MEMBERS(ID INTEGER, NAME VARCHAR(64))";
    private static final String SELECT_MEMBERS = "SELECT * FROM MEMBERS";
    private static final String INSERT_MEMBERS = "INSERT INTO MEMBERS(ID, NAME) VALUES(?, ?)";

    public SqlRepository(String connectionString) throws SQLException {
        connection = DriverManager.getConnection(connectionString);
    }

    public boolean createTable() throws SQLException {
        Statement createStatement = connection.createStatement();
        return createStatement.execute(CREATE_MEMBERS);
    }

    public List<Member> allMembers() throws SQLException {
        List<Member> allMembers = new ArrayList<>();
        Statement selectStatement = connection.createStatement();
        ResultSet membersResultSet = selectStatement.executeQuery(SELECT_MEMBERS);
        while (membersResultSet.next()) {
            allMembers.add(Member.aMember(membersResultSet.getInt(1), membersResultSet.getString(2)));
        }
        return allMembers;
    }

    public void insertMembers(List<Member> members) throws SQLException {
        final PreparedStatement insertMembers = connection.prepareStatement(INSERT_MEMBERS);
        members.forEach(member -> insertMember(member, insertMembers));
        insertMembers.executeBatch();
    }

    private void insertMember(Member member, PreparedStatement insertMembers) {
        try {
            insertMembers.setInt(1, member.id());
            insertMembers.setString(2, member.name());
            insertMembers.addBatch();
        } catch (SQLException e) {
            throw new UnsupportedOperationException(e.getMessage());
        }
    }
}

H2Repository.java

I added this class under “Test Sources Root” because it is only used by the tests.

As you notice, it extends the SqlRepository class implemented previously. Thus, we don’t have a lot to implement here. The only method added is a new method “closeConnection” that drops all the existing tables from the database.

You might wonder why would we need that since we are using an In-Memory database. That might be true for this simple example, but it will be a necessity when running multiple tests classes. That is because, in Java, all the tests are run in the same JVM which means that the H2 instance initialized in the first test will be shared with the next test classes. This approach might lead to an unexpected behavior when using the same tables in the different test classes.

package dal;

import java.sql.SQLException;

public final class H2Repository extends SqlRepository {
    public H2Repository(String connectionString) throws SQLException {
        super(connectionString);
    }

    public void closeConnection() throws SQLException {
        connection.createStatement().execute("DROP ALL OBJECTS");
    }
}

H2MembersRepositoryTest.java

This is the simple test class! Our single test (it_correctly_inserts_members_to_a_database) is invoking the three methods we implemented before (createTable, insertMember and allMembers). If any of those methods is badly written our test would fail.

package dal;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import static dal.Member.aMember;
import static org.fest.assertions.Assertions.assertThat;

public class H2MembersRepositoryTest {

    private static final String H2_CONNECTION_STRING = "jdbc:h2:mem:test";
    private static final List<Member> MEMBERS = new ArrayList<>(10);
    private static H2Repository h2Repository;

    @BeforeClass
    public static void
    setup_database() throws SQLException {
        h2Repository = new H2Repository(H2_CONNECTION_STRING);
        initializeMembers();
    }

    @Test
    public void
    it_correctly_inserts_members_to_a_database() throws SQLException {
        h2Repository.createTable();
        h2Repository.insertMembers(MEMBERS);

        assertThat(h2Repository.allMembers()).isEqualTo(MEMBERS);
    }

    private static void initializeMembers() {
        for (int index = 0; index < 10; index++) {
            MEMBERS.add(aMember(index, "Name_" + index));
        }
    }

    @AfterClass
    public static void
    tear_down_database() throws SQLException {
        h2Repository.closeConnection();
    }
}

TearDown

If you can test your DAL, you can test anything! (I just came up with this ;))

Keep the tests going!

References:

  1. H2-Database Engine
  2. H2-Maven
  3. JUnit Maven
  4. GitHub

How to reference Ruby-JMeter from Java project?

INTRODUCTION

Apache JMeter is an open-source Java application that allows you to design, build and run performance tests. It is an excellent tool and currently we are using it to analyze our software’s performance. But being a graphical tool, after building a couple of tests you will notice that most of the test steps were repeated each time. It becomes frustrating and a waste of time and here is when Ruby-JMeter comes to the rescue!!! Ruby-JMeter is a tool that allows you to write your JMeter test plan in Ruby and then convert it to a JMX file. Since it is an open-source project, you have the flexibility of extending it and customizing it to fit your needs.

How can this tutorial benefit you?l

The purpose of this tutorial is to show how to integrate ruby-jmeter within a Java project. We are using this integration in an in-house Java based benchmark framework that dynamically reads and executes JMX files. Another way to benefit from this tutorial is if you want to reference any of the RubyGems from your application. Ruby-jmeter is one of the gems hosted on RubyGems, thus, you can follow the steps defined in the 2nd part of the tutorial to reference other gems.

Tutorial Structure

I have divided this tutorial into three sections. The first one shows how to execute ruby scripts directly from Java; the second section shows how to ruby ruby-jmeter scripts from Java. Whereas the last section is dedicated from some refactoring of our test class.

Executing Ruby Scripts

1. Maven Dependency

One of the possible options to run Ruby from Java is by using JRuby.
Since we are writing a maven project, then we need to add JRuby dependency in our pom. To do that we just add the following dependency to the dependencies section of our pom.Xml file.

<dependency>
    <groupId>org.jruby</groupId>
    <artifactId>jruby-complete</artifactId>
    <version>9.0.3.0</version>
</dependency>

2. RubyExecutor

Now we can reference JRuby in our code, so let us start by adding a new java file “RubyExecutor” to our project. In this class, we will add a static method that takes the full path of the ruby script as a parameter.

Below is the code for the method:

public class RubyExecutor {
    public static void run(String rubyFile) throws FileNotFoundException, ScriptException {
         final ScriptEngine scriptEngine = new JRubyEngineFactory().getScriptEngine();
         final FileReader rubyFileReader = new FileReader(rubyFilePath);
         scriptEngine.eval(rubyFileReader);
         rubyFileReader.close();
    }
}

The first line initializes an instance of JRubyEngine.
The third line just calls the eval method that is responsible for executing the ruby code in the JRubyEngine.

3. Testing our code

The best way to assert our code is working as we want, is by writing JUnit tests. In this case, we can add a new test class RubyExecutorTest.java under “src/test/java”.
For simplicity, we will make our ruby code generate a new file at the end of its execution. This generated file has the same filename but with “jmx” as an extension.
Based on that we should make our test call the method RubyExecutor.run() and assert that a new file has been added.
First we should start by writing a simple ruby script in the test-resources folder.

Steps:

  1. Add a new folder “src/test/resources/RubyExecutor”
  2. Add a new ruby file “simpleCode.rb” under that folder
  3. Add the following code to the Ruby script. This code creates a new empty file with the name “simpleCode.jmx”
`File.new('target/test-classes/RubyExecutor/simpleCode.jmx', "w+")`

Now we can write our test class that should look like this:

public class RubyExecutorTest {
    @Test
    public void
    it_should_generate_a_file_from_ruby_code() throws IOException, ScriptException {
       final String rubyFile = this.getClass().getResource("/RubyExecutor/simpleCode.rb").getFile();
       RubyExecutor.run(rubyFile);
       final File expectedFile = new File(rubyFile.replace("rb", "jmx"));
       Assertions.assertThat(expectedFile).exists();
    }
}

The test asks the RubyExecutor to execute the ruby script named “simpleCode.rb” (the one we created in previous steps) and asserts that the file “simpleCode.jmx” has been created.
Now let us try to run the test using maven command. To do so run the below command from the directory “jruby-jmeter-integration”

mvn clean install

The tests should be green, and we should get the following success message:

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 01:17 min

We should also notice that the file “simpleCode.jmx” has been created under “target/test-classes/RubyExecutor/”
Note: I use the library org.fest.assertions.Assertions, thus make sure you have added it to your dependencies in maven.

Executing Ruby-JMeter Scripts

Now that we are capable of executing Ruby scripts from our project, we will work on running ruby-jmeter scripts.

1. Ruby-JMeter

As stated earlier, ruby-jmeter is a tool that allows you to define your jmeter test-plan in Ruby code. The tool provides methods for most of the JMeter controllers, thus, it is a matter of calling the right method with the right parameters. Upon execution, the tool will generate a JMX file from the Ruby code.
I’m not going to dig into details on how it works, for more details you can have a look at some examples available on project’s workspace on GitHub

Now it is time to write a simple test plan with ruby-jmeter.

Steps:

  1. Add a new ruby script “src/test/resources/RubyExecutor/simpleJmx.rb”
  2. Add the following code to the Ruby script:
require 'rubygems'
require 'ruby-jmeter'   
test do
  threads count: 1, rampup: 0, loops: 1, scheduler: false do
    beanshell_sampler query:'log.info("********Hello World********");'
  end
end.jmx(file: 'target/test-classes/RubyExecutor/simpleJmx.jmx')

This ruby code will do the following:

  1. Create a new test plan
  2. Add a new “Thread Group” with a single thread and single loop count
  3. Adds a BeanShellSampler as a child of the thread group to print “********Hello World********”
  4. Saves the test-plan in the file simpleJmx.jmx

The generated JMX file should look like this:

SimpleJmx

2. Ruby-JMeter within our maven project

Now we know exactly what our code should do; what are its input and output. So, it would be great if we write a JUnit test to check if our code has indeed generated a JMX file from the Ruby script we wrote in the previous section.
So our test should execute the ruby script and is expected to generate the respective JMX file (“target/test-classes/RubyExecutor/simpleJmx.jmx”) at the end of the execution.
Based on this, we can add a new test to RubyExecutorTest.java

@Test
public void
it_should_generate_a_jmx_file_from_ruby_code() throws IOException, ScriptException {
 final String rubyFile = this.getClass().getResource("/RubyExecutor/simpleJmx.rb").getFile();
 RubyExecutor.run(rubyFile);
 final File expectedFile = new File(rubyFile.replace("rb", "jmx"));
 Assertions.assertThat(expectedFile).exists();
}

Let us give it a try:

mvn clean install

Unfortunately if fails:

LoadError: no such file to load -- ruby-jmeter
    require at org/jruby/RubyKernel.java:939
    require at uri:classloader:/META-INF/jruby.home/lib/ruby/stdlib/rubygems/core_ext/kernel_require.rb:54
        <top> at <script>:2
    Tests run: 2, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 11.657 sec <<< FAILURE!
    it_should_generate_a_jmx_file_from_ruby_code(RubyExecutorTest)  Time elapsed: 11.446 sec  <<< ERROR!
    javax.script.ScriptException: org.jruby.embed.EvalFailedException: (LoadError) no such file to load -- ruby-jmeter
        at org.jruby.embed.jsr223.JRubyEngine.wrapException(JRubyEngine.java:104)
        at org.jruby.embed.jsr223.JRubyEngine.eval(JRubyEngine.java:121)
        at org.jruby.embed.jsr223.JRubyEngine.eval(JRubyEngine.java:146)

The exception is telling us that it couldn’t execute ruby-jmeter. Maven wasn’t able to locate the ruby-jmeter libraries as we haven’t referenced it yet in any of our code.
To fix this problem, we will have to add more dependencies to our pom file!

3. Ruby-Gems Dependency

RubyGems is a repository hosting Ruby community’s gems. Once referenced, Maven will access this repository to sync the required ruby libraries locally to your project.
In our case we are using the ruby-jmeter gems to add dependency on that we should modify our pom file as follows:

1.  Add repository: This will be used by maven to download the gems from the specified URL

     <repositories>
        <repository>
            <id>rubygems-releases</id>
            <url>http://rubygems-proxy.torquebox.org/releases</url>
        </repository>
    </repositories>

2.  Add dependency on ruby-jmeter

<dependency>
    <groupId>rubygems</groupId>
    <artifactId>ruby-jmeter</artifactId>
    <version>2.13.8</version>
    <type>gem</type>
</dependency>

3.  Add build step

<build>
    <plugins>
        <plugin>
            <groupId>de.saumya.mojo</groupId>
            <artifactId>gem-maven-plugin</artifactId>
            <version>1.1.3</version>
            <extensions>true</extensions>
            <executions>
                <execution>
                    <goals>
                        <goal>initialize</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <includeRubygemsInResources>true</includeRubygemsInResources>
            </configuration>
        </plugin>
    </plugins>
</build>

4.  Rerun maven:

mvn clean install

Now we should get a “Build Success.”

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running RubyExecutorTest
I, [2015-12-23T17:27:19.643000 #7564]  INFO -- : Test plan saved to: target/test-classes/RubyExecutor/simpleJmx.jmx
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 7.278 sec
Results :
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ java-ruby-jmeter-integration ---
[INFO] Building jar: D:\Dev\java\workspace\GitHub\sample-projects\jruby-jmeter-integration\target\java-ruby-jmeter-integration-1.0-SNAPSHOT.jar
[INFO]
[INFO] --- maven-install-plugin:2.4:install (default-install) @ java-ruby-jmeter-integration ---
[INFO] Installing D:\Dev\java\workspace\GitHub\sample-projects\jruby-jmeter-integration\target\java-ruby-jmeter-integration-1.0-SNAPSHOT.jar to D:\Dev\maven_repo\aatwi\github\java-ruby-jmeter-integration\1.0-SNAPSHOT\java-ruby-jmeter-integration-1.0-SNAPSHOT.jar
[INFO] Installing D:\Dev\java\workspace\GitHub\sample-projects\jruby-jmeter-integration\pom.xml to D:\Dev\maven_repo\aatwi\github\java-ruby-jmeter-integration\1.0-SNAPSHOT\java-ruby-jmeter-integration-1.0-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

Code Refactoring

The last part of this tutorial is to do some code refactoring to our test class. Although it isn’t essential here, it became a habit for me!!
If we look at our current code, we can notice that there is some code duplication in the two tests.
One thing we can do here is extracting the method “assertJmxFileExists” to remove this duplication.
Our new method will take the responsibility of calling RubyExecutor.run and asserting the generation of the JMX file.
The code below shows the difference before and after refactoring.
Before Refactoring:

@Test
public void
it_should_generate_a_file_from_ruby_code() throws IOException, ScriptException {
    final String rubyFile = this.getClass().getResource("/RubyExecutor/simpleCode.rb").getFile();
    RubyExecutor.run(rubyFile);
    final File expectedFile = new File(rubyFile.replace("rb", "jmx"));
    Assertions.assertThat(expectedFile).exists();
}  

@Test
public void
it_should_generate_a_jmx_file_from_ruby_code() throws IOException, ScriptException {
    final String rubyFile = this.getClass().getResource("/RubyExecutor/simpleJmx.rb").getFile();
    RubyExecutor.run(rubyFile);
    final File expectedFile = new File(rubyFile.replace("rb", "jmx"));
    Assertions.assertThat(expectedFile).exists();
}

After Refactoring:

@Test
public void
it_should_generate_a_file_from_ruby_code() throws IOException, ScriptException {
    assertJmxFileExists("simpleCode.rb");
}

@Test
public void
it_should_generate_a_jmx_file_from_ruby_code() throws IOException, ScriptException {
    assertJmxFileExists("simpleJmx.rb");
}

private void assertJmxFileExists(final String rubyScript) throws IOException, ScriptException {
    final String rubyFile = this.getClass().getResource("/RubyExecutor/" + rubyScript).getFile();
    RubyExecutor.run(rubyFile);
    final File expectedJmxFile = new File(rubyFile.replace("rb", "jmx"));
    Assertions.assertThat(expectedJmxFile).exists();
}

We can do more refactoring, but I think it is not worth at this point. The code is simple and can be easily read and understood.
Finally, rerun maven to make sure that nothing was broken with this refactoring.

References