Category Archives: Groovy

Groovy + GPars: Handling Concurrency

PROBLEM

Let’s assume given a list of user IDs (ex: 1, 2, 3, 4, and 5), we need to query 2 data sources to get the names and the addresses before returning a list of Employee objects. The Employee class looks like this:-

@ToString(includePackage = false)
class Employee {
    Long id
    String name
    String address
}

To simplify the example, let’s assume the name lookup takes 2 seconds to run and the address lookup takes 4 seconds to run.

And now, we have the following code:-

class App {
    String nameLookup(Integer id) {
        log("Name lookup: ${id}")
        Thread.sleep(2000)
        "User ${id}"
    }

    String addressLookup(Integer id) {
        log("Addr lookup: ${id}")
        Thread.sleep(4000)
        "Address ${id}"
    }

    void log(String message) {
        println "${Thread.currentThread()} - ${new Date().format('HH:mm:ss')} - ${message}"
    }

    List<Employee> lookup(List<Integer> userIds) {
        ???
    }

    void run() {
        def start = System.currentTimeMillis()

        def employees = lookup([1, 2, 3, 4, 5])

        def end = System.currentTimeMillis()

        println '---------------------------------'
        println "Total time in seconds: ${(end - start) / 1000}"
        println '---------------------------------'

        employees.each {
            println it
        }
    }

    static void main(String[] args) {
        new App().run()
    }
}

We are going to explore multiple solutions to implement lookup(..) to run as fast as possible.

ATTEMPT 1

The simplest and the most straightforward approach is to perform the task synchronously.

Code:-

List<Employee> lookup(List<Integer> userIds) {
    userIds.collect { id ->
        new Employee(
                id: id,
                name: nameLookup(id),
                address: addressLookup(id)
        )
    }
}

Output:-

Thread[main,5,main] - 20:54:09 - Name lookup: 1
Thread[main,5,main] - 20:54:11 - Addr lookup: 1
Thread[main,5,main] - 20:54:15 - Name lookup: 2
Thread[main,5,main] - 20:54:17 - Addr lookup: 2
Thread[main,5,main] - 20:54:21 - Name lookup: 3
Thread[main,5,main] - 20:54:23 - Addr lookup: 3
Thread[main,5,main] - 20:54:27 - Name lookup: 4
Thread[main,5,main] - 20:54:29 - Addr lookup: 4
Thread[main,5,main] - 20:54:33 - Name lookup: 5
Thread[main,5,main] - 20:54:35 - Addr lookup: 5
---------------------------------
Total time in seconds: 30.129
---------------------------------
Employee(1, User 1, Address 1)
Employee(2, User 2, Address 2)
Employee(3, User 3, Address 3)
Employee(4, User 4, Address 4)
Employee(5, User 5, Address 5)

Since everything runs synchronously in one thread, it takes about 30 seconds to complete.

ATTEMPT 2

In this attempt and the rest of the attempts, we are going to use GPars, which is a concurrency and parallelism library.

<dependency>
    <groupId>org.codehaus.gpars</groupId>
    <artifactId>gpars</artifactId>
    <version>1.2.1</version>
</dependency>

In this attempt, we are going to use GParsPool.executeAsyncAndWait(..).

Code:-

List<Employee> lookup(List<Integer> userIds) {
    (List<Employee>) GParsPool.withPool {
        userIds.collect { id ->
            def results = GParsPool.executeAsyncAndWait(
                    this.&nameLookup.curry(id),
                    this.&addressLookup.curry(id)
            )

            new Employee(
                    id: id,
                    name: results[0],
                    address: results[1]
            )
        }
    }
}

Output:-

