如何让应用知道它的运行单元是否在纯Swift项目中测试?

bog*_*gen 68 xcode ios xctest swift

在XCode 6.1中运行测试时,一个令人讨厌的事情是整个应用程序必须运行并启动其storyboard和root viewController.在我的应用程序中,它运行一些获取API数据的服务器调用.但是,我不希望应用程序在运行其测试时执行此操作.

随着预处理器宏的消失,对于我的项目来说最好的是要知道它是在运行测试而不是普通的启动时启动的吗?我用CMD + U和机器人正常运行它们.

伪代码将是:

// Appdelegate.swift

if runningTests() {
   return
} else {
   // do ordinary api calls
}
Run Code Online (Sandbox Code Playgroud)

Mic*_*ire 71

如果你想拥有曾经被称为纯粹的"逻辑测试"的话,Elvind的答案也不错.如果您仍然希望运行包含主机应用程序但是有条件地执行或不执行代码,具体取决于是否运行了测试,您可以使用以下内容来检测是否已注入测试包:

if NSProcessInfo.processInfo().environment["XCTestConfigurationFilePath"] != nil {
     // Code only executes when tests are running
}
Run Code Online (Sandbox Code Playgroud)

我使用了本回答中描述的条件编译标志,因此运行时成本仅在调试版本中产生:

#if DEBUG
    if NSProcessInfo.processInfo().environment["XCTestConfigurationFilePath"] != nil {
        // Code only executes when tests are running
    }
#endif
Run Code Online (Sandbox Code Playgroud)

编辑Swift 3.0

if ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil {
    // Code only executes when tests are running
}
Run Code Online (Sandbox Code Playgroud)

  • 不确定这是什么时候停止工作,但是使用Xcode 7.3我现在使用`XCTestConfigurationFilePath`环境键而不是`XCInjectBundle`. (7认同)
  • 在最新的Xcode中不起作用 - 环境变量名称似乎已更改 (2认同)

Eiv*_*ler 41

您可以在没有主机应用程序本身的情况下运行测试,而不是检查测试是否正在运行以避免副作用.转到项目设置 - >选择测试目标 - >常规 - >测试 - >主机应用程序 - >选择"无".只需记住包含运行测试所需的所有文件,以及Host应用程序目标通常包含的库.

在此输入图像描述


Jes*_*sse 38

我在应用程序中使用它:didFinishLaunchingWithOptions:

// Return if this is a unit test
if let _ = NSClassFromString("XCTest") {
    return true
}
Run Code Online (Sandbox Code Playgroud)

  • 这仅适用于单元测试,不适用于新的 Xcode 7 UI 测试。 (3认同)

iCa*_*mba 22

其他,在我看来更简单:

您编辑您的方案以将布尔值作为启动参数传递给您的应用程序.像这样:

在Xcode中设置启动参数

所有启动参数都会自动添加到您的NSUserDefaults.

您现在可以像以下一样获得BOOL:

BOOL test = [[NSUserDefaults standardUserDefaults] boolForKey:@"isTest"];
Run Code Online (Sandbox Code Playgroud)

  • 似乎这是迄今为止我发现的最干净的方式。我们不必将所有应用程序文件添加到测试目标,也不必依赖一些奇怪的解决方案检查“XCTestConfigurationFilePath”或“NSClassFromString(“XCTest”)”。我已经在 Swift 中使用全局函数 ```func isRunningTests() -> Bool { return UserDefaults.standard.bool(forKey: "isRunningTests") }``` 实现了这个解决方案 (3认同)

idS*_*tar 18

我相信想知道你是否在考试中跑步是完全合法的.这有很多原因可以帮助我们.例如,在运行测试时,我会从App Delegate中的application-did/will-finish-started方法提前返回,这使得测试开始更快,因为代码与我的单元测试没有密切关系.然而,由于其他一些原因,我无法进行纯粹的"逻辑"测试.

我过去常常使用@Michael McGuire描述的优秀技术.但是,我注意到Xcode 6.4/iOS8.4.1停止了为我工作(也许它早点破了).

也就是说,在我的框架的测试目标内运行测试时,我不再看到XCInjectBundle.也就是说,我正在测试框架的测试目标中运行.

因此,利用@Fogmeister建议的方法,我的每个测试方案现在都设置了一个我可以检查的环境变量.

在此输入图像描述

然后,这里有一些我在一个叫做类的代码APPSTargetConfiguration可以为我回答这个简单的问题.

static NSNumber *__isRunningTests;

+ (BOOL)isRunningTests;
{
    if (!__isRunningTests) {
        NSDictionary *environment = [[NSProcessInfo processInfo] environment];
        NSString *isRunningTestsValue = environment[@"APPS_IS_RUNNING_TEST"];
        __isRunningTests = @([isRunningTestsValue isEqualToString:@"YES"]);
    }

    return [__isRunningTests boolValue];
}
Run Code Online (Sandbox Code Playgroud)

