Reputation: 15471
In ios apps the default behaviour appears to be to fail for Test Compilation.
Why would I want that to be default? Surely, at worst, I would want Debug to have it enabled? What changes does Enabling Testability actually make?
Upvotes: 9
Views: 7992
Reputation: 133019
Testability provides unit tests with access to symbols that would normally not be accessible from outside a module/framework, so tests can be written for these components.
Here's an (probably incomplete) overview of what Testability
means in different contexts.
In case of Swift, testability means that the access modifier for symbols can be overridden during import.
Usually Swift knows the following access modifiers:
open
: Class is visible outside the current module can can be overridden from outside the current module (unless final
). open
can only be applied to classes.
public
: Symbol is visible outside the current module. If symbol is a class, it is visible but cannot be overridden from outside the current module.
internal
: Symbol is visible only within the current module. Default access modifier for symbols at top level.
fileprivate
: Symbol is only visible within the current file.
private
: Symbol is only visible within the current enclosing scope. If applied to top level symbols, behaves exactly like fileprivate
as the file itself is the enclosing scope.
Enabling Testability
changes nothing on its own but it allows you to import a module for testing. So instead of
import Module
you can do
@testable import Module
And this has the following effects:
All classes that are public
or internal
are treated as if they are open
. All other symbols that are internal
are treated as if they are public
. Symbols that are fileprivate
or private
keep their access modifier.
In case of ObjC, testability directly changes the visibility of symbols.
By default all symbols in ObjC code are treated as if they are public
in Swift, except for classes, that are treated as if they are open
in Swift.
You can declare a symbol to be __private_extern__
, in which case they behave like internal
in Swift and you can declare certain symbols to be static
(not classes or protocols), in which case they are treated like fileprivate
in Swift.
In the Xcode build settings you can enable Symbols Hidden by Default
, which automatically declares all symbols to be __private_extern__
, unless they are static
. Symbols that shall be visible outside of the current module must then be marked as public using __attribute__((visibility("default")))
, e.g. you can use a define:
#define public __attribute__((visibility("default")))
public
@interface MyClass : NSObject
When you enabled testability, the default visibility is set back to public and thus overrides the Xcode build setting Symbols Hidden by Default
. Also the define __private_extern__
is changed and also means public visibility. Only static
symbols stay hidden.
Careful: If you manually add the compiler flag -fvisibility=hidden
, you force all symbols to be hidden by default, just like the build setting Symbols Hidden by Default
does, however this cannot be overridden by testability. Also if you explicitly mark a symbol hidden using __attribute__((visibility("hidden")))
, this cannot be overridden either.
In case of frameworks, testability changes the visibility of project
headers.
By default frameworks have three visibility levels for header files:
project
: The header is only available when building the framework itself.
private
: The header is available when building the framework as well as when linking against that framework but is removed before that framework is embedded into an application.
public
: The header stays inside the framework at all times, even if the framework is embedded into an application.
Declarations in project
headers are not available when liking against a framework. When testability is enabled, project
headers are treated like private
headers, so a unit test linking against the framework is able to access symbols that were only declared in project
headers and would normally not be visible to code importing that framework.
Upvotes: 3
Reputation: 7331
I happened upon this while tracking down another issue. But perhaps I can give provide a scenario. Why would you ever not want to enable testability?
-fvisibility=hidden.
If you want to use the GCC_SYMBOLS_PRIVATE_EXTERN (aka Symbols Hidden By Default), enable testability has higher precedence and will override this.
In my case, I have a configuration which is copied from Debug and hence has Enable Testability == YES. I have an external static library which was built with -fvisibility=hidden which is used to build one of my own static libs (built with Xcode). However when building my debug builds, I get errors such as (I redacted the function names and paths)
Showing All Messages : Direct access in function ... means the weak symbol cannot be overridden at runtime. This was likely caused by different translation units being compiled with different visibility settings.
From the Apple doc here:
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.
It would seem that it is also there for Swift accessibility. So if you are not using Xcode's testing and Swift, it would also seem like you could do without this.
Upvotes: 12