Skip to content

Tutorial: rick

Deepika Tiwari edited this page May 31, 2022 · 5 revisions

A tutorial on generating tests + mocks with rick

Step 1: Setting up

pankti

First, build pankti

cd pankti/
mvn clean install

A case study: Shopizer

For these tutorials, we work with an open-source Java project called Shopizer. Let's start by cloning it.

git clone git@github.com:shopizer-ecommerce/shopizer.git --branch v3.1.0

You can build and run it, as well as the react frontends, following the steps in its README. Play around with the website to get familiar with it.

Glowroot

Glowroot is an open-source APM agent. We use it for instrumentation and monitoring. Follow the instructions on the website to download it. Also, create a directory called plugins within glowroot/.

You can try to attach glowroot as a javaagent to Shopizer, by updating its POM and then running it.

...
  <properties>
    <coverage.lines>.04</coverage.lines>
    <coverage.branches>.01</coverage.branches>
    <commons-rng-simple.version>1.3</commons-rng-simple.version>
+   <glowroot.plugin.jar>-javaagent:/path/to/glowroot/glowroot.jar</glowroot.plugin.jar>
+   <boot.jvm.args>${glowroot.plugin.jar}</boot.jvm.args>
  </properties>
...
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
+       <configuration>
+         <jvmArguments>${boot.jvm.args}</jvmArguments>
+       </configuration>
      </plugin>
    </plugins>
    <finalName>shopizer</finalName>
  </build>
...

Step 2: Extracting a list of target methods

Next, we statically analyze the Shopizer source to find public methods that can be our targets for test generation.

cd pankti-extract/
java -jar target/pankti-extract-<version>-jar-with-dependencies.jar /path/to/shopizer/ --void --report

This produces ./extracted-methods-shopizer.csv The flag void indicates that we want to include methods that return void (i.e., don't return anything).


Step 3: Instrumenting the target methods

Next, we instrument these methods so that the object they are called on (receiving object), the objects passed as parameters, as well as the object returned from this method, are serialized as Shopizer executes. First,

cd pankti-instrument/

Since the ./extracted-methods-shopizer.csv we created in Step 2 has a lot of methods, we can find a list of classes with the most methods with the following:

python3 find-cuts.py ../pankti-extract/extracted-methods-shopizer.csv

This creates two files: ./cuts.txt and ./cuts-mockables.txt. We'll use cuts-mockables.txt for test generation with rick. It has a list of classes, as well the number of target methods within them. Let's pick com.salesmanager.core.business.utils.ProductPriceUtils which has 3 potential target methods that we can instrument.

python3 instrument-mock.py ../pankti-extract/extracted-methods-shopizer.csv com.salesmanager.core.business.utils.ProductPriceUtils

This generates 6 aspect classes (MethodAspect1, MethodAspect1Nested1 to MethodAspect3, MethodAspect3Nested1) in se.kth.castor.pankti.instrument.plugins and also updates pankti-instrument/src/main/resources/META-INF/glowroot.plugin.json with this list of new aspects. We can finally package this into a plugin with

mvn clean install

This produces pankti-instrument/target/pankti-instrument-<version>-jar-with-dependencies.jar. Copy this jar and paste it in the glowroot/plugins/ directory we created in Step 1.


Step 4: Executing Shopizer and collecting data

Now that we've added the plugin with the serialization instructions to glowroot, run Shopizer (make sure you've modified the Shopizer POM as presented in Step 1). Click around the website and interact with the products, etc. You'll find that /tmp/pankti-object-data/ has some XML files (which are the serialized versions of objects) and an invoked-methods.csv (which is a list of the methods that were invoked within com.salesmanager.core.business.utils.ProductPriceUtils as a result of your interactions). You can stop the Shopizer server when you're done interacting with the site.


Step 5: Generating tests

Finally,

cd rick/
java -jar target/rick-<version>-jar-with-dependencies.jar /path/to/shopizer/ /tmp/pankti-object-data/invoked-methods.csv /tmp/pankti-object-data/

This generates tests within rick/output/generated/shopizer/ as well as related resource files for longer object XMLs. Find the list of generated test classes with

cd output/generated/shopizer/
find . -name "Test*PanktiGen"

We see that we've generated the test class TestProductPriceUtilsPanktiGen.java. The generated resource files are in rick/output/generated/object-data/.


Step 6: Executing the tests

Create a new project module within Shopizer called rick-tests, add the following properties and dependencies in its POM.

  <properties>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
    <junit.jupiter.engine.version>5.7.0</junit.jupiter.engine.version>
    <junit.platform.runner.version>1.7.0</junit.platform.runner.version>
    <junit.jupiter.params.version>5.7.0</junit.jupiter.params.version>
    <xstream.version>1.4.12</xstream.version>
    <mockito.core.version>4.4.0</mockito.core.version>
    <mockito.junit.jupiter.version>2.23.0</mockito.junit.jupiter.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>com.thoughtworks.xstream</groupId>
      <artifactId>xstream</artifactId>
      <version>${xstream.version}</version>
    </dependency>
    <dependency>
      <groupId>org.junit.vintage</groupId>
      <artifactId>junit-vintage-engine</artifactId>
      <version>5.7.0</version>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <version>${junit.jupiter.engine.version}</version>
    </dependency>
    <dependency>
      <groupId>org.junit.platform</groupId>
      <artifactId>junit-platform-runner</artifactId>
      <version>${junit.platform.runner.version}</version>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-params</artifactId>
      <version>${junit.jupiter.params.version}</version>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
      <version>${junit.jupiter.engine.version}</version>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>${junit.jupiter.engine.version}</version>
    </dependency>
    <dependency>
      <groupId>org.mockito</groupId>
      <artifactId>mockito-inline</artifactId>
      <version>${mockito.core.version}</version>
    </dependency>
    <dependency>
      <groupId>org.mockito</groupId>
      <artifactId>mockito-junit-jupiter</artifactId>
      <version>${mockito.junit.jupiter.version}</version>
    </dependency>
    <dependency>
      <groupId>com.shopizer</groupId>
      <artifactId>sm-core</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>
...

Create the package com.salesmanager.core.business.utils under src/test/java/ within this new module. Copy the generated test inside this package. Also create src/test/resources/ and add the generated resource files. The generated tests are now ready to run!

Note: Deserialization for java.sql.Date and java.sql.Timestamp fails, causing the tests to fail. Here's a fix, to be added to the generated test:

  @BeforeAll
  public static void setxStream() {
    xStream.registerConverter(new Converter() {
      @Override
      public void marshal(Object o, HierarchicalStreamWriter hierarchicalStreamWriter, MarshallingContext marshallingContext) {}

      @Override
      public Object unmarshal(HierarchicalStreamReader hierarchicalStreamReader, UnmarshallingContext unmarshallingContext) {
        return new Timestamp(System.currentTimeMillis());
      }

      @Override
      public boolean canConvert(Class aClass) {
        return aClass.getCanonicalName().equals("java.sql.Timestamp") ||
               aClass.getCanonicalName().equals("java.sql.Date");
      }
    });
  }