Thread[ForkJoinPool-1-worker-2,5,main] - 20:54:40 - Addr lookup: 1
Thread[ForkJoinPool-1-worker-1,5,main] - 20:54:40 - Name lookup: 1
Thread[ForkJoinPool-1-worker-2,5,main] - 20:54:44 - Name lookup: 2
Thread[ForkJoinPool-1-worker-1,5,main] - 20:54:44 - Addr lookup: 2
Thread[ForkJoinPool-1-worker-1,5,main] - 20:54:48 - Name lookup: 3
Thread[ForkJoinPool-1-worker-2,5,main] - 20:54:48 - Addr lookup: 3
Thread[ForkJoinPool-1-worker-1,5,main] - 20:54:52 - Addr lookup: 4
Thread[ForkJoinPool-1-worker-2,5,main] - 20:54:52 - Name lookup: 4
Thread[ForkJoinPool-1-worker-2,5,main] - 20:54:56 - Addr lookup: 5
Thread[ForkJoinPool-1-worker-1,5,main] - 20:54:56 - Name lookup: 5
---------------------------------
Total time in seconds: 20.184
---------------------------------
Employee(1, User 1, Address 1)
Employee(2, User 2, Address 2)
Employee(3, User 3, Address 3)
Employee(4, User 4, Address 4)
Employee(5, User 5, Address 5)

This allows us to run both the name lookup and the address lookup concurrently for each user ID.

Speed gain compared to first attempt: 1.5x

ATTEMPT 3

How about collectParallel(..)?

Code:-

List<Employee> lookup(List<Integer> userIds) {
    (List<Employee>) GParsPool.withPool {
        userIds.collectParallel { Integer id ->
            new Employee(
                    id: id,
                    name: nameLookup(id),
                    address: addressLookup(id)
            )
        }
    }
}

Output:-

Thread[ForkJoinPool-2-worker-1,5,main] - 20:55:00 - Name lookup: 1
Thread[ForkJoinPool-2-worker-4,5,main] - 20:55:00 - Name lookup: 4
Thread[ForkJoinPool-2-worker-3,5,main] - 20:55:00 - Name lookup: 2
Thread[ForkJoinPool-2-worker-2,5,main] - 20:55:00 - Name lookup: 3
Thread[ForkJoinPool-2-worker-5,5,main] - 20:55:00 - Name lookup: 5
Thread[ForkJoinPool-2-worker-4,5,main] - 20:55:02 - Addr lookup: 4
Thread[ForkJoinPool-2-worker-3,5,main] - 20:55:02 - Addr lookup: 2
Thread[ForkJoinPool-2-worker-1,5,main] - 20:55:02 - Addr lookup: 1
Thread[ForkJoinPool-2-worker-2,5,main] - 20:55:02 - Addr lookup: 3
Thread[ForkJoinPool-2-worker-5,5,main] - 20:55:02 - Addr lookup: 5
---------------------------------
Total time in seconds: 6.156
---------------------------------
Employee(1, User 1, Address 1)
Employee(2, User 2, Address 2)
Employee(3, User 3, Address 3)
Employee(4, User 4, Address 4)
Employee(5, User 5, Address 5)

This allows us to run all name lookups concurrently, followed by all address lookup concurrently.

Speed gain compared to first attempt: 4.9x

ATTEMPT 4

Perhaps, another approach is to kick start all the lookups asynchronously and hold on to the returned Future objects:-

Code:-

List<Employee> lookup(List<Integer> userIds) {
    (List<Employee>) GParsPool.withPool {
        List<Future> nameFutures = userIds.collect {
            this.&nameLookup.callAsync(it)
        }

        List<Future> addressFutures = userIds.collect {
            this.&addressLookup.callAsync(it)
        }

        userIds.withIndex().collect { Integer id, Integer i ->
            new Employee(
                    id: id,
                    name: nameFutures[i].get(),
                    address: addressFutures[i].get()
            )
        }
    }
}

Output:-

