Yes, we do test private functions

Stéphane Derosiaux
Powerspace Engineering
5 min readJul 11, 2016

--

You should try to open every door

Few days ago, I had a discussion at Powerspace about private and public function accesses in the code.

Building a mental picture of the code

Before doing some modifications to a codebase, I like to read the source for a bit, to get what is the main idea behind the lines of the files I need to change. How is it used? Is there one main function or many? Are they linked together or are they independent? etc.

In those situations, I build a mental picture of the code, of its flow, to be sure not to miss anything. I guess you do it too, right?

One thing that slowed me down when I was building this metal picture, was that some files only contained public functions. It’s hard to imagine a concise image when functions can be potentially called from anywhere.

It’s important to limit the accessibility of the internal methods if they are not called from elsewhere. A helper function can help us limiting the visibility of the functions that are only accessed internally to the file — Intellij IDEA had a nice upgrade recently , it now detects those use-cases and invites you to change it.

The typical case is splitting a function. In that case, the best practice are 2 functions, one private and one main, that is public and calls the private one.

Right. But what about tests ?

Yes, we do test private functions

Tests are really important to us. But if it’s private, you can’t test it, the tests won’t have access to it. And yes, testing private functions is necessary.

You don’t want to test only the main entry point of an app. You want to unit-test the smaller parts to be sure the internals are working properly too. This is why in my case, those functions were not declared as private.

You can also test more edge-cases when you test small functions. It can be very tricky, complex or bloaty to test public functions because they can handle so much cases.

Finally, writing tests for private functions make it clear for anyone to know what this function is doing. You don’t understand the code in this function? Check the test. Private implementation or not, it’s helpful.

To simplify the code flow, I like to limit the visibility of the functions and fields. They should always have the smallest scope they can. And it should always be possible to test them.

What can we do?

Let’s start by defining the functions that don’t need to be public but private. Tests are now failing because they can’t find the functions. How can we fix that?

  • Test only the public functions, no test for the private ones.

I don’t recommend that. It’s generally easy to test the privates and their edge-cases. They are more focused and have simpler input and output.

It can be quite complicated to test only a public function, as it can call hundreds of privates behind. If a test fails, how do you know which private sent wrong stuff ? It can be very hard to spot.

  • When using ScalaTest, it’s possible to use trait PrivateMethodTester to get a reference to a private. (does not work in the private is itself in a trait)
val myfn = PrivateMethod[String]('myfn)
obj invokePrivate myfn(1)

Again, not really a fan of using this type of code in a test, but at least, it’s quite straightforward.

Method method = targetClass.getDeclaredMethod(methodName, argClasses);
method.setAccessible(true);
method.invoke(targetObject, argObjects);
  • With Scala, you can also declare the function to be private to the package, instead of simply private. Then, you define your test in this same package (generally it is), and you have access to the private function.
// src/main/scala/com/powerspace/Entity.scala
... private
[powerspace] def compute(a: Int, b: Int): Int = ...
// src/test/scala/com/powerspace/helpers
... test("it works") { new Entity().compute(2, 3) should === (42) }

That’s better, we just need this little trick.

  • In languages embarking the concept of trait or interface, it’s possible to create one that contains only the public methods, and override them in their implementation with a specific keyword.
trait Computable {
def compute(a: Int, b: Int): Int
}
class Laptop extends Computable {
override
def compute(a: Int, b: Int): Int = ...
def
kindaPrivate(c: Int) = ...

You know the implemented methods are the public ones, because they belong to the trait/interface. The other functions are implementation details, aka “privates”.

Moreover, that’s following the SOLID principle by exposing only what matters (interface-segregation principle).

// @private
  • Or even an annotation/decorator, for the languages that can handle them. Java, Scala, Javascript (ES2016 version, using Babel) for instance.
@Private
  • Another method, the simplest I think, is to rename the function and prefix (or suffix) it with an underscore. Who doesn’t know it means “private” ? It appeared in many language such as C, PHP, Javascript… More generally, in OO languages, we often create a get/set “foo” with their related private variable named “_foo” or “foo_”.
def _compute(a: Int, b: Int): Int = ...

I even saw some projects using a variable named “_private”. That’s quite clear right ? (and yet, it was still used by another program for some reasons).

However, those underscore are meaningless to the language itself. The functions will still available from anywhere. They will still appear in the auto-complete of your editor and so on. We know humans are not really disciplined so they will use them.

Conclusion

  • Split your functions to reduce their complexity
  • Name your functions properly and add tests no matter its visibility to achieve self-documentation
  • Apply the smallest necessary visibility to your fields and methods
  • Or use the ISP principle (implement an interface and override functions)

Thanks to Jean-Charles and Cedric Sadai for reviewing this post.

--

--

Founder of conduktor.io | CTO, CPO, CMO, just name it. | Kafka and data streaming all the way down