AspectJ with Arquillian testing - ideas
smikloso Jul 14, 2013 12:19 PMHi all,
I would like to show you very interesting approach how to solve a whole range of problems when trying to do something low-level with Arquillian.
All started when I was thinking about some way how to incorporate some screenshooter for our Arquillian Droidium extension. That screenshooter should be very flexible in a way how you incorporate it into your testing project. There is very much ways how to do screenshots, webdriver has its own way, selendroid has its own facilities, Android device itself can do also the same thing but there is no one common way how to take screenshots when something goes wrong.
I came up with the idea like this gist: https://gist.github.com/smiklosovic/5974532#file-arquillianscreenshootertestcase-java
As you can see, you just place your annotation on your test classes and you are done. You can see very interesting problem with these annotations like
@TakeScreenShots({BeforeAssert.class, AfterAssert.class})
That annotation is basically saying that I want to take screenshots before and after *every* assert call I do in that test. We can go further, take screenshots before test method, after test method and before / after these asserts.
It is not so complicated to do something when a test result is obtained as done in already existing extension for screenshots and videos like here: https://github.com/arquillian/arquillian-extension-screenrecorder/blob/master/src/main/java/org/jboss/arquillian/extension/screenRecorder/LifecycleObserver.java#L114 but what I do not like about that extension is that you have no power to hook your own implementation of screenshooter. E.g. when you want to take screenshots for your Android device, you can not use this extension since you have to take screenshots differently (like with some build in Android command). Secondly, you do not have power to do it in more granular level - e.g. "I want to take screenshot before every assert call of Assert class".
So, there was a lot of discussion about it, my colleagues went like you have to modify byte code and so on to go "deeper" since ordinary reflection is just useless here.
Then I realized that there is AspectJ framework which would be totally awesome for this. You just write your aspects, you waive them into your test _in compilation time_ and you are ready to go. Simple as that.
So I set up very easy Maven artifact like this:
<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/maven-v4_0_0.xsd"> <!-- Parent --> <parent> <groupId>org.arquillian.extension</groupId> <artifactId>arquillian-droidium-screenshooter-build</artifactId> <version>0.0.1-SNAPSHOT</version> <relativePath>../arquillian-droidium-screenshooter-build/pom.xml</relativePath> </parent> <!-- Artifact Configuration --> <artifactId>arquillian-droidium-screenshooter-aspects</artifactId> <name>Arquillian Droidium Screenshooter AspectJ</name> <!-- Dependencies --> <dependencies> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>${version.aspectjrt}</version> </dependency> </dependencies> <!-- Properties --> <properties> <aspectj.source>1.6</aspectj.source> <aspectj.target>1.6</aspectj.target> <aspectj.compliance.level>1.6</aspectj.compliance.level> </properties> <!-- Build --> <build> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>aspectj-maven-plugin</artifactId> <version>${version.aspectj.maven.plugin}</version> <configuration> <source>${aspectj.source}</source> <target>${aspectj.target}</target> <complianceLevel>${aspectj.compliance.level}</complianceLevel> <verbose>true</verbose> </configuration> <executions> <execution> <goals> <goal>compile</goal> </goals> </execution> </executions> <dependencies> </dependencies> </plugin> </plugins> </build> </project>
You just have to use ordinary aspectj library on classpath and you have to say in your build that you want to compile your aspects with aspectj compiler (ajc) (it is bundled in these dependencies) so your code is prepared to be waived into the target test project.
Notice that this is very convenient way how to introduce AspectJ into Arquillian - you compile this artifact _once_ and you release it into Maven central as you would normaly do. The next step is to take this artifact and add it to your testing classpath when you are doing tests:
Let's say we have some profile in our test:
<profiles> <profile> <id>test01</id> <activation> <activeByDefault>true</activeByDefault> </activation> <dependencies> <!-- Arquillian Droidium container support --> <dependency> <groupId>org.arquillian.container</groupId> <artifactId>arquillian-droidium-container-depchain</artifactId> <version>${version.droidium}</version> <type>pom</type> <scope>test</scope> </dependency> <!-- Arquillian Droidium Native extension in order to be able to test native Android applications. --> <dependency> <groupId>org.arquillian.extension</groupId> <artifactId>arquillian-droidium-native-depchain</artifactId> <version>${version.droidium}</version> <type>pom</type> <scope>test</scope> </dependency> <!-- IMPORTANT --> <!-- For Arquillian Droidium screenshooter support --> <dependency> <groupId>org.arquillian.extension</groupId> <artifactId>arquillian-droidium-screenshooter-depchain</artifactId> <version>${version.droidium}</version> <type>pom</type> <scope>test</scope> </dependency> </dependencies> <!-- ^^^^^^^^^^^^^^^^^^^^^^ --> <!-- We have to waive our test classes for screenshooter support --> <build> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>aspectj-maven-plugin</artifactId> <version>1.4</version> <configuration> <aspectLibraries> <aspectLibrary> <groupId>org.arquillian.extension</groupId> <artifactId>arquillian-droidium-screenshooter-aspects</artifactId> </aspectLibrary> </aspectLibraries> <source>${aspects.source}</source> <target>${aspects.target}</target> <complianceLevel>${aspects.target}</complianceLevel> </configuration> <executions> <execution> <goals> <goal>test-compile</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </profile> </profiles>
You see you just add that arifact on testing class path (-depchain which contains that -aspects module), you point your build to aspectLibrary (that is your coded aspects), your tests are waived with your aspects with aspectj compiler and you are ready to go.
Now the whole point of this, your aspects in -aspects module looks like this:
package org.arquillian.droidium.screenshooter.aspect; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; @Aspect public class AssertionAspect { @Pointcut("execution(@org.junit.Test * *())") public void testMethodEntryPoint() { } @Before("testMethodEntryPoint()") public void executeBeforeEnteringTestMethod() { System.out.println("EXECUTE ACTION BEFORE ENTERING TEST METHOD"); } @After("testMethodEntryPoint()") public void executeAfterEnteringTestMethod() { System.out.println("EXECUTE ACTION AFTER ENTERING TEST METHOD"); } }
When you are not familiar with AspectJ basics, @Pointcut annotation marks a join point in your soruce code. When execution flow arrives to this join point, so called "advices" are applied to that join point and these advices are executed afterwards. So what this actually means is that when your code reach some method annotated with @org.junit.Test (our test method) which has whatever return value (*) and is in whatever package (*) and takes zero arguments (()) - this will be recognized as "testMethodEntryPoint" join point.
Now, you can write your advices as @Before ad @After your pointcut (and countless others since aspect programming is the whole new programming paradigm), so you easilly conclude that executeBeforeEnteringTestMethod is executed before you enter some test method. That's pretty cool.
And this is just a tip of an iceberg. When we return to our previous problem, how to execute something _before_ assertion call of Assert is executed, this is exactly the way to go, it would be something like
call(void org.junit.Assert.assert*(..))
saying that you have a join point which gets triggered when whatever method of Assert class (assert*) which takes any arguments (..) is reached. And this is the place when our screenshooter get called!
This is just a proof of concept and further investigation needs to be done but you have the very basic understanding of the problem.
What do you think? It is applicable in other areas of Arquillian as well? Would you like to see such extension?