Thread[ForkJoinPool-3-worker-5,5,main] - 20:55:06 - Name lookup: 5
Thread[ForkJoinPool-3-worker-4,5,main] - 20:55:06 - Name lookup: 4
Thread[ForkJoinPool-3-worker-2,5,main] - 20:55:06 - Name lookup: 2
Thread[ForkJoinPool-3-worker-3,5,main] - 20:55:06 - Name lookup: 3
Thread[ForkJoinPool-3-worker-1,5,main] - 20:55:06 - Name lookup: 1
Thread[ForkJoinPool-3-worker-3,5,main] - 20:55:08 - Addr lookup: 4
Thread[ForkJoinPool-3-worker-2,5,main] - 20:55:08 - Addr lookup: 3
Thread[ForkJoinPool-3-worker-4,5,main] - 20:55:08 - Addr lookup: 2
Thread[ForkJoinPool-3-worker-5,5,main] - 20:55:08 - Addr lookup: 1
Thread[ForkJoinPool-3-worker-1,5,main] - 20:55:08 - Addr lookup: 5
---------------------------------
Total time in seconds: 6.032
---------------------------------
Employee(1, User 1, Address 1)
Employee(2, User 2, Address 2)
Employee(3, User 3, Address 3)
Employee(4, User 4, Address 4)
Employee(5, User 5, Address 5)

It’s slightly better than the previous attempt, but the performance gain is rather negligible.

Speed gain compared to first attempt: 5.0x

ATTEMPT 5

What if we take the previous attempt and increase the number of threads to 10?

Code:-

List<Employee> lookup(List<Integer> userIds) {
    GParsPool.withPool 10, {
        List<Future> nameFutures = userIds.collect {
            this.&nameLookup.callAsync(it)
        }

        List<Future> addressFutures = userIds.collect {
            this.&addressLookup.callAsync(it)
        }

        userIds.withIndex().collect { Integer id, Integer i ->
            new Employee(
                    id: id,
                    name: nameFutures[i].get(),
                    address: addressFutures[i].get()
            )
        }
    }
}

Output:-

Thread[ForkJoinPool-4-worker-1,5,main] - 20:55:12 - Name lookup: 1
Thread[ForkJoinPool-4-worker-2,5,main] - 20:55:12 - Name lookup: 2
Thread[ForkJoinPool-4-worker-3,5,main] - 20:55:12 - Name lookup: 3
Thread[ForkJoinPool-4-worker-4,5,main] - 20:55:12 - Name lookup: 4
Thread[ForkJoinPool-4-worker-5,5,main] - 20:55:12 - Name lookup: 5
Thread[ForkJoinPool-4-worker-6,5,main] - 20:55:12 - Addr lookup: 1
Thread[ForkJoinPool-4-worker-8,5,main] - 20:55:12 - Addr lookup: 3
Thread[ForkJoinPool-4-worker-7,5,main] - 20:55:12 - Addr lookup: 2
Thread[ForkJoinPool-4-worker-9,5,main] - 20:55:12 - Addr lookup: 4
Thread[ForkJoinPool-4-worker-10,5,main] - 20:55:12 - Addr lookup: 5
---------------------------------
Total time in seconds: 4.016
---------------------------------
Employee(1, User 1, Address 1)
Employee(2, User 2, Address 2)
Employee(3, User 3, Address 3)
Employee(4, User 4, Address 4)
Employee(5, User 5, Address 5)

Speed gain compared to first attempt: 7.5x

And now, we have successfully improve our implementation performance from 30 seconds to 4 seconds.

Groovy: Log Annotation

PROBLEM

My personal goal of 2017 is to write less useless-yet-neccessary code, such as initializing a logger:-

import org.slf4j.Logger
import org.slf4j.LoggerFactory

class MyClass {
    static Logger LOGGER = LoggerFactory.getLogger(MyClass)

    static void main(String[] args) {
        LOGGER.debug('TEST')
    }
}

SOLUTION

Groovy provides several useful annotations that allow us to inject a logger instance into the class.

Here’s a rewrite of the above example:-

import groovy.util.logging.Slf4j

@Slf4j
class MyClass {
    static void main(String[] args) {
        log.debug('TEST')
    }
}

If we insist of using the same variable ( LOGGER ), we can do this:-

import groovy.util.logging.Slf4j

@Slf4j(value = 'LOGGER')
class MyClass {
    static void main(String[] args) {
        LOGGER.debug('TEST')
    }
}

