OUTCASTGEEK

tips, tricks, tuts, and more

getting started with unit testing using groovy


INTRODUCTION

Unit Testing:

  1. Method by which individual units of source code are tested to determine if they are fit for use.
  2. Internal structures or workings of an application are tested as opposed to its functionality.

Behavior-Driven Development:

  1. Agile software development technique that encourages collaboration between developers, QA and non-technical or business participants in a software project.
  2. Focuses on obtaining a clear understanding of desired software behavior through discussion with stakeholders. Extends Test Driven Development by writing test cases in a natural language that non-programmers can read.

Dependency Injection:

  1. Design pattern whose purpose is to improve testability of, and simplify deployment of components in very large software systems.
  2. Involves:
  3. a dependent consumer
  4. a declaration of a component's dependencies, defined as interface contracts
  5. an injector (sometimes referred to as a provider or container) that creates instances of classes that implement a given dependency interfaces on request.

Mock Objects:

  1. Simulated objects that mimic the behavior of real objects in controlled ways.
  2. Can simulate the behavior of complex, real (non-mock) objects and are therefore useful when a real object is impractical or impossible to incorporate into a unit test.

TOOLS

Some Common Tools

All good but just use

Groovy...

  • is an agile and dynamic language for the Java Virtual Machine
  • builds upon the strengths of Java but has additional power features inspired by languages like Python, Ruby and Smalltalk
  • makes modern programming features available to Java developers with almost-zero learning curve
  • supports Domain-Specific Languages and other compact syntax so your code becomes easy to read and maintain
  • makes writing shell and build scripts easy with its powerful processing primitives, OO abilities and an Ant DSL
  • increases developer productivity by reducing scaffolding code when developing web, GUI, database or console applications
  • simplifies testing by supporting unit testing and mocking out-of-the-box
  • seamlessly integrates with all existing Java classes and libraries
  • compiles straight to Java bytecode so you can use it anywhere you can use Java

When it comes to Unit and Behavior Testing groovy is your best friend

IDE Setup

Groovy Eclipse Plugin

The latest Groovy-Eclipse release is available from the following Eclipse update sites. To install, point your Eclipse update manager to the update site appropriate for your Eclipse version.

  • For Eclipse 3.7: http://dist.springsource.org/release/GRECLIPSE/e3.7/
  • For Eclipse 3.6: http://dist.springsource.org/release/GRECLIPSE/e3.6/
  • For Eclipse 3.5: http://dist.springsource.org/release/GRECLIPSE/e3.5/

EclEmma

EclEmma is a free Java code coverage tool for Eclipse, available under the Eclipse Public License. Internally it is based on the great EMMA Java code coverage tool, trying to adopt EMMA's philosophy for the Eclipse workbench:

  • Fast develop/test cycle: Launches from within the workbench like JUnit test runs can directly be analyzed for code coverage.
  • Rich coverage analysis: Coverage results are immediately summarized and highlighted in the Java source code editors.
  • Non-invasive: EclEmma does not require modifying your projects or performing any other setup.
  • The Eclipse integration has its focus on supporting the individual developer in an highly interactive way.

The update site for EclEmma is http://update.eclemma.org/.

As an alternative the following can be used:

SpringSource Tool Suite

SpringSource Tool Suiteā„¢ (STS) provides the best Eclipse-powered development environment for building Spring-powered enterprise applications. STS supplies tools for all of the latest enterprise Java, Spring, Groovy and Grails based technologies as well as the most advanced tooling available for enterprise OSGi development.

Both Groovy and EclEmma come nicely packaged as extensions to STS, and are available in the extensions tab of its dashboard.

Sample Groovy Unit and Behavior Test

Guidelines

  • Always write components to an interface
  • Avoid the new keyword; Let Spring create and inject your objects
  • Use groovy's map coercion to mock complex dependencies
  • Test in isolation; The test runtime is all you need
  • Translate requirements into test scenarios with the help of the stakeholders

Let's now take a look at a sample

Class Under Test: OrderSaveService.java

// Imports go here

public class OrderSaveService implements IOrderSaveService {
    private static Log logger = LogFactory.getLog(OrderSaveService.class);

    private IOrderSaveDao orderSaveDAO;
    private IRFXHelperService helperService;
    private OrderMemoPostService orderMemoPostService;
    private OrderInventoryUpdateService inventoryUpdateService;
    private OrderSerializedInventoryUpdateService serializedInventoryUpdateService;
    private ICredentialService credentialService;

    @Autowired //This Component will be create and injected by Spring at runtime
    private OrderSaveValidator validator;

  // Getters and Setters go here

  @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void validateAndSaveOrder(Orders order) {
        validator.validateOrder(order, false, null);
        Boolean suspicious = checkSuspiciousCustomer(order.getCustomerLastName(),order.getCustomerFirstName(),order.getCustomerSsnorTin());

        if (suspicious) {
            order.setSuspiciousCustomer(true);
            saveSuspiciousCustomer(order);
        }

        orderSaveDAO.save(order);
  }
}

Unit Test: OrderSaveServiceGTest.groovy

//Imports go here

class OrderSaveServiceGTest {

    def isSaved = false
    def markedSuspicious = false
    def suspiciousCustomerSaved = false
    def orderSaveDAO = null
    def validator = null
    def orders = null

