Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write testable code in Swift

So this question of mine all began when I started to do unit testing for a simple 2 line of postNotification and addObserver. From this similar question here you can see that to make it testable you need to add ~20 lines & part away from the common way of writing your code.

Facing this problem was actually the first time I understood the difference between Unit Testing and TDD. Unit testing is easy if your code is testable ie if you are following the TDD mindset. Next I was brought to how can I write testable code, which I didn't find much guidelines, every tutorial simply just jumps into writing a unit test. Apple own documentation has nothing for this.

My initial thought was that I need to aim for 'functional programming' and write my functions in a pure function way. But then again this is very time consuming and may require lots of refactoring in existing codes or even for new projects lots of added lines and I'm not even sure if that is the correct approach. Are there any suggested guidelines or standards for writing testable code in an easy way?

What I know myself already: I know that you shouldn't write any code, unless there is a test for it to fail, so basically I have to write the test first and as soon as I get an error, even a compiler error then I would have to switch back to the actual class being tested write whatever necessary and make my test code not give any errors , then switch back to the test class and continue writing my test and fixing compile errors until done. Then run the test and see if it checks what I want it to check.

For all tests I should make sure that my tests would pass and fail exactly where I expect to fail ie the test would pass when it's expected to fail.

What I don't know is how can I smoothen the process in a more easier way.

I am not asking for how to write a testable code for NSNotificationCenter, I am asking for general guidelines for writing testable code.

like image 386
mfaani Avatar asked Aug 23 '16 15:08

mfaani


People also ask

How do you write a swift test?

Click on the Test Navigator tab in the Xcode Navigator (the left-most window). This tab can also be reached by pressing ⌘ + 6. In the bottom left corner you can add the test target by pressing the '+' button. Follow the instructions on the screen and voila: You are now ready to write your first unit test.

What is testable import Swift?

If a Swift module is compiled with “testing enabled” it allows us to import that module using the @testable annotation to alter the visibility of our code. Classes and their methods that are marked as internal or public become open , which allows subclassing and overriding in tests.

What is XCTest Swift?

Use the XCTest framework to write unit tests for your Xcode projects that integrate seamlessly with Xcode's testing workflow. Tests assert that certain conditions are satisfied during code execution, and record test failures (with optional messages) if those conditions aren't satisfied.


1 Answers

This is a rather large question, and one where developers' views sway to very different directions. It is also important to note that making code testable is in many essential ways not a Swift specific question: a lot of what makes you able to write testable code routinely and conveniently actually relies on you following some fundamental, generally applicable principles. Often test driven design practices help you indirectly, by validating you have followed practices that make executing your code via tests plausible, beside bringing you other programmer productivity and reliability benefits. So, unfortunately writing testable code is not a question of learning some mechanical tricks of working with Xcode, rather than often a proof of you having designed and planned the programs and libraries you write keeping to some good practices.

I'll do my best to link below to some Swift specific resources to demonstrate the more general principles I tend to myself follow to make my code testable.

  1. Making your code testable is often a side effect of following sound object oriented design principles.

    • You may find reading about the SOLID principles useful.
    • Take a look also at this Swift based demonstration of each of the five SOLID principles.
    • Google's Code Reviewer's Guide (PDF) is also great resource for learning about typical OOD problems and how to avoid (also by no coincidence, this OOD related document happens to be subtitled "Writing Testable Code").
  2. Following a sound object oriented design is often itself the side effect of good higher level architectural decisions. Basically, think early and often about the kinds of types you plan to introduce in the object graph of the program. What are the roles and dependencies between them? Are any of the dependencies in your object graph hard to meet or correctly construct when executing your code from different contexts (e.g. from UI code vs a unit test)?

    • There is a lot of computer science literature about architectural design patterns. The Gang of Four remains a valuable book to read about this topic (although not all of it applies to your typical Swift program).
    • Take a look at this Swift design patterns demonstration for an overview of how many common design patterns can be implemented in Swift.
    • Especially if your code is for a mobile app, you should read about VIPER, a mobile app oriented architectural pattern emerged out of the typical architectural needs of iOS apps.
    • To link design patterns to the SOLID principles listed above, the "Single Responsibility" principle is perhaps the one principle most glaringly violated in many large Cocoa programs that is a result of bad architectural practices, also resulting in very difficult to test code. People in fact often for jokingly refer to MVC as applied in practice in Cocoa as the "Massive View Controller".

The above points raised are in no way Swift specific, and I would expect not terribly controversial claims. Swift does however also offer language features that also help you write reliable, bug free code, in part as these language features help you make your code testing friendly. Here's a few of good Swift practices:

  • Use value types when you can: Swift is well suited for writing programs which minimise automatic reference counted references (pointers). This carries with it performance benefits, but also improves your chances of writing reliable code that minimises unexpected dependencies and data races which can be difficult to capture in testing.
  • Keep things immutable when you can: the syntax conventions and type system in Swift help you avoid mutable state, which amongst other things has a negative impact on testability of your code (configuring your object graph for testing can become difficult, as will achieving real-world usable test coverage of your code if the possibile state space in your program is large).
  • Apple has also made available some guidance on the architectural matter of value types and immutability that also touches on testability of your code.
  • Use protocols when you can: to understand why, read up on the Liskov substitution principle :-) More seriously, preferring protocols over concrete types helps you write testable code for instance by for allowing you to fulfil dependencies in a test setting with test specific types that fake or mock some resources irrelevant for your tests.
  • Use functional programming techniques (when it makes sense): often composing functionality with functions helps you write readable, flexible code that avoids mutable state. I recommend Functional Swift by Chris Eidhof and others as a good guide to applying functional patterns in Swift.

Again, this question is large and I am really just scratching the surface with my answer, in order to make the point that there is no single magic bullet to testability – it's something you achieve as a result of following many best practices when designing your code.

like image 192
mz2 Avatar answered Oct 10 '22 16:10

mz2