We can also use @Log for Java Util Logger, @Log4j for Log4j and @Commons for Apache Common Logging.

LdapTemplate: javax.naming.PartialResultException: Unprocessed Continuation Reference(s); remaining name ‘…’

BACKGROUND

Let’s assume we have the following LDAP configuration…

@Configuration
class LdapConfig {
    @Bean
    AuthenticationSource getAuthenticationSource(AppConfigService appConfigService) {
        return new AuthenticationSource() { ... }
    }

    @Bean
    ContextSource contextSource(AuthenticationSource authenticationSource) {
        return new LdapContextSource(
                authenticationSource: authenticationSource,
                url: 'ldap://server:389',
                base: 'dc=domain'
        )
    }

    @Bean
    LdapTemplate getLdapTemplate(ContextSource contextSource) {
        return new LdapTemplate(contextSource: contextSource)
    }
}

When running any LDAP query, the following exception is thrown:-

Caused by: javax.naming.PartialResultException: Unprocessed Continuation Reference(s); remaining name '/'
	at com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:2846)
	at com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:2820)
	at com.sun.jndi.ldap.LdapNamingEnumeration.getNextBatch(LdapNamingEnumeration.java:129)
	at com.sun.jndi.ldap.LdapNamingEnumeration.hasMoreImpl(LdapNamingEnumeration.java:198)
	at com.sun.jndi.ldap.LdapNamingEnumeration.hasMore(LdapNamingEnumeration.java:171)
	at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:365)

SOLUTION

There are 3 solutions to this problem.

Query against Gobal Catalog

To prevent the referral issues when dealing with Active Directory, we may query against the Global Catalog by using port 3268.

@Bean
ContextSource contextSource(AuthenticationSource authenticationSource) {
    return new LdapContextSource(
            authenticationSource: authenticationSource,
            url: 'ldap://server:3268',
            base: 'dc=domain'
    )
}

The possible downside to this approach is the Global Catalog may not have the pertinent data we need, such as employeeID, etc.

Configure Referral to Follow

We can configure LdapTemplate to automatically follow any referrals.

@Bean
ContextSource contextSource(AuthenticationSource authenticationSource) {
    return new LdapContextSource(
            authenticationSource: authenticationSource,
            url: 'ldap://server:389',
            base: 'dc=domain',
            referral: 'follow'
    )
}

The downside to this approach is it makes the query much slower. Based on my testing, it is at least 5 to 10 seconds slower.

Ignore Exception

Sometimes, it pays to read the JavaDoc. Based on the LdapTemplate’s documentation, it says…