    @Before
    public void setUp() throws Exception {
        orderSaveDAO = [save:{entity ->
                          isSaved = true
                                print "\nSaving order..."
                       }, setOrderEntrySite:{order ->
                                print "\nSetting order entry site..."
                               }, setOrderShippingOption:{order, customerProfile ->
                                print "\nSetting order shipping option..."
                               }, saveOrder:{ order, excludeStatusHistory ->
                                print "\nSaving order..."
                      return new Orders()
                    }, updateOrder:{order, excludeStatusHistory ->
                      print "\nUpdating order..."
                    }, findByCriteria:{criteria ->
                      print "\n Finding by criteria..."
                    }] as IOrderSaveDao            
        validator = [validateOrder:{order, isBroker, customerProfile ->
                           print "\nValidating order..."
                           }] as OrderSaveValidator
    orders = [setSuspiciousCustomer:{ bool ->
                    markedSuspicious = bool
                        print "\nMarking order as suspicious..."
                    }] as Orders
    }

    @Test
    public void testValidateAndSaveOrder() {
        assert !isSaved

        orderSaveService = new OrderSaveService() {
            @Override
            public  Boolean checkSuspiciousCustomer(String lastName, String firstName, String ssn) {
                return true
            }

      @Override
            protected void saveSuspiciousCustomer(Orders orders) {
                suspiciousCustomerSaved = true
                print "\nSaving suspicious customer..."
            }
        }
        orderSaveService.setDao(orderSaveDAO)
        orderSaveService.setValidator(validator)

        orderSaveService.validateAndSaveOrder(orders)

    assert markedSuspicious
        assert suspiciousCustomerSaved
        assert isSaved
    }

  @After
    public void tearDown() throws Exception {
        isSaved = false
        markedSuspicious = false
        suspiciousCustomerSaved = false
        orderSaveDAO = null
        validator = null
        orders = null
  }
}

Requirements in Plain English: OrderPlacementScenarios.txt

  • Placing a new order Scenario:
  • Given a new order
  • When a customer is trustworthy
  • Then it is ok to save to directly save the order

  • Placing a new suspicious order Scenario:

  • Given a new order
  • When a customer is not trustworthy
  • Then both the suspicious customer and the order should be saved with the order marked suspicious

Pay special attention to the given, when and then keywords, and notice how they are used in the following behavior test...

Behavior Test: OrderSaveServiceStory.groovy

  //Imports go here

  /**
   * Order Validation and Save with a Trustworthy Customer scenario
  */
  scenario "make a new order", {
      given "a new order",{
            isSaved = false
            orderSaveDAO = [save:{entity ->
                                isSaved = true
                                      print "\nSaving order..."
                              }] as IOrderSaveDao
              validator = [validateOrder:{order, isBroker, customerProfile ->
                             print "\nValidating order..."
                             }] as OrderSaveValidator
              orders = [] as Orders
      }

      when "a customer is trustworthy", {
            orderSaveService = new OrderSaveService() {
                  @Override
                  public  Boolean checkSuspiciousCustomer(String lastName, String firstName, String ssn) {
                        return false
                  }
             }
             orderSaveService.setDao(orderSaveDAO)
             orderSaveService.setValidator(validator)
       }

       then "it is ok to save to directly save the order", {
             orderSaveService.validateAndSaveOrder(orders)
                 isSaved.shouldBe true
           }
       }
    }

  /**
   * Order Validation and Save with a Suspicious Customer scenario
  */
  scenario "make a new suspicious order", {
      given "a new order",{
            isSaved = false
            markedSuspicious = false
            orderSaveDAO = [save:{entity ->
                                      isSaved = true
                                      print "\nSaving order..."
                                      }] as IOrderSaveDao
          validator = [validateOrder:{order, isBroker, customerProfile ->
                                 print "\nValidating order..."
                                 }] as OrderSaveValidator
              orders = [setSuspiciousCustomer:{ bool ->
                          markedSuspicious = bool
                              print "\nMarking order as suspicious..."
                          }] as Orders
      }

      when "a customer is trustworthy", {
            orderSaveService = new OrderSaveService() {
                  @Override
                  public  Boolean checkSuspiciousCustomer(String lastName, String firstName, String ssn) {
                        return true
                  }
                  @Override
                  protected void saveSuspiciousCustomer(Orders orders) {
                        print "\nSaving suspicious customer..."
                  }
            }
            orderSaveService.setDao(orderSaveDAO)
            orderSaveService.setValidator(validator)
      }

      then "both the suspicious customer and the order should be saved with the order marked suspicious", {
            orderSaveService.validateAndSaveOrder(orders)
            markedSuspicious.shouldBe true
            isSaved.shouldBe true
      }
  }

CONCLUSION

  • Groovy's syntax comes handy to mock the complex dependencies without having to reach for a third party tool.
  • Java's syntax is valid Groovy's syntax. Groovy = Java + Additional Power Features.
  • Groovy lets you test private methods, as private Java methods are alway accessible from Groovy.
  • Writing components to an interface, and having Spring create and inject the dependencies provides you with a very testable system.
  • The new keyword makes components untestable.
  • Test scenarios can come from the stakeholders as plain english text, and be translated to behavior tests.
  • Good code coverage is usually in the upper seventies, as getters and setters are usually left out.

Any questions? Comments? Suggestions?


❯❯ Back to Blog ❮❮ ❮ Previous: build your polyglot app with jruby and rake Next: tornado static content ❯
blog comments powered by Disqus