XCTest 的@testable 幕后发生了什么?
What's happening behind the scenes in XCTest's @testable?
我知道
@testable import MyModule
能够从 "test"(使用 "testTarget" 构建)模块 MyModuleTests
.[=14= 探索 MyModule
的非 public 成员]
我的 "non-test" 模块需要相同的功能。未在生产中,仅处于调试模式。
我的问题是:你知道怎么做吗?
以及相关的(我认为,更难的问题):@testable
背后究竟发生了什么魔法?
为了回答你的问题,为了调试目的,你实际上可以使用它。假设您有一个工作区 MyAwesomeWkspace
和一个项目 MyAwesomeProject
。
现在,创建一个名为 MyAwesomeModule
的新 framework
又名 module
。在该模块中创建一个名为 Person
的非 public class。
如果您尝试通过 import MyAwesomeModule
在 MyAwesomeProject
中使用 class Person
,然后执行 let p = Person()
之类的操作,您将遇到错误。
但如果你这样做 @testable import MyAwesomeModule
,奇迹就会发生,你现在可以使用 class。
基本上 @testable
允许您测试未声明的内容 public。如您所见,注释仅适用于 import
here。
所以为了工作,目标是用 -enable-testing
编译的,这样你就可以访问非 public 成员。至少基于 here
因为默认情况下,debug
构建配置是使用 -enable-testing
编译的,我向您展示的示例将起作用。但是,如果您将构建配置更改为 release
,您将看到一个错误提示 Module .. was not compiled for testing
,因为 release
配置不是使用该标志构建的。
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.
更多here
后期编辑:swift 最酷的部分之一是开源。因此,如果您想深入了解 "magic",请查看:https://github.com/apple/swift
@testable import 和-enable-testing
consumer side uses @testable import -> producer side should use `-enable-testing` flag
生产方:启用-enable-testing
Enable Testability(ENABLE_TESTABILITY)
- 是
Other Swift Flags(OTHER_SWIFT_FLAGS)
- -enable-testing
消费方:@testable
internal(default)
和 public
class 的访问级别是 可见 当前模块为 open
internal(default)
其他人(结构、枚举)的访问级别对于当前模块是 可见的 public
如果您使用 @testable
构建测试模式(消费者),但生产者不包含 -enable-testing
,您会得到
Module '<module_name>' was not compiled for testing
一些实验:
一些模块
internal class SomeInternalClass {
internal func foo() { }
}
public class SomePublicClass {
public func foo() { }
}
internal class SomeInternalStruct {
internal func foo() { }
}
internal enum SomeInternalEnum: String {
case foo = "hello world"
}
测试:如果您省略 @testable
将出现下一个错误
import XCTest
@testable import ExperimentsTests
class ExperimentsTestsTests: XCTestCase {
func testExample() throws {
let someInternalStruct = SomeInternalStruct() //Cannot find 'SomeInternalStruct' in scope
someInternalStruct.foo()
let someInternalEnum = SomeInternalEnum(rawValue: "") //Cannot find 'SomeInternalEnum' in scope
SomeInternalEnum.foo //Cannot find 'SomeInternalEnum' in scope
}
class SomePublicSubClass: SomePublicClass { //Cannot inherit from non-open class 'SomePublicClass' outside of its defining module
override func foo() { } //Overriding non-open instance method outside of its defining module
}
class SomeInternalSubClass: SomeInternalClass { //Cannot find type 'SomeInternalClass' in scope
override func foo() { } //Method does not override any method from its superclass
}
}
我知道
@testable import MyModule
能够从 "test"(使用 "testTarget" 构建)模块 MyModuleTests
.[=14= 探索 MyModule
的非 public 成员]
我的 "non-test" 模块需要相同的功能。未在生产中,仅处于调试模式。
我的问题是:你知道怎么做吗?
以及相关的(我认为,更难的问题):@testable
背后究竟发生了什么魔法?
为了回答你的问题,为了调试目的,你实际上可以使用它。假设您有一个工作区 MyAwesomeWkspace
和一个项目 MyAwesomeProject
。
现在,创建一个名为 MyAwesomeModule
的新 framework
又名 module
。在该模块中创建一个名为 Person
的非 public class。
如果您尝试通过 import MyAwesomeModule
在 MyAwesomeProject
中使用 class Person
,然后执行 let p = Person()
之类的操作,您将遇到错误。
但如果你这样做 @testable import MyAwesomeModule
,奇迹就会发生,你现在可以使用 class。
基本上 @testable
允许您测试未声明的内容 public。如您所见,注释仅适用于 import
here。
所以为了工作,目标是用 -enable-testing
编译的,这样你就可以访问非 public 成员。至少基于 here
因为默认情况下,debug
构建配置是使用 -enable-testing
编译的,我向您展示的示例将起作用。但是,如果您将构建配置更改为 release
,您将看到一个错误提示 Module .. was not compiled for testing
,因为 release
配置不是使用该标志构建的。
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.
更多here
后期编辑:swift 最酷的部分之一是开源。因此,如果您想深入了解 "magic",请查看:https://github.com/apple/swift
@testable import
consumer side uses @testable import -> producer side should use `-enable-testing` flag
生产方:启用-enable-testing
Enable Testability(ENABLE_TESTABILITY)
- 是Other Swift Flags(OTHER_SWIFT_FLAGS)
--enable-testing
消费方:@testable
internal(default)
和public
class 的访问级别是 可见 当前模块为open
internal(default)
其他人(结构、枚举)的访问级别对于当前模块是 可见的public
如果您使用 @testable
构建测试模式(消费者),但生产者不包含 -enable-testing
,您会得到
Module '<module_name>' was not compiled for testing
一些实验:
一些模块
internal class SomeInternalClass {
internal func foo() { }
}
public class SomePublicClass {
public func foo() { }
}
internal class SomeInternalStruct {
internal func foo() { }
}
internal enum SomeInternalEnum: String {
case foo = "hello world"
}
测试:如果您省略 @testable
将出现下一个错误
import XCTest
@testable import ExperimentsTests
class ExperimentsTestsTests: XCTestCase {
func testExample() throws {
let someInternalStruct = SomeInternalStruct() //Cannot find 'SomeInternalStruct' in scope
someInternalStruct.foo()
let someInternalEnum = SomeInternalEnum(rawValue: "") //Cannot find 'SomeInternalEnum' in scope
SomeInternalEnum.foo //Cannot find 'SomeInternalEnum' in scope
}
class SomePublicSubClass: SomePublicClass { //Cannot inherit from non-open class 'SomePublicClass' outside of its defining module
override func foo() { } //Overriding non-open instance method outside of its defining module
}
class SomeInternalSubClass: SomeInternalClass { //Cannot find type 'SomeInternalClass' in scope
override func foo() { } //Method does not override any method from its superclass
}
}