Note for Active Directory (AD) users: AD servers are apparently unable to handle referrals automatically, which causes a PartialResultException to be thrown whenever a referral is encountered in a search. To avoid this, set the ignorePartialResultException property to true. There is currently no way of manually handling these referrals in the form of ReferralException, i.e. either you get the exception (and your results are lost) or all referrals are ignored (if the server is unable to handle them properly. Neither is there any simple way to get notified that a PartialResultException has been ignored (other than in the log).

Bada Bing, Bada Boom…

@Bean
LdapTemplate getLdapTemplate(ContextSource contextSource) {
    return new LdapTemplate(
            contextSource: contextSource,
            ignorePartialResultException: true
    )
}

LdapTemplate: AttributesMapper vs ContextMapper

BACKGROUND

When using Spring’s LdapTemplate, there are two ways to transform the queried results: AttributesMapper and ContextMapper.

List<MyBean> list = ldapTemplate.search(
    '',
    '(cn=some-group-name)',
    // AttributesMapper or ContextMapper 
)

Here’s the comparison between these mapper classes.

AttributesMapper

If you are migrating your existing LDAP queries to Spring’s LdapTemplate, AttributesMapper seems ideal because you can copy most of the code over because it provides javax.naming.directory.Attributes.

List<MyBean> list = ldapTemplate.search(
    '',
    '(cn=some-group-name)',
    new AttributesMapper<MyBean>() {
        @Override
        MyBean mapFromAttributes(final Attributes attributes) throws NamingException {
            return new MyBean(
                cn: attributes.get('cn')?.get(),
                members: attributes.get('member')?.getAll()?.toSet() as Set<String> ?: []
            )
        }
    }
)

However, you have to handle possible null values if the attribute keys do not exist.

ContextMapper

With ContextMapper, it handles null values for us. Spring also provides an abstract class called AbstractContextMapper to further simplify the code.

List<MyBean> list = ldapTemplate.search(
    '',
    '(cn=some-group-name)',
    new AbstractContextMapper<MyBean>() {
        @Override
        protected MyBean doMapFromContext(final DirContextOperations ctx) {
            return new MyBean(
                cn: ctx.getStringAttribute('cn'),
                members: ctx.getStringAttributes('member')
            )
        }
    }
)

Spring: Component Scan Selected Classes

PROBLEM

Let’s assume we have a package with the following classes where each class is either annotated with Spring’s @Service, @Component, @Controller or @Repository.

app
├── A.groovy
├── B.groovy
├── C.groovy
├── D.groovy
└── E.groovy

When writing unit test, we want Spring to component scan class A and class B.

SOLUTION

Before we begin, we configure Log4j to log Spring in debug level.

<logger name="org.springframework">
    <level value="debug"/>
</logger>

Step 1

If we configure the test class like this…

@ContextConfiguration
class ASpec extends Specification {
    @Configuration
    @ComponentScan(
            basePackageClasses = [A]
	)
    static class TestConfig {
    }

    def "..."() {
        // ...
    }
}

It will scan all Spring components that reside in the same package as class A.

Debugging log:-

[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/A.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/B.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/C.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/D.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/E.class]

Step 2

We can set includeFilters to include just class A and class B…

@ContextConfiguration
class ASpec extends Specification {
    @Configuration
    @ComponentScan(
            basePackageClasses = [A],
            includeFilters = [@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = [A, B])]
	)
    static class TestConfig {
    }

    def "..."() {
        // ...
    }
}

… but it doesn’t do anything.

Debugging log:-

[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/A.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/B.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/C.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/D.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/E.class]

Step 3

To fix this, we set useDefaultFilters to false to disable any automatic detection of classes annotated with Spring’s @Service, @Component, @Controller or @Repository.

@ContextConfiguration
class ASpec extends Specification {
    @Configuration
    @ComponentScan(
            basePackageClasses = [A],
            useDefaultFilters = false,
            includeFilters = [@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = [A, B])]
    )
    static class TestConfig {
    }

    def "..."() {
        // ...
    }
}

Now, we get the intended behavior.

Debugging log:-

[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/A.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/B.class]

Better Preconditions: v0.1.0

DEPENDENCY

<dependency>
  <groupId>com.github.choonchernlim</groupId>
  <artifactId>better-preconditions</artifactId>
  <version>0.1.0</version>
</dependency>

Introduction

The goal of Better Preconditions is to provide a set of Java APIs that allows developers to create succinct, yet readable and testable preconditions.

Why Write Preconditions?

Let’s assume we have the following code:-

public void save(final String name, final LocalDate birthDate) {
    dao.save(new Entity(name.toUpperCase(), birthDate));
}

Although this example is simple and trivial, every developer that looks at this code will interpret this API differently. For example:-

  • Can name be blank?
  • What if we pass in a null value for name?
  • Can birth date be null?
  • Can birth date be after today’s date?

The truth of the matter is we cannot create an API that handles every possible scenario. Otherwise, we will never get our products out the door.

Thus, it is better to safeguard our API with a set of preconditions to ensure our fellow developers (or even you) know what this API needs before it performs the real work. Further, it provides a living documentation that would never go stale. Here’s an example of what the preconditions might look like:-

public void save(final String name, final LocalDate birthDate) {
    // precondition 1: name cannot be blank
    // precondition 2: birth date cannot be null
    // precondition 3: birth date cannot be after today's date

    dao.save(new Entity(name.toUpperCase(), birthDate));
}

