How To Check For Testable Code

Checking For Testable Code

There are a few ways to do this at a glance:

  • Looks for testing source code
  • Check for annotations in code that signal a testing framework is used (think Swagger)

If you don't see any of that, look at the code for tried and true patterns or general coding best practices:

  • Look for modular code
  • Look for well defined interfaces
  • Look for evidence of contractual integrations (like http api contract or proto buffs (the client library interface is the contract))

Why do we even care if code is testable when we can just test it in production? Not the kind of testing I'm talking about but I am a fan of testing in production.

Code, as it lives in a repository may or may not be testable. Its one of those hard facts that can really set you back if you're responsible for building the CICD pipeline that legends are made of.

I once thought everyone wrote code like I did; with testing in mind. Turns out in a lot of environments where business drives code instead of engineers or the engineering skill level could use improvement there's

a strong likely hood that code is not modular and therefore harder to or impossible to test.

The easiest way to check if code is testable is to simply give it a quick glance. Depending on the language, framework and tech stack this task's difficulty depends on how much boiler plate code and/or complex plubming imposed on the engineer, or again, skill level. Lets look at a quick example of code that is not easily testable:

1. public String do5things(JSON some_payload){

2.

3. String integration_1 = GATHER_DATA

4. String integration_2 = CACHE_DATA

5.

6. String string_payload = stringify(some_payload)

7.

8. HttpAgent m = new HttpAgent()

9.

10. String reply = m.call(integration_1,string_payload)

11.

12. String formatted_reply = doSomeFormatting(reply)

13. formatted_reply = RegEx.replace(/replace_this/with_that/g,formatted_reply)

14.

15. m.call(integration_2, reply)

16.

17. String auth = m.call('http://some_api.should_be_own_method.com/auth',{uname: bla, pass: pass})

18.

19.

20. return m.call('http://some_api.should_be_own_method.com/do_something',auth,formatted_reply)

21.

22.

23. }

If it doesn't look so bad, then think about the following fact from that old Finite Mathematics course in college:

One equation, multiple variables = infinite solutions

Simply stated, if a function is doing more than 1 or two small things then testing is going to be more difficult or impossible. In the code above do5things(JSON some_payload) the engineer called three integrations, with a total of 4 visible calls over the network.

How could this be unit tested? Line 13 could be unit tested reliably because it uses some internal regular expression. Since we have control and reliable expectations from regular expression engines, unit testing makes sense here.

What about the rest of the function? We need integration tests but there are too many calls and finally, the returned value is a call itself, which means the caller of do5things is now at the mercy of the integration's returned value. This is not only impossible for you

to test but also a security concern and a bad hand off. So how many tests would or could you write to have confidence in this method? Its impossible in its current state, too many variables, one equation.

This code needs a refactor. In the real world, you, SRE or DevOps professional will take one of three options:

  • Do nothing, I have bigger fish to fry
  • Put a work item to 'refactor' in that team's backlog
  • Check out the code and make a pull request

Over and over, the right answer is to check the code out, fix it and make a pull request. You need to enable yourself to bring higher confidence code into production.

Now I'm changing the code to a more modular and therefore testable set of functions by reusing what is already out there or creating re-usable functions:

HttpAgent m = new HttpAgent()

String uname = System.environment('API_UNAME')

String pass = System.environment('API_PASS')

public String do5things(JSON some_payload){

String string_payload = stringify(some_payload)

String formatted_reply = doSomeFormatting(reply)

formatted_reply = RegEx.replace(/replace_this/with_that/g,formatted_reply)

String a = gatherData(formatted_reply)

JSON api_reply = apiDoSomething(authSomeApi(),a)

if(!cacheData(a)) logger.warn('Data Not Cached')

}

private String gatherData(String payload){

String reply = m.call(GATHER_DATA,string_payload);

return reply

}

private String cacheData(String reply){

String isCached = m.call(CACHE_DATA, reply)

return isCached

}

private String authSomeApi(){

String auth = m.call('http://some_api.opsulent.com/auth',{uname: bla, pass: pass})

return auth

}

private JSON apiDoSomething(auth,request_body){

String reply = m.call('http://some_api.opsulent.com/do_something',auth,formatted_reply)

return reply_json

}

After this refactor, we're in a good enough position to test thoroughly, safely, and predictably. Unit testing is now much easier, but now we need to test the functions that reach out to that expensive api. Mock data is the key and you need to be able to plumb in

mock data as a skill. Sometimes, the framework you're using makes it difficult, but its much less difficult than explaining why your code just cost the business a lot of money and finding a new job. Let me turn that into a positive, you'll gain the respect and experience points and recognition as the DevOps or SRE engineer you should be.

Recognition may take some time but do it anyway.

Other positives; We might even save other developers some cycles by moving the integration calls into a distributable, tested library. Each method can now be tested because now instead of many variables, one equation we have for each variable, one equation. In other words, each function has one responsibility and therefore is predictably testable.

If we don't use mock data we're not out of the woods for 'flaky' tests, but we are with testability. Tests flake when integrations are unreliable, like anything that requires reaching out over a network, could be your connection or it could be their reliability but it happens. Spend the time and effort working on testable code. Without it, you can't have CI and the other environments before production won't be as effective.

Ask Us