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

时间:2014-12-16 09:04:59

标签: ios swift xctest

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

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

伪代码将是:

// Appdelegate.swift

if runningTests() {
   return
} else {
   // do ordinary api calls
}

16 个答案:

答案 0 :(得分:65)

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

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

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

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

编辑Swift 3.0

if ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil {
    // Code only executes when tests are running
}

答案 1 :(得分:36)

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

enter image description here

答案 2 :(得分:33)

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

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

答案 3 :(得分:17)

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

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

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

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

enter image description here

然后,这里有一些名为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];
}

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

答案 4 :(得分:16)

其他,在我看来更简单:

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

Set launch arguments in Xcode

所有启动参数都会自动添加到您的public class SocialAController : ApiController { private ISocialClient client; ... }

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

NSUserDefaults

答案 5 :(得分:7)

var isRunningTests: Bool {
    return ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil
}

用法

if isRunningTests {
    return "lena.bmp"
}
return "facebook_profile_photo.bmp"

答案 6 :(得分:3)

这是我在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

(你显然不应该做这样的UI测试,你希望应用程序正常启动!)

答案 7 :(得分:3)

这是快速的方法。

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
  }
}

这是您的使用方式:

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

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

答案 8 :(得分:2)

您可以根据此处的方案将运行时参数传递到应用程序中......

enter image description here

但我会质疑它是否真的需要。

答案 9 :(得分:2)

我一直使用的方法在Xcode 12 beta 1中停止工作。在尝试了所有基于构建的问题答案之后,@ ODB的答案启发了我。这是一个相当简单的解决方案的Swift版本,适用于Real Devices和Simulators。它也应该是相当“释放的证明”。

插入测试设置:

import serial


ser = serial.Serial(
    port = "COM2",
    timeout = 1,
    baudrate=9600,
    parity=serial.PARITY_EVEN,
    stopbits=serial.STOPBITS_ONE,
    bytesize=serial.SEVENBITS,




)

ser.write(str.encode("W"))


weight = ser.read(8)
print(weight.decode('ascii'), weight)

插入应用程序:

let app = XCUIApplication()
app.launchEnvironment.updateValue("YES", forKey: "UITesting")
app.launch()

要使用它:

let isTesting: Bool = (ProcessInfo.processInfo.environment["UITesting"] == "YES")

答案 10 :(得分:1)

@Jessy和@Michael McGuire的综合方法

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

所以这是代码:

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

答案 11 :(得分:0)

其中一些方法不适用于UITests,如果您基本上使用应用程序代码本身进行测试(而不是将特定代码添加到UITest目标中)。

我最终在测试的setUp方法中设置了一个环境变量:

XCUIApplication *testApp = [[XCUIApplication alloc] init];

// set launch environment variables
NSDictionary *customEnv = [[NSMutableDictionary alloc] init];
[customEnv setValue:@"YES" forKey:@"APPS_IS_RUNNING_TEST"];
testApp.launchEnvironment = customEnv;
[testApp launch];

请注意,这对我的测试是安全的,因为我目前不使用任何其他launchEnvironment值;如果你这样做,你当然希望先复制任何现有的值。

然后在我的应用程序代码中,如果/当我想在测试期间排除某些功能时,我会查找此环境变量:

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

注意 - 感谢RishiG的评论给了我这个想法;我只是将其扩展为一个例子。

答案 12 :(得分:0)

为我工作:

Objective-C

[[NSProcessInfo processInfo].environment[@"DYLD_INSERT_LIBRARIES"] containsString:@"libXCTTargetBootstrapInject"]

迅速:     ProcessInfo.processInfo.environment["DYLD_INSERT_LIBRARIES"]?.contains("libXCTTargetBootstrapInject") ?? false

答案 13 :(得分:0)

首先添加变量进行测试:

enter image description here

并在您的代码中使用它:

 if ProcessInfo.processInfo.environment["IS_UNIT_TESTING"] == "1" {
                 // Code only executes when tests are running
 } 

答案 14 :(得分:0)

显然,在Xcode12中,如果您使用的是新的XCTestBundlePath,则需要搜索环境键XCTestConfigurationFilePath而不是XCTestPlan

答案 15 :(得分:0)

我在我的 SwiftUI Scene.body (Xcode 12.5) 中使用它:

if UserDefaults.standard.value(forKey: "XCTIDEConnectionTimeout") == nil {
  // not unit testing
} else {
  // unit testing
}