What’s wrong with Guava Preconditions?

The biggest advantage of using Guava Preconditions is its flexibility. The biggest disadvantage of it is also its flexibility. Here’s an example written with Guava Preconditions:-

public void save(final String name, final LocalDate birthDate) {
    checkArgument(!nullToEmpty(name).trim().isEmpty(), "Name cannot be blank");
    checkNotNull(birthDate, "Birth date cannot be null");
    checkArgument(!birthDate.isAfter(LocalDate.now()), "Birth date cannot be after today's date");

    dao.save(new Entity(name.toUpperCase(), birthDate));
}

A couple of observations:-

  • Too verbose, which makes the code very messy. If we start inserting the raw values into the error messages, it becomes even messier.
  • We use checkArgument(..) and checkNotNull(..) most of the time. When an exception is thrown, we either get IllegalArgumentException or NullPointerException. This makes the API very confusing to unit test if there are many preconditions.

Introducing the power of Better Preconditions

Let’s rewrite the example above with Better Preconditions:-

public void save(final String name, final LocalDate birthDate) {
    expect(name, "Name")
            .not().toBeBlank()
            .check();

    expect(birthDate, "Birth Date")
            .not().toBeNull()
            .not().toBeAfter(LocalDate.now(), "Today's Date")
            .check();

    dao.save(new Entity(name.toUpperCase(), birthDate));
}

A couple of observations:-

  • Short and succinct.
  • Each thrown exception provides very useful error message for debugging purpose. For example, if birth date is after today’s date, the error message would be Birth Date [ 2015-02-01 ] must not be after Today's Date [ 2015-01-01 ]
  • When one of the preconditions fails, a specific exception is thrown. For example:-
    • blank name throws StringBlankPreconditionException
    • null birth date throws ObjectNullPreconditionException
    • birth date after today’s date throws JodaTimeAfterPreconditionException

    This makes it simpler to unit test because we can easily catch specific exception without any doubts.

Conclusion

Lastly, play around with it. This post barely scratches the surface of what Better Preconditions can do for you. If it works for you, great. If it doesn’t work for you and you are still interested to use it, create an issue at my GitHub page so that I can fix it. Due to my current real world workload, I’m using the 80/20 rule… the provided APIs should solve 80% of the problem.

To learn more about Better Preconditions, visit https://github.com/choonchernlim/better-preconditions

Guava: Testing equals(..) and hashcode(..)

PROBLEM

Let’s assume we want to test the following equals(..):-

public class Person {
    private String name;
    private int age;

    @Override
    public boolean equals(Object o) {
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        Person person = (Person) o;
        return Objects.equal(name, person.name);
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

A correctly implemented equals(..) must be reflexive, symmetric, transitive, consistent and handles null comparison.

In another word, you have to write test cases to pass at least these 5 rules. Anything less is pure bullshit.

SOLUTION

You can write these tests yourself… or you can leverage Guava’s EqualsTester. This library will test these 5 rules and ensure the generated hashcode matches too.

First, include the needed dependency:-

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava-testlib</artifactId>
    <version>18.0</version>
    <scope>test</scope>
</dependency>

Instead of writing JUnit tests, I’ll be writing Spock specs, which is essentially built on top of Groovy, because it allows me to write very clear and clean tests.

class PersonSpec extends Specification {

    def person = new Person(name: 'Mike', age: 10)

    def "equals - equal"() {
        when:
        new EqualsTester().
                addEqualityGroup(person,
                                 new Person(name: 'Mike', age: 10),
                                 new Person(name: 'Mike', age: 20)).
                testEquals()

        then:
        notThrown(AssertionFailedError.class)
    }

    def "equals - not equal"() {
        when:
        new EqualsTester().
                addEqualityGroup(person,
                                 new Person(name: 'Kurt', age: 10)).
                testEquals()

        then:
        thrown(AssertionFailedError.class)
    }
}

Oh… wait for it… BOOM!