With examples in iOS
There’re a lot of good tutorials and guides for beginners on how to unleash the full power of iOS frameworks for developing stunning iOS apps. At the same time, most of the newbies bypass the unit tests topic or even being unknown about its exexistence.
Moreover, even more, mature iOS teams which work on mid or large-size projects ignore unit test writing at all. I won’t lie it hadn’t been apparent to me before some point in my career as well. Hence I wanna tell about why it’s crucial and pretty much beneficial to test your code. In another article which I’m gonna publish later, I’ll cover the best approaches and tools which help you to write the iOS test without any pain.
P.S. Things I’m gonna talk about are applicable not only to iOS development although the examples are written in Swift and for iOS development.
There’re can be found more but I’m sure these 4 should be enough to convince you.
When your code base is growing it’s becoming hard to manually test all parts of the system that have changed their behavior after new code changes.
In the following example, we have some boolean field regarding showing button for the user or not:
And we have a line in our test to check it’s true after initialization:
If we change
needToShowButton clause to consider
isUserAuthorized to be
true at the same time:
Our test will break and we need to adjust it to expected behavior (for ex. the value should be false with new conditions)
Let’s be honest — we’re not writing documentation for all code we write (and it’s not essential for some cases). Unit tests are running on some example input and are passed when the result matches the expectations so it’s a great way to learn how some component of a system works and should be used by looking at and running tests covering it.
There’s an example where we can see what params could be passed and how a service modifies a round time and item count with certain params in input:
You can’t cover code by tests if it’s written poorly and unscalable. To write a useful unit test, you have to isolate the parts of the tested component is using in order to correctly configure an environment to test various use cases.
In the example below there’s a poorly structured class that is hard to test correctly. As you can see, we can’t control what the service will produce and what items the factory will generate:
Factory and service can be injected via initialiser under protocol (interface) so we can mock them by our objects and specify behaviour for different cases. You can read more about dependency injection here.
For sure you’ll spend more time implementing features by covering them completely through unit tests (useful unit tests!). However, sooner or later you’ll end up saving a huge amount of time on your manual regression testing.
Just imagine, your application has become big and has 100 thousand plus lines of code. After some refactoring has been done which is piercing the most part of your codebase, you definitely want to test it heavily. Testing all edge cases manually is insanely loose to just run unit tests and it’s much more reliable.
As I said above, you’ll probably spend more time learning how to write unit tests correctly.
Some of you, who have already tried and learned it, can argue it’s also required a lot of time to create a boilerplate code, mock dependencies, etc.
It’s true if you’re doing it manually and don’t use tools that save your time on doing repetitive tasks.
As I said before, I’m preparing the next article for setting up your environment to write them fast and painlessly by automating routine work. Hope I’ll have finished it very soon.
You don’t have to pursue to have 100% code lines covered.
First of all, it’s simply because of the Pareto principle — you’ll spend much more time on reaching 80–100% metric as it doesn’t make a huge leap in usefulness.
Secondly, speaking about iOS development, it’s really hard to cover view layers of feature modules.
Let’s consider an example where you decided to test that after your model calls a callback in some view it’s configuring to update its labels. Of course, you can check that labels have text fields not to be empty but probably you’re more concerned about how it’ll look on the user’s screen.
Thus the best approach I personally prefer is to make a view layer as dumb as possible (move your logic in layers below) and write snapshot tests or UI tests but it’s a completely different story.
Okay, I had to mention there’re some cases for which, in my opinion, you shouldn’t write unit tests in the first place.
The first one is a small pet project or experimental project of your own. You should consider what is your main goal. If it’s just to test some new technologies (ex. WWDC new features and frameworks), it doesn’t worth digging yourself into writing tests.
Secondly, if it’s a legacy code. If you want to cover your old and bad structured code with tests it’s much easier to refactor it and write the tests for the new component as you’ll waste a lot of time trying to correctly mock dependencies and test edge cases.
To sum up, as soon as you start to cover your code by unit tests, you’ll see the impact I’ve described and how you’ll start to think differently and become better developer.
The important point is also that it works if you write tests wisely because it’s also not so hard to begin producing useless unit tests. There’re a lot of decent articles and books about it — you can get it started with this one.