在XCTest的@testable幕后发生了什么?

时间:2017-10-17 19:57:47

标签: ios swift macos xctest

我知道

@testable import MyModule

能够从"测试"中探索MyModule的非公开成员。 (使用" testTarget"构建)模块MyModuleTests

我需要在我的"非测试"中使用相同的功能。模块。不在生产中,只是在调试模式下。

我的问题是:你知道怎么做吗?

相关(我认为,更难的问题):@testable背后实际发生了什么魔法?

2 个答案:

答案 0 :(得分:16)

要回答您的问题,出于调试目的,您实际上可以使用它。我们假设您有一个工作区MyAwesomeWkspace和一个MyAwesomeProject内的项目。

现在,创建一个名为framework的新module又名MyAwesomeModule。在该模块内部创建一个名为Person的非公共类。

如果您通过Person尝试使用MyAwesomeProject内的课程import MyAwesomeModule,然后使用let p = Person()之类的内容,则会出现错误。

但如果你做@testable import MyAwesomeModule,那么神奇就会发生,现在你可以使用这个类了。

基本上@testable允许您测试您没有宣布公开的内容。注释仅适用于import,因为您可以看到here

因此,为了工作,目标将使用-enable-testing进行编译,以便您可以访问非公共成员。至少基于here

的基础

因为默认情况下,debug构建配置是使用-enable-testing编译的,所以我向您展示的示例将起作用。但是,如果您将构建配置更改为release,则会发现错误Module .. was not compiled for testing,因为release配置未使用该标记构建。

  

Swift访问控制模型,如访问控制中所述   Swift编程语言(Swift 4)的一部分,防止了   外部实体访问在app中声明为内部的任何内容   或框架。默认情况下,为了能够从您的访问这些项目   测试代码,您需要至少提升他们的访问级别   公众,减少了Swift类型安全的好处。

     

Xcode为这个问题提供了一个由两部分组成的解决方案:

     

将“启用可测试性”构建设置设置为“是”时,即   对于新项目中的测试构建,默认情况下为true,Xcode包含   编译期间的-enable-testing标志。这使得在已编译模块中声明的Swift实体有资格获得更高级别的访问权限。   将@testable属性添加到导入语句时   在启用测试的情况下编译的模块,您激活提升的访问   对于该范围内的该模块。标记为的类和类成员   内部或公共行为就好像被标记为开放一样。其他实体   标记为内部行为,好像他们被宣布为公开。

更多here

延迟编辑:swift的一个很酷的部分是开源。因此,如果您想深入了解"魔法",请查看:https://github.com/apple/swift

答案 1 :(得分:-1)

@testable 导入 和 -enable-testing

[Swift access modifiers]
[Swift module]

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)classpublic 访问级别对于当前模块是 可见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
    }
}