Last month, our Netguru iOS team (also with a few Rails people!) took part in a webinar by Paweł Dudek about testing iOS apps and if you are thinking now meh, another boring obligatory webinar, check this out: after an 8-hour workday, not a single participant left a more than 2-hour-long Hangout before it ended! I'm afraid that if I wrote about all the things I've discovered, you'd finish reading this next day, but I'd like to share at least these 10 tips I’ve learned about testing iOS apps.
1. There is no such thing as untestable behavior.
There is only untestable code! If you want to learn only one thing from my post, this is it. Simple as that.
If you can’t test something in your app, you probably need to refactor a few parts of the code. It’s also important to answer the question: why can’t I test this?. The answer would probably sound like the code is too complicated! or there are too many actions in this method!. Write cleaner code by dividing it and by making it more modular.
2. There should be a single source of truth.
You should know exactly how your object should behave and you should know that you know that.
In simple words, try to make simple classes which have concrete, specified responsibilities that you can understand and describe, preferably in the RSpec-like way. In the iOS world, we have two great BDD-style testing frameworks: Specta for Objective-C and Quick for Swift.
3. Objects should have as little dependencies as they can
The less your objects know, the less you need to test. The less you need to test, the simpler your test writing becomes. Simpler writing = better quality. Aim for high cohesion.
Devices, operating systems and programming languages change over time, but the classic, solid foundations of building good software don’t. In the case of testing, takes S, L and D rules as essential:
Liskov substitution principle: your objects ought to be replaceable with other instances (of their subtypes, too). You probably often use mocks to test a specific behaviour of your object, but they may not apply to the whole system/program.
Single responsibility principle: your class is supposed to have have only one responsibility. It will be easier for you to understand what exactly it does and that makes writing its specification easier.
Dependency inversion principle: in the case of iOS, it’s most effective to decouple your class (e.g. using protocols) for a few parts and test them independently. Trying to test the whole thing at once won't do the trick.
5. Don’t mix business logic with initialization
In your initializers, you are only to instantiate (as their name suggests) your objects, not do your business domain tasks. This simplifies your code (by dividing it into parts), making it more legible and testable.
Let’s look at this code:
What’s wrong? Of course, it will compile and work - but it’s not really testable! There are too many actions in one method, especially for an initializer. Here's how to deal with this issue:
And that’s it! All the other messages should be passed somewhere else. In the initializer, you instantiate your object, not change or present it. There are better places to do it.
6. If your class is longer than 150 lines of code, something is wrong.
And if it is longer than 300, something is really broken! Divide your code into smaller parts. It makes the whole thing more legible and easier to maintain.
7. Avoid singletons.
And if you really can’t, at least make sure you know how to test them. For testing purposes in Objective-C and Specta, use your singleton as a local variable, also checking if wiring is working properly:
8. Look at the big picture.
Sometimes when you work with a piece of code for a really long time, you lose focus and stop seeing other ways to do particular things. For example: you have a few really well-written classes that pass tests flawlessly, but you feel that even then something seems wrong. Maybe this class is a little too big or maybe this test is a little bit unclear. In that case, it’s a great idea to take a little break and then show and explain your code to another developer. No dev soul hanging around? You can even use your rubber duck. You’ll be surprised how many new ideas you’ll explore.
9. Tests are here to help you.
I used to think that writing tests is an unnecessary waste of time. They don’t bring any new functionality to the app’s end user and often the test for class has more lines of code than the class itself! I changed my mind when I joined other developers for my first project maintenance and had no idea what was going on in the code! Countless classes, confusing tricks, new areas to explore. How do I get to know that my changes in the code will work? After reading almost the whole post, I guess you found out. :)
10. You can always learn something new, even if you are a pro.
This isn’t applicable to testing only, but programming in general. Don’t close yourself off from new things. If you think that testing is for lame coders and your code always works as you expect - well, maybe it’s true. But even then if you start writing tests in some time, you will see that your development time is speeding up. Machine-check of your code goes far faster than reading the whole class (or even whole program) line by line, manually. And trust me, you break code sometimes without even knowing it. Even if you are a pro.
I hope you’ve found my post useful. I started to learn testing not long ago, so this knowledge is good for newbies, just as I used to be. If you want to dig for more, Paweł Dudek collected a great list of resources about testing that you should definitely read and star on GitHub! Thanks for reading and see you next time!