What's happening behind the scenes in XCTest's @testable?
To answer your question, for debugging purposes, you can actually use this. Let's say you have a workspace MyAwesomeWkspace
and a project inside MyAwesomeProject
.
Now, create a new framework
aka module
called MyAwesomeModule
. Inside that module create a non-public class called Person
.
If you try to use the class Person
inside MyAwesomeProject
by doing import MyAwesomeModule
and then something like let p = Person()
you will have an error.
But if you do @testable import MyAwesomeModule
, the magic happens and you can now use the class.
Basically @testable
allows you to test things that you didn't declare public. The annotation only works with import
as you can see it here.
So in order to work, the target is compiled with -enable-testing
so that you can have access to non-public members. At least based on what's here
Because, by default, the debug
build configuration is compiled with -enable-testing
, the example I showed you will work. But if you change the build config to release
, you'll see an error saying Module .. was not compiled for testing
since the release
config is not built with the flag.
The Swift access control model, as described in the Access Control
section of The Swift Programming Language (Swift 4), prevents an
external entity from accessing anything declared as internal in an app
or framework. By default, to be able to access these items from your
test code, you would need to elevate their access level to at least
public, reducing the benefits of Swift’s type safety.Xcode provides a two-part solution to this problem:
When you set the Enable Testability build setting to Yes, which is
true by default for test builds in new projects, Xcode includes the
-enable-testing flag during compilation. This makes the Swift entities declared in the compiled module eligible for a higher level of access.
When you add the @testable attribute to an import statement for a
module compiled with testing enabled, you activate the elevated access
for that module in that scope. Classes and class members marked as
internal or public behave as if they were marked open. Other entities
marked as internal act as if they were declared public.
More here
Late edit: One of the cool parts of swift is that is open source. So if you want to dive deep into the "magic", check it out: https://github.com/apple/swift
What does a module mean in swift?
A module is a single unit of code distribution—a framework or application that is built and shipped as a single unit and that can be imported by another module with Swift’s import keyword.
Each build target (such as an app bundle or framework) in Xcode is treated as a separate module in Swift. If you group together aspects of your app’s code as a stand-alone framework—perhaps to encapsulate and reuse that code across multiple applications—then everything you define within that framework will be part of a separate module when it’s imported and used within an app, or when it’s used within another framework.
As the docs indicate, the module is an application or a framework (library). If you create a project with classes A
and B
, they are part of the same module. Any other class in the same project can inherit from those classes. If you however import that project to another project, classes from that another project won't be able to subclass A
nor B
. For that you would have to add open
indicator before their declarations.
Basically, if you work on a single app then you are working in one single module and unless declared as private
or fileprivate
, the classes can subclass each other.
EDIT
Let us have following class in module (project) Module1:
class A {
}
Since this class is not open
, it can be subclassed only within the same module. That means that following class:
class B: A {
}
Can be written only in the same project, in Module1.
If you add Module1 as a dependency to project Module2, and try to do this:
import Module1
class C: A {
}
It will not compile. That's because class A
is not open
(in other words it has access public
or less) and it does not belong to the same module as C
. A
belongs to Module1, C
belongs to Module2.
Note
import
keyword imports a dependency module to your current module. If you write import UIKit
in your project, you are telling the compiler that you want to use module UIKit
in your module. import
does not define current module. Current module is the current project.
Adding import UIKit
at the beginning of the file does not change nor define to which module the file belongs. It just tells the compiler that in that file you want to use code from UIKit
module.
Swift 3: The difference between Public and Internal access modifiers?
Your diagram is just incorrect.
Public members of A.swift
and B.swift
are available to C.swift
and D.swift
. The only restriction is that classes can't be subclassed (they would need to be open
.
Related Topics
Could Not Build Module 'Nanopb' Error in Xcode 12.0.1
Hidden Property Cannot Be Changed Within an Animation Block
Core Data Transient Values with Swift
Self Sizing Uitextview Till Specific Height
Swift 4 Get Error Code from Error
Playing an Audio File Repeatedly with Avaudioengine
Custom Uitoolbar Too Close to the Home Indicator on iPhone X
How to Get the User "Name" Using Swift
How to Add Interactive Uilabels on Top of a Uiimageview
How to Implement a Payment App with Braintree in iOS
Swift: Have Searchbar Search Through Both Sections and Not Combine Them
Auto Focus and Auto Exposure in Avfoundation on Custom Camera Layer
How to Handle Homophones in Speech Recognition
Xcode 6 & Swift: Black Bars Appear Above and Below the Viewcontroller on iOS 7 iPhone 5 Device