Friday, December 9, 2016

How to Unit Test Private Methods in Swift

Bart Jacobs:

While access control is a very welcome addition with many benefits, it can complicate unit testing, especially if you are new to unit testing. You probably know that you can apply the testable attribute to an import statement in a test target to gain access to entities that are declared as internal.

While this is a convenient addition, it doesn’t give you access to private entities in a test target.

[…]

The key takeaway of this article is that private entities don’t need to be unit tested. Unit testing is a form of black-box testing. This means that we don’t test the implementation of the AccountViewViewModel struct, we test its specification.

This doesn’t mean that we are not interested in the implementation, though. We need to make sure the suite of unit tests covers every code path of the entity we are testing. Code coverage reports are invaluable to accomplish this.

I don’t find this very persuasive.

4 Comments RSS · Twitter

Steve Riggins

I've argued similarly. Private methods are implementation details and code coverage should expose if they are exercised by testing the API. While not testing the functions directly, we need to make sure the code is tested via other paths.

Having said this, if @testable were changed so that we could test private methods and internal classes, I would be ok with that.

@Steve I just don’t see why the functions should be untestable directly, just because of the way they fit into the larger structure. It would be nice if @testable could work with them.

They should be untestable directly because there is no point doing that and Swift like to enforce good practices. A unit test goal is to test that the target behaves as expected and that doing change in the code does not breaks the expectations.

If you write unit test for internal details, each time you change your code, you will have to update the tests to accommodate the changes even if the new code behaves exactly as it should and does not change the visible API at all.

Theses kind of test kind of test is just a burden to maintain. Moreover, by testing only the API, you can detect easily (using coverage) which code path is never used and probably unreachable in the implementation.

By writing test that call private methods directly, you miss this information which may be important as each method will be covered, even if this is dead code.

In the case of TDD, when you are in the green stage your goal is to get the test passing as quickly as possible. You are free to get to green by hook, or by crook, writing dirty code in a public method. Once the test is passing, you move to the refactor step, where you clean the code, allowing you to move faster in the future. A common refactoring step is to extract code into private methods. As long as the tests still pass, you know you have changed the structure, without changing behavior—the definition of a refactoring. There is no need to test the private methods directly; they only exist because they have been extracted from already working code.

Of course, if you are not following TDD, the above may not apply :-)

Leave a Comment