Monday, July 2, 2012

Using Scripts for Unit Testing


Coming up with a readable, maintainable way to unit test service resource boundaries is an important part of service development. Commonly developers use source code implementations of their unit tests. This has the benefit of utilizing the same development language for your testing that you use for your development, but it has the disadvantages for being hard to read and being directly tied to your implementation. Readability problems result in costly maintenance as developers spend more time trying to figure out what a test is trying to do. Coupling to the implementation means that if you have a 100 coupling points that you now have 100 changes to make.


Consider the following Java example where a simple resource is requested and then the response is examined to make sure it contains the expected elements.

   /**     * Encoded URL should be decoded properly.     */     @Test     public void encodedQueryInfo() {         ClientRequest request = new ClientRequest(serviceId, "/echo/1?msg2=2&msg3=% 3D3%20cow");         ClientResponse response = ServiceRuntime.makeRequest(request);         Assert.assertTrue(response.isSuccessful());         Assert.assertTrue("msg was not found in the response", "1".equals(response.getData().getString("msg")));         Assert.assertTrue("msg2 was not found in the response", "2".equals(response.getData().getString("msg2")));         Assert.assertTrue("msg3 was not found in the response", "=3 cow".equals(response.getData().getString("msg3")));     }

There is nothing really special about this code, but it takes time to unwind what it is doing. We can attempt to clean up the example by embedding the payload string in the Java code, but this requires us to escape the string.


   /**     * Encoded URL should be decoded properly.     */     @Test
    public void getQueryInfo() {
        ClientRequest request = new ClientRequest(serviceId, "/echo/1?msg2=2&msg3=%3D3% 20cow");
        ClientResponse response = ServiceRuntime.makeRequest(request);
        Assert.assertTrue(response.isSuccessful());
        String expectedResponse = DataProperties.create(
            "{" +
                "\"msg\": \"1\"," +
                "\"msg2\": \"2\"," +
                "\"msg3\": \"=3 cow\"" +
            "}"
        ).toString(1);

        Assert.assertTrue("wrong response given", expectedResponse.equals(response.getData().toString(1)));
    }



The + and \" encoding makes it more difficult to maintain the test, and so we can try drop the encoding by putting it on one line and allowing single quotes, but that only trades the encoding problem for an long string readability problem.



    String expectedResponse = DataProperties.create("{'msg': '1', 'msg2': '2', 'msg3': '=3 cow'}").toString(1);

If we instead abandon our desire to use our implementation language as our testing language we can get closer to the actual interface and produce a much more human readable (i.e. maintainable) version of the test. This has the additional benefit of decoupling the code so that there is only one place to update whenever we change our backing implementation.

    "EncodedQueryInfo":{
        "description":"Encoded URL should be decoded properly.",
        "incoming":[
            {
                "request":{
                    "resource":"/echo/1?msg2=2&msg3=% 3D3%20cow",

                    "response":{
                        "body":{
                            "msg": "1",
                            "msg2": "2",
                            "msg3": "=3 cow"
                        }
                    }
                }
            }
        ]
    },