Eradicate Duplication; Embrace Communication
Yesterday, I spent some time cleaning up Acceptance Tests on a project which exposes some REST APIs.
Following is a snippet of one of the tests:
1 | Response response = REST_API_call_Using_Wrapper Which_wraps_xml_response_in_a_response_helper_object; |
1 2 3 4 5 6 7 8 9 10 | Assert.IsTrue(response.HasHeader); Assert.IsTrue(response.HasMessageId); Assert.IsTrue(response.Has("X-SenderIP: " + senderIp)); Assert.IsTrue(response.Has("X-SenderDomain: " + senderDomain)); Assert.IsTrue(response.Has("X-recipientDomain: " + recipientDomain)); Assert.IsTrue(response.Has("X-SPF: " + spfValue)); Assert.IsTrue(response.Has("X-1stClassification: " + firstClassificationResult)); Assert.IsTrue(response.Has("X-2ndClassification: " + secondClassificationResult)); Assert.IsTrue(response.Has("X-3rdClassification: " + thirdClassificationResult)); Assert.IsTrue(response.Has("X-MANUALLY-CLASSIFIED: " + manuallyClassified)); |
As you can see there is a lot of duplication (Assert.IsTrue is basically noise). It’s also not very clear what the intent of those assert is.
Since Response is a Test Helper class. We thought moving the asserts on the response makes sense. But we also want to make sure the person reading this test understands that we are verifying a bunch of things on the response object.
Since we are using C#, we could do the following using a Delegate.
1 | public delegate void ThingsToBeVerified(); |
1 2 3 4 | public void AssertThat(ThingsToBeVerified codeBlock) { codeBlock(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | response.AssertThat( delegate{ response.HasHeader; response.HasMessageId; response.Has("X-SenderIP: " + senderIp); response.Has("X-SenderDomain: " + senderDomain); response.Has("X-recipientDomain: " + recipientDomain); response.Has("X-SPF: " + spfValue); response.Has("X-1stClassification: " + firstClassificationResult); response.Has("X-2ndClassification: " + secondClassificationResult); response.Has("X-3rdClassification: " + thirdClassificationResult); response.Has("X-MANUALLY-CLASSIFIED: " + manuallyClassified); } ); |
Now that we got the asserts out of the way. The following things stand-out as redundant:
- The repeating response word
- The semicolon at the end of each line
- The ‘: ” + ‘ in each Has call
So we got rid of the delegate and used Method Chaining (fluent interfaces) instead. (Other samples of using Fluent Interfaces in Tests)
1 2 3 4 5 6 7 8 9 10 11 | response.AssertThat.It .HasHeader .HasMessageId .Has("X-SenderIP",senderIp) .Has("X-SenderDomain",senderDomain) .Has("X-recipientDomain", recipientDomain) .Has("X-SPF", spfValue) .Has("X-1stClassification", firstClassificationResult) .Has("X-2ndClassification", secondClassificationResult) .Has("X-3rdClassification", thirdClassificationResult) .Has("X-MANUALLY-CLASSIFIED", manuallyClassified); |
Now the Has call and the parentheses looks redundant. One way to eliminate that is by using Operator overloading, something like:
lets.checkThat(response).HasHeader.HasMessageId.Has + "X-SenderIP" = senderIp + "X-SenderDomain" = senderDomain + "X-recipientDomain" = recipientDomain + "X-SPF" = spfValue + "X-1stClassification" = firstClassificationResult + "X-2ndClassification" = secondClassificationResult + "X-3rdClassification" = thirdClassificationResult + "X-MANUALLY-CLASSIFIED" = manuallyClassified; |
We have not implemented this, but technically its possible to do this.