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 MyAwesomeModuleMyAwesomeProject 中使用 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
    }
}