Hallo velden en bergen, Leo hier. Today we will talk about @testable when testing in Swift.
Before diving into Swift per se, let’s briefly discuss another topic.
Lately, I’ve been thinking a lot about how we judge other people’s tastes. I’ve seen in several situations when someone says he likes reality shows like Big Brother, for example, other people start looking down at him. The same pattern happens with music, I come from a country where we have a whole genre of music that comes from the economically exposed people, we call it Funk (which doesn’t relate to North American Funk).
I can’t believe the year is 2024 and people still do this kind of behavior, what’s the matter if someone like Funk or Bach’s Cantatas? Or if someone likes Breaking Bad, Big Brother, or Cosmos? It’s quite sad that behavior to be honest. Some of the most wonderful people I ever met liked the least intellectual stuff on TV or music genres and that’s completely ok.
Maybe it is just the “Mozart effect”. What does “the Mozart effect” mean? In common discourse, “the Mozart effect” suggests that listening to Mozart’s compositions can boost overall intelligence or IQ. However, in scientific circles, it denotes a more precise phenomenon: the assertion that individuals experience temporary enhancements in visual-spatial skills, lasting 10-15 minutes, following exposure to brief segments of Mozart’s music. Additionally, the term is employed to denote the perceived health advantages of music listening, encompassing its benefits for individuals grappling with anxiety, high blood pressure, and epilepsy. That’s simply not true.
I think in the end people end up judging others by their entertainment choices just to feel better about themselves. It is a way to say to themselves: “I’m not like him, I don’t watch Big Brother, I watch [insert whatever you think is highly intellectual stuff here]”. And when this happens is the moment that you are full of yourself and nothing can convince you otherwise, and we know the dark path that could lead you, right Anakin?
Vivre et laisser vivre! We don’t need one more burden in our lives with one more problem which is others’ judgments in our tastes. If your taste is not hurting anything, what’s the problem?
No more talking, let’s code! But first…
Special Thanks To Sponsors
Recently we received a donation of 5 coffees from a user named dCEO. I can’t stress enough how glad I am with those donations that help to keep the motivation up since I removed all the Google Ads Fiesta from the blog.
Painting of The Day
The painting I chose today is called The Spinner, an XVIII century art piece by Pietro Longhi.
Pietro Longhi, originally Pietro Falca, was a Venetian artist renowned for capturing everyday life in Venice. Encouraged by his father and trained by Antonio Balestra, Longhi initially focused on historical themes but shifted to everyday scenes after his historical works received little acclaim.
Under Giuseppe Maria Crespi’s influence, he dedicated himself to depicting the quotidian aspects of Venetian society, diverging from the grandiose themes prevalent in Venetian art. Renowned for his vibrant and socially insightful paintings, Longhi also excelled in drawing, often creating works solely for pleasure. Besides his artistic contributions, Longhi was an influential teacher and founded an academy, leaving behind a rich legacy through his depictions of Venetian life and his accomplished son, Alessandro Longhi.
I chose this painting because of the spinner. That reminded me about the TDD testing cycle, the write test -> fail -> make it pass -> write tests -> …. And that cycle going round and round and round.
The Problem – Discovering @testable keyword
You are reading the code and suddenly you see a @testable
To really discover why @testable is a necessary tool, we first need to review the concept of module and access control levels.
It is well known that in Swift if you don’t explicitly add an access control keyword, for example, “private/fileprivate/public/etc” the entity or property will be internal. For example, if you declare the struct below:
struct User { let username: String }
It is read by the compiler like this:
internal struct User { internal let username: String }
Why’s that? Because the default access modifier is internal. But what internal means? The internal access modifier is to enable entities to be used within any source file from their defining module, but not in any source file outside of that module. You typically use internal access when defining an app’s or a framework’s internal structure. Using our example above, only other entities (code) inside the module where the struct User is defined can read it. This is awesome for encapsulation because this guarantees that nothing is used inadvertently outside the module.
Knowing that let’s go to the testing part.
Testing Modules and Production Code Modules
The way that Apple designed the test framework is that the tests will always be in a different module of our actual production code, this is also a nice design decision because of several reasons.
1. Clean Separation of Concerns
Imagine your app as a restaurant. Your main code is the kitchen where all the magic happens, and the testing module is like the quality check team that makes sure everything tastes right before it goes out. Keeping these two separate means the kitchen stays focused on cooking up great dishes, without the quality check team getting in the way.
With this in place, whenever you are writing production or test code you will never be mistaken for one for the other. You know exactly where to do each one of the tasks making the code way simpler to maintain.
2. Simpler Codebase
When your testing code is out of sight from your main app code, your project is easier to navigate. I mean, it is literally less files/code to read in your main app. This makes your main code cleaner and more focused on what it should do – run your app smoothly.
3. Enhanced Security
By isolating your testing code, you reduce the risk of accidentally including test cases or mocks in your production app. Think of it as not leaving spare keys under the doormat. This way, there’s less chance for security loopholes or unintended behaviors in the final app that users get their hands on.
4. Dependency Management
When your tests are in a separate module, you can include testing libraries and frameworks without adding them to your main app bundle. This keeps your app’s dependencies clean and to the point.
Since the testing module is separate, you can build and run your main app without having to compile all the testing code every time. This setup encourages a more disciplined approach to development and testing, making you think carefully about the interfaces and interactions between different parts of your app.
Keeping testing code separate means your app doesn’t get bogged down by extra code it doesn’t need to run, especially in production.
5. Easier Continuous Integration (CI)
With separate modules, it’s easier to set up CI pipelines that run tests automatically without interfering with the rest of the development process.
In summary, keeping your testing code in a different module from your real app code in iOS apps is all about staying organized, efficient, and focused. It helps ensure that your app is the best it can be, without any unnecessary clutter or complications.
Now let’s go to the coding samples.
Improving Your Test Suite – The Reasoning Behind @testable
These are all advantages of keeping them split but sometimes we want to test our code further. Imagine that you define a module with several small structs/classes that have business logic in them. Each of the classes is internal because no one outside of the module would need to know about the existence of them but you still could want to make sure that all the logic inside those files are properly tested. How would you do that if you can’t reach those classes outside the module they were defined?
That’s where our magic @testable annotation appears.
As per Apple docs states:
When you’re building an app and include a unit test section, you have to make sure the app’s code can be checked out by this testing part. Normally, only the parts of your code that you’ve flagged as open or public can be peeked at by different parts of your project. If you put a @testable tag on the import statement where you’re bringing in your main app’s code, and you’ve set up your app’s code to be test-friendly, then your tests can get into the internal stuff of your app that’s usually off-limits.
That is exactly what we needed. Check the import example below:
import XCTest // Mark 1
import TestableUtils // Mark 2
@testable import TestableTutorial // Mark 3
final class TestableTutorialTests: XCTestCase {
// your tests here
}
Now every time you look at the code above you can think:
Well lets see, we have three imports here. The first one is importing the Apple testing framework. The second import is importing all the PUBLIC available entities on TestableUtils module. And on the Mark 3 we are using @testable to transform all the entities that has internal access modifier to public, this way we can access to test/initialize everything inside TestableTutorial.
In other words, what @testable does is everything that has an internal access modifier of the target module becomes public and accessible for tests.
Thinking About @testable Downsides
I can think of some downsides to marking a module @testable in our tests. Like everything in life, have the good and the bad side. Let’s explore some problems now.
Let’s check some situations you might find yourself in if you lean on @testable too much. Don’t get me wrong, I’m not saying @testable is a no-go. It’s just that there’s a balance to strike, and it’s good to be mindful about it.
First off, let’s talk about what it means when we say something in our code is ‘public’. It’s like making a promise that this part of your code is going to play nice and stay consistent for anyone who’s using it. It’s a big deal because it means you’re committing to keeping things stable and reliable for your users, allowing you to tinker under the hood without them ever knowing.
Ever find yourself setting up tests and then feeling trapped by them when you want to change something? It’s like they’re holding you back instead of helping. This usually happens when we test too many details instead of focusing on the big picture. @testable makes it tempting to go down this rabbit hole because it’s so easy to expose everything to your tests. But sometimes, it’s better to test through the public parts of your code and leave the nitty-gritty details out of it. If something’s not being used, maybe it’s time for it to go.
Using @testable can blur the lines between what’s meant to be stable and what’s flexible in your code. Normally, marking something as ‘public’ is like putting a label on it that says, “This is important and stable, count on it.” But if everything’s accessible with @testable, it’s harder to see those labels. It’s like everything’s shouting for attention.
TDD, or Test-Driven Development, is awesome, but it’s not a magic fix, especially when mixed with @testable. It’s easy to end up focusing too much on the tests themselves, rather than the actual code you’re trying to write. Always ask yourself if your tests are zooming in too much on the details instead of the overall functionality. Be mindful of where to zoom in and where to zoom out while testing.
One symptom that your tests are poorly written is that when you have to change any internal behavior all the tests suddenly break.
So, what’s the takeaway?
Well, it’s not about avoiding @testable altogether. It’s more about being thoughtful with it. Consider making parts of your code public on purpose, to really think about what needs to be stable and what can stay behind the scenes. Even within a single module, using those visibility labels can help signal to anyone who’s reading your code later on what’s meant to be solid and what’s open to change.
In short, @testable is cool, just don’t let it become your go-to move without good reason.
And we are done!
Conclusion – The @testable Annotation
In wrapping up our discussion on the @testable annotation in Swift, it’s clear that this tool offers a valuable bridge for testing internal components of our modules, enabling a more thorough validation of our code’s behavior without compromising encapsulation.
However, as with any powerful tool, the key lies in its judicious use. It’s imperative to balance the desire for comprehensive testing with the principles of good software design. Over-rely on @testable might tempt us to overlook the architectural boundaries within our applications, potentially leading to a codebase where the lines between public interfaces and internal implementations become blurred. Such a scenario not only complicates maintenance but can also make future refactoring efforts more daunting.
Therefore, as developers, we must tread carefully, employing @testable where it truly benefits our testing strategy without undermining the integrity of our code’s architecture. It’s about striking the right balance between accessibility for testing and adhering to the principles of encapsulation and modular design.
Fellow iOS Developers, that’s all. I hope you liked reading this article as much as I enjoyed writing it.
If you want to support this blog you can Buy Me a Coffee or say hello on Twitter. I’m available on LinkedIn or send me an e-mail through the contact page.
You can likewise sponsor this blog and help our community to grow.
Thanks for the reading and…
That’s all folks.
Image credit: Featured Painting