Testability Tip for Swift Developers – Public Over Private

Quite often in unit testing, especially when practicing Test-Driven Development, I find myself wanting to test every single line of code. When I run up against a private function, however, I often scratch my head and ask, “How am I supposed to test this??”.

Most experienced testers will tell you, “Don’t test private implementation – only public API”.

“Okay… But how, does that private function get tested?”, I always asked.

In this article, I intend to share a testability tip catered to the Swift developer community that helps alleviate this issue with testing private functions.

Testing and observability

Developers as clients of APIs

As developers, we are “clients” of APIs on a daily basis. We interact with other developers’ frameworks and libraries through the visible, observable, Application Programming Interface that they’ve exposed to us. It’s the way they’ve designed for us to interact with their code.

Notice three words that I chose in that introductory paragraph:

  • Visible
  • Observable
  • Exposed

If we are going to use another developer’s library, all we have as developers is the public interface that they’ve made visible to us… It’s the only observable thing we can look at and go, “Oh! Okay, to do this, I call that function”. The only thing exposed are the functions and properties that the developer intends for us to see.

Tests as clients of APIs

Have you ever viewed your unit test suite as a “client” of your code? It is!

And just like a developer, the unit tests in your test target interacts with your app’s API through the same visible, observable, interface that you’ve exposed to them.

If you start to personify your test target and think of it in terms of “just another client of your code”, you begin to see that it really doesn’t have any more visibility of your code than another developer would if he or she were consuming it.

All of this boils down to say, if it’s observable, it’s testable. Which means, the easiest and most natural code to test is public code.

Public over private (and internal)

So should everything be public instead of private or internal? Certainly not.

Object-oriented programming strives for data-hiding and encapsulation, so there are reasons to keep some things non-public.

Additionally, there are certain Swift compiler optimizations (which lead to run-time optimizations) that can be gained when you mark things in your Type as final. Using the private access modifier allows the compiler to infer that it is final because it’s narrowly scoped to the current Type only. These are handy things to know, but as is always the case with optimization, avoid premature optimization by measuring first to decide if you really need them.

Whenever possible, I prefer public over the other access modifiers to help enable testing for my apps.

So when is “whenever possible”?

Public when part of a Type’s reason for existence

Obviously, properties and functions that are part of a Type’s core purpose can be marked public. My practice is to decide, “Is this function why this Type exists?”. If so, I mark it as public.

Note also that the Type itself needs to be marked public if it’s going to be visible to your unit tests.

 1// Instead of this (default --internal-- access)...
 2class MyClass {
 3    func myFunc() {
 4        // Performs something essential to why MyClass exists
 5    }
 8// Make things public...
 9public class MyClass {
10    public func myFunc() {
11        // Performs something essential to why MyClass exists
12    }

Transition many private components to new Type

Having many private properties and functions can be an indication that there needs to be a new Type created to encapsulate those components. I’ve heard developers talk about this situation as one that “screams, ‘New Type!’”.

If we extract out sets of related private properties and functions into a new Type, those pieces of code are the reason that Type exists, and thus should be marked public. Testing, then, becomes a matter of writing unit tests for the new Type and its public API!

Even if you just have a few private code blocks in the Type you’re trying to test, it can sometimes make your testing life easier to transition them to a new Type as public components.

If I’m really uncomfortable marking functions public in the Type where they currently exist, creating a new Type and marking them public there is usually the better alternative, and it immediately enables testing for that set of code.

Testing non-public code

As I mentioned in the beginning, it’s not good to just haphazardly go through your code and “public all the things!!”. After appropriately marking fundamental functions public and transitioning sets of private functions to new Types where they can happily live as part of that Type’s public API, there will likely be a few private or internal things left over.

How do these get tested?

Well, ideally, these are small, simple, helper functions that are only useful when called within the Type you’re working on.

If these functions produce observable results in the places where they’re called, you end up testing these non-public components implicitly, that is, by testing the things that are public.

During the course of testing some public function which calls another non-public function, that non-public function’s logic will be executed. If the outcome of that function’s execution affects the Type’s state, or the output of its public parent function, those are the things that you’d be able to write unit tests for, because those are the “observation points”, so to speak.

Wrapping up

The bottom line is: observable == testable. Just like another developer, the suite of unit tests in your test target interacts with your app’s API through the visible, observable, interface that you’ve exposed to them. The more observable your API components are, the more testable your code becomes. Which is why I prefer public over private whenever possible!

comments powered by Disqus