18

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.

Community
  • 1
  • 1
mfaani
  • 33,269
  • 19
  • 164
  • 293

1 Answers1

21

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.

  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.

mz2
  • 4,672
  • 1
  • 27
  • 47
  • I will have to go through the links. It will take some time. Can you give you an example of a bad code to test & another test for good code and also mention what we should to make mocking easier? – mfaani Oct 17 '16 at 00:04
  • 1
    I would suggest reading through my response in some more detail to understand why it is that I will not present you with a bad and good example (I'll give you a hint, you are oversimplifying the problem) :-) So, I suggest you do the background reading – there is no way to cheat skill and experience on this matter. – mz2 Oct 17 '16 at 00:09
  • Your first link for solid is missing a `)` at its end. I read through the links and believe you are guiding me into the write path. Can you pinpoint exactly what you need to do address unit testing a PostNotification, AddObserver. Do any of the links address that? I already included a link in my question, addressing my concern. PS I think that Swift Design pattern link would keep me busy for quite some time – mfaani Oct 17 '16 at 15:17
  • Thanks, I've fixed the URL! The design pattern material for sure will keep you busy a while :-) Regarding testing notification observation, that sounds like a good separate question to ask, but XCTestExpectation (described well in [this NSHipster post](http://nshipster.com/xctestcase/), which allows for testing asynchronous code, can help in doing that (you set up an expectation, create an observer for the notification you expect to observe, trigger code that should lead to it being posted and then it'll fail as a timeout if the notification was not posted). – mz2 Oct 17 '16 at 16:00
  • My question is more like should TDD be something such troublesome as mentioned [here](http://stackoverflow.com/questions/28292685/how-to-write-unit-test-for-nsnotification/28293347#28293347). So how can such circumstances in general be avoided when you want to do TDD. But it seems that your answer is `XCTestExpectation`... – mfaani Oct 17 '16 at 16:10
  • But don't you mean it would fail if the observer isn't triggering? I mean how can a postNotification fail?! – mfaani Oct 17 '16 at 16:11
  • 1
    We really shouldn't be conversing on that question in this thread where you specifically noted you requested more general advice. As a general StackOverflow related point I would request asking questions phrased more directly than "How can I do the unit testing" as I see you have phrased that question – what precise thing you want to test is not clear. Generally speaking, XCTestExpectations help test asynchronous code, but I now see that you are testing view controller code, which is really better tested using [Xcode's UI testing system](https://developer.apple.com/videos/play/wwdc2015/406/). – mz2 Oct 17 '16 at 16:22
  • 1
    Also, test driven development is not really generally speaking tedious… except for in the common case where you are dealing with code that wasn't designed to be tested :-) In that other question the problem is really more an awkward testing strategy rather than a genuine need to subclass NSNotificationCenter. You can register as an observer for the default NSNotificationCenter and use the callback or the closure based notification observation API to test what you want there, assuming you know the name of notification you are attempting to observe a) happen and b) see a correct response to. – mz2 Oct 17 '16 at 16:30
  • *dependencies in your object graph* <-- what does object graph mean? I think you are speaking about something visual which I have no clue of... – mfaani Jan 06 '17 at 13:55
  • I don't think you're quite following the broader point I am trying to make: you actually need to do your reading (starting with that Wikipedia page) and plan and understand your program's architecture at different levels, you will not find mechanical tricks to make stuff testable. It's quite obvious from your questions that you for example have not read into literature on the SOLID principles, in particular on dependency injection / inversion of control principle related literature (Gang of Four I linked to will talk about this for example, amongst many other resources). – mz2 Jan 06 '17 at 14:04
  • ??? I am **not** trying to find a mechanical trick. I haven't read A-Z of all links you provided but read through them and read the wikipedia link...still I am asking if you know of a tool to create object graphs. – mfaani Jan 06 '17 at 14:11
  • You'll find a description on that Wikipedia page I linked to that explains how the object graph is an abstract concept. It's a way to think about your program's state, it's not meant in the context I wrote it in, nor what you'll find in the literature, as a concrete visual image you need a tool to create for. You don't need a tool – you are already creating object graphs in your programs, and object graphs with certain properties are good for testability, others less so. – mz2 Jan 06 '17 at 14:14
  • Thank you @mz2 for the links. These really helped me, – Mostafa Nov 30 '17 at 21:18