22 February 2017

Using Spockframework to unroll donzs of test cases from a few of them

Part of working on spwrap, I am working to writing unit and integration tests form this tiny framework.

I've used spockframework to write some test cases on a project in my current company, I find it very handy and complete framework.

Because spockframework is written in Groovy, it provides very dynamic features that is hard to achieve in other Java-based testing frameworks, at least the syntax of other testing/mocking frameworks will not as good as Spock.

Besides Spock is provides basic testing functionality, it provides what they called "interaction-based testing" (a.k.a. Mocking) and one amazing feature what is "Data Driven testing"

In this post I'll talk about how I used both of them to write 8 test cases and got more than 150 unit test generated.

One of the basic spwrap features is to let user call stored procedures that return result sets and output parameters, and it is user responsibility to extract the data from these JDBC interfaces.

This mapping is done in Mappers, where the user have to implement one of two interfaces either ResultSetMapper or TypedOutputParameterMapper.

example of class implements both interfaces:


public class Customer implements TypedOutputParamMapper<Customer>, ResultSetMapper<Customer> {

    private Integer id;
    private String firstName, lastName;

    @Override
    public Customer map(Result<?> result) {
        if (result.isResultSet()) {// for ResultSetMapper
            return new Customer(result.getInt(1), result.getString(2), result.getString(3));
        } else { // for TypedOutputParamMapper
            return new Customer(null, result.getString(1), result.getString(2));
        }
    }

The map function above have one parameter of Type Result, which is a wrapper for both java.sql.ResultSet and java.sql.CallableStatement

The Result class has two subclasses (ResultSetWrapper and CallableStatementWrapper) that delegate the call to ResultSet and CallableStatement respectively and re-throw SQLException as non-checked CallException.

Each class of the Wrapper classes (ResultSetWrapper and CallableStatementWrapper) has about 40 methods like getString, getBoolean, getByte, getShort, getInt, getLong, getFloat, etc.

So, I need to write about 40 * 2 (1 success path and 1 fail path) * 2 (2 classes to test) ~= 160 method.

so let's see how we accomplish this using spock:


def callableStatementWrapper
def callableStatementMock = Mock(CallableStatement)

@Shared METHOD_NAMES = ["getString", "getBoolean", "getByte", "getShort", "getInt",
                        "getLong", "getFloat", "getDouble", "getBytes", "getDate", "getTime",
                        "getTimestamp", "getObject", "getBigDecimal", "getRef", "getBlob", "getClob",
                        "getArray", "getURL"];
void setup() {
    callableStatementWrapper = new CallableStatementWrapper(callableStatementMock, 1)
}

def "calling #methodName(int) on CallableStatementWrapper calls the same method name on CallableStatement" (String methodName){
    when:
        callableStatementWrapper."$methodName"(1)
    then:
        1 * callableStatementMock./get.*/(_)
    where:
        methodName << METHOD_NAMES
}

The first line is just a definition for a reference that will hold the object that we need to test which is a CallableStatementWrapper.

The second line is mocking the java.sql.CallableStatement into an variable named callableStatementMock

then we have a static (shared) field of type array of string, actually these are method names on CallableStatementWrapper we need to test.

The setup method instantiate the CallableStatementWrapper using the callableStatementMock mocked object.

the test method do 2 important things:

1. In the when block we say: whenever the user calls callableStatementWrapper. (some dynamic method name that we will supply later in the where block)

2. in the then block: we say, expect 1 call to the mocked object getXXX method that takes any parameter (see interaction-based testing for more details)

3. in the where block, we substitute the methodName in step 1 (when step) by the method name from the static array of method names METHOD_NAMES Array.

When run this test,  and because of the class annotate by @Unroll, we got about 20 test case runs for us.

see the test cases on github for CallableStatementWrapper and ResultSetWrapper.

Hope you can find spock and spwrap helpful!




No comments: