Closing a Safer Deal — Contract and Collaboration Tests working together to improve security

Henrique Cassus
mercos-engineering
Published in
5 min readJun 8, 2016

After watching this interesting talk from JBrains, I could understand the mathematics of the exponential growth in integrated test cases opposed to the linear growth of Collaboration + Contract testing. After that, I started thinking about a way of validating the model.

I’ve decided to validate it against a very simple program, that would require some level of complex dependency issues, and, for that purpose, I have created a program that selects players, for a determined sport by using three criteria:

1- Player must be taller than 1.7 m

2- Player must weigh less than 130 kg

3- Player’s BMI must be between 21 and 26

The classes depended on each other like the diagram below:

It’s possible to see our dependencies are coupled, but that was made on purpose, believe me.

So let’s start by unit testing the standalone components:

Since we have no outer dependencies, it is pretty simple to test it. Basically get_weight receives a player and returns its weight, and get_height_in_meters returns height in centimeters divided by 100. Piece of cake.

Let’s take a look at the BMI tests:

Our main business logic depends on height and weight entities, but since we want to avoid integrated testing, we have mocked the logic that’s outside our software under test. By passing a player with 1.7 m and 75 kg (That’s what the PLAYER_170_75 constant represents, I’ll get to that later) I expect get_height_in meters to return 1.7, and get_weight to return 75, and as a result (here comes the main purpose of this test) I get a BMI of 25.95.

To wrap up our unit tests, we test select player:

For our main logic, we do not need to understand any of the outside logic, so we can mock height, weight and BMI. Unit testing covered.

But what if any of the returns change? What if I wanted to change decimal places, limit a maximum height to be reported or even report weight backwards? Well, all of my mocked objects would continue to return the same data as they did before the change, even if it didn’t comply to the real world, what would lead to unexpected results when the new rule applies to the data that is returning

To cover that, I have created contract tests, assuring that for every input I have mocked I get a corresponding test against the implementation, which assures the returned value is actually the one I expect. It works pretty much like the integrated test with a simple difference: optimization of test code (in a first analysis). Let’s compare.

Imagining a class demands data from another class in order to complete its logic, let’s call it “Client Class” and the provider of this data “Server Class”. The table below shows which integrated tests I’d have to cover

That would lead us to 5 test classes and all of the test cases in it. If we add another method depending on one or more of these, we get an exponential growth in our test classes and cases.

What If we guarantee that the inputs we used while mocking are actually returned by the implementation without plugging anyone on it?

Let’s take a look at it:

We have reduced our Tests and kept the purpose of it. What about some code?

Remember our player constants? We are now checking in each of the classes that the expected outputs are given when the actual method is called with those values.

Let’s take a look at the BMI Tests, since it also has dependencies

Since the contract I need to respect is regarding my calculation, and not the height and weight, I keep on mocking them, and add the possible input to their contract tests since I expect this contract to be respected in my contract test (Testception?)

It seems confusing, but that’s due to a poor design (done on purpose), and one of your tests' roles is to raise flags on that!

By doing that, by the time a contract is broken, we’re expected to reflect that change on whoever uses that expectation and see the results. I have added an extra layer of security by sharing constants between unit and contract tests so that changing one triggers breaking (or not breaking) the other.

You can find this code on my GitHub repo (https://github.com/hcassus/contract-test/), fork it, and try changing around the “production code” like you would in a normal project to see if it aggregates the value you expect.

--

--