这种方法的一个警告是,如果您从主应用程序方案运行测试,因为XCTest将允许您(即,不选择您的测试方案之一),您将不会获得此环境变量集.

  • 而不是在"运行"中添加它,如果我们在所有方案的"测试"中添加它不会更有帮助吗?这样,isRunningTests将适用于所有方案. (2认同)

zdr*_*kin 13

您现在就可以使用

if ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] == nil {
            // Code only executes when tests are not running
}
Run Code Online (Sandbox Code Playgroud)

该选项在 Xcode 12.5 中已弃用

首先添加用于测试的变量:

在此输入图像描述

并在您的代码中使用它:

 if ProcessInfo.processInfo.environment["IS_UNIT_TESTING"] == "1" {
                 // Code only executes when tests are running
 } 
Run Code Online (Sandbox Code Playgroud)


The*_*eer 10

这是做到这一点的快捷方式。

extension Thread {
  var isRunningXCTest: Bool {
    for key in self.threadDictionary.allKeys {
      guard let keyAsString = key as? String else {
        continue
      }

      if keyAsString.split(separator: ".").contains("xctest") {
        return true
      }
    }
    return false
  }
}
Run Code Online (Sandbox Code Playgroud)

这就是你如何使用它:

if Thread.current.isRunningXCTest {
  // test code goes here
} else {
  // other code goes here
}

Run Code Online (Sandbox Code Playgroud)

这是完整的文章: https://medium.com/@theinkedengineer/check-if-app-is-running-unit-tests-the-swift-way-b51fbfd07989

  • for 循环可以像这样 Swiftilized: `return threadDictionary.allKeys.anyMatch { ($0 as? String)?.split(separator: ".").contains("xctest") == true }` (2认同)
  • 还将补充一点,如果您想检测它是否是标记为故事板入口点的视图控制器的生命周期函数中的单元测试,则这不起作用。线程字典尚不包含这些键。在我的初始视图控制器上的断点上,我可以看到“NSClassFromString”变体有效,并且“ProcessInfo”变体也有效。 (2认同)

m8l*_*abs 8

@Jessy 和 @Michael McGuire 的组合方法

(在开发框架时,接受的答案对您没有帮助)

所以这里是代码:

#if DEBUG
        if (NSClassFromString(@"XCTest") == nil) {
            // Your code that shouldn't run under tests
        }
#else
        // unconditional Release version
#endif
Run Code Online (Sandbox Code Playgroud)


neo*_*eye 8

var isRunningTests: Bool {
    return ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil
}
Run Code Online (Sandbox Code Playgroud)

用法

if isRunningTests {
    return "lena.bmp"
}
return "facebook_profile_photo.bmp"
Run Code Online (Sandbox Code Playgroud)


Jos*_*phH 5

这是我在 Swift 4 / Xcode 9 中用于单元测试的方法。它基于Jesse's answer

完全阻止故事板被加载并不容易,但是如果您在 didFinishedLaunching 的开头添加它,那么它会让您的开发人员非常清楚发生了什么:

func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions:
                 [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    #if DEBUG
    if let _ = NSClassFromString("XCTest") {
        // If we're running tests, don't launch the main storyboard as
        // it's confusing if that is running fetching content whilst the
        // tests are also doing so.
        let viewController = UIViewController()
        let label = UILabel()
        label.text = "Running tests..."
        label.frame = viewController.view.frame
        label.textAlignment = .center
        label.textColor = .white
        viewController.view.addSubview(label)
        self.window!.rootViewController = viewController
        return true
    }
    #endif
Run Code Online (Sandbox Code Playgroud)

(您显然不应该在 UI 测试中做这样的事情,因为您确实希望应用程序正常启动!)


Chu*_*k H 5

我一直使用的方法在 Xcode 12 beta 1 中停止工作。在尝试了这个问题的所有基于构建的答案后,我受到了 @ODB 的答案的启发。这是一个相当简单的解决方案的 Swift 版本,适用于真实设备和模拟器。它也应该是相当“发布证明”。

插入测试设置:

let app = XCUIApplication()
app.launchEnvironment.updateValue("YES", forKey: "UITesting")
app.launch()
Run Code Online (Sandbox Code Playgroud)

在应用程序中插入:

let isTesting: Bool = (ProcessInfo.processInfo.environment["UITesting"] == "YES")
Run Code Online (Sandbox Code Playgroud)

使用方法:

    if isTesting {
        // Only if testing
    } else {
        // Only if not testing
    }
Run Code Online (Sandbox Code Playgroud)