순수한 Swift 프로젝트에서 단위 테스트를 실행 중인지 앱에 알리는 방법은 무엇입니까?


86

Xcode 6.1에서 테스트를 실행할 때 한 가지 성가신 점은 전체 앱이 스토리 보드와 루트 뷰 컨트롤러를 실행하고 실행해야한다는 것입니다. 내 앱에서는 API 데이터를 가져 오는 서버 호출을 실행합니다. 그러나 테스트를 실행할 때 앱이이 작업을 수행하는 것을 원하지 않습니다.

전 처리기 매크로가 사라진 상태에서 내 프로젝트가 일반 실행이 아닌 테스트 실행 중임을 인식하는 가장 좋은 방법은 무엇입니까? command+ U와 봇에서 정상적으로 실행합니다 .

의사 코드 :

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

"전체 앱은 스토리 보드와 루트 뷰 컨트롤러를 실행하고 실행해야합니다."맞습니까? 나는 그것을 테스트하지 않았지만 나에게 옳지 않은 것 같습니다. 흠 ...
— Fogmeister

예, 응용 프로그램이 실행을 마쳤으며 루트 뷰 컨트롤러에 대한 viewdidload도 실행됩니다
— bogen

아, 방금 테스트했습니다. 그게 사실이라고 생각하지 않았습니다. 이것에 대해 테스트에서 문제를 일으키는 것은 무엇입니까? 아마도 다른 방법이 있습니까?
— Fogmeister 2014

테스트를 염두에두고 앱의 실행을 알리면되므로 이전 전 처리기 매크로와 같은 플래그가 작동하지만 신속하게 지원되지 않습니다.
— 보겐

그래, 근데 왜 그렇게 "필요"해? 그렇게해야한다고 생각하는 이유는 무엇입니까?
— Fogmeister

답변:


44

부작용을 피하기 위해 테스트가 실행 중인지 확인하는 대신 호스트 앱 자체없이 테스트를 실행할 수 있습니다. 프로젝트 설정으로 이동-> 테스트 대상 선택-> 일반-> 테스트-> 호스트 응용 프로그램-> '없음'을 선택합니다. 테스트를 실행하는 데 필요한 모든 파일과 일반적으로 Host 앱 대상에 포함 된 라이브러리를 포함해야합니다.

여기에 이미지 설명 입력


1
호스트 응용 프로그램을 제거한 후 대상에 대한 브리징 헤더를 포함하는 방법은 무엇입니까?
— Bhargav

1
이 질문을 시도했지만 반대의 작업을 제안하는이 답변에 도달 할 때까지 테스트를 위반했습니다. stackoverflow.com/a/30939244/1602270
— eagle.dan.1349

그렇게하면 (예를 들어) cloudKit을 테스트 할 수 있으므로 테스트 중이면 applicationDidFinishLaunching에서 감지하고 YES이면 앱의 주요 클래스를 할당하지 않고 반환하는 것입니다.
— user1105951

86

Elvind의 대답은 순수 "Logic Tests"라고 불리는 것을 갖고 싶다면 나쁘지 않습니다. 포함 된 호스트 애플리케이션을 계속 실행하고 싶지만 테스트 실행 여부에 따라 조건부로 코드를 실행하거나 실행하지 않으려면 다음을 사용하여 테스트 번들이 삽입되었는지 감지 할 수 있습니다.

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

런타임 비용이 디버그 빌드에서만 발생하도록 이 답변에 설명 된 대로 조건부 컴파일 플래그를 사용했습니다 .

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

3
합니까 최신 엑스 코드에서하지 작업 - 환경 변수의 이름이 변경 것으로 보인다
— amleszk

7
이 작동을 중지하지만, 엑스 코드 7.3과 내가 지금 사용하고 확실하지 정확히 XCTestConfigurationFilePath대신 환경 키를 XCInjectBundle.
— ospr

감사 @ospr, 내가 엑스 코드 7.3과 작업에 대한 답변을 편집 한
— 마이클 맥과이어

@tkuichooseyou po ProcessInfo.processInfo.environment에는 키가 없습니다 XCTestConfigurationFilePath. 코드를 공유해 주시겠습니까? 이는 전년도에게 UITest 대상 체크
— 탈 시온

@TalZion 죄송합니다. UITest 대상을 의미한다는 것을 몰랐습니다. NSClassFromString("XCTest")아래 방법 을 시도해 보셨습니까 ?
— tkuichooseyou

44

나는 이것을 application : didFinishLaunchingWithOptions에서 사용합니다.

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

3
이것은 단위 테스트에만 적용되며 새로운 Xcode 7 UI 테스트에는 적용되지 않습니다.
— Jesse

34

기타, 내 생각에 더 간단한 방법 :

앱에 시작 인수로 부울 값을 전달하도록 스키마를 편집합니다. 이렇게 :

Xcode에서 시작 인수 설정

모든 시작 인수는 자동으로 NSUserDefaults .

이제 다음과 같이 BOOL을 얻을 수 있습니다.

BOOL test = [[NSUserDefaults standardUserDefaults] boolForKey:@"isTest"];

2
이것이 내가 지금까지 찾은 가장 깨끗한 방법 인 것 같습니다. 테스트 대상에 모든 앱 파일을 추가 할 필요가 없으며 "XCTestConfigurationFilePath"또는 .NET Framework를 검사하는 이상한 솔루션에 의존 할 필요가 없습니다 NSClassFromString("XCTest"). 나는 전역 함수와 스위프트에이 솔루션을 구현했습니다func isRunningTests() -> Bool { return UserDefaults.standard.bool(forKey: "isRunningTests") }
— 케빈 허쉬에게

21

테스트 내에서 실행 중인지 여부를 알고 싶은 것이 완전히 합법적이라고 생각합니다. 이것이 도움이 될 수있는 여러 가지 이유가 있습니다. 예를 들어 테스트를 실행할 때 App Delegate의 application-did / will-finish-launching 메서드에서 일찍 돌아와서 단위 테스트와 관련이없는 코드에 대해 테스트를 더 빠르게 시작합니다. 그러나 다른 여러 가지 이유로 순수한 "논리적"테스트를 수행 할 수 없습니다.

위의 @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];
}

이 접근 방식의 한 가지주의 사항은 XCTest에서 허용하는 것처럼 기본 앱 체계에서 테스트를 실행하면 (즉, 테스트 체계 중 하나를 선택하지 않음)이 환경 변수 집합을 얻지 못한다는 것입니다.


5
'실행'에 추가하는 대신 모든 구성표에 대해 '테스트'에 추가하면 더 도움이되지 않을까요? 이런 식으로 isRunningTests는 모든 체계에서 작동합니다.
— Vishal Singh

@VishalSingh 예, 더 깨끗하다고 ​​믿습니다. 그 접근 방식을 시도해 보셨습니까? 그것이 당신에게 잘 작동하는지 알려주십시오.
— idStar

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

용법

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

다른 답변과 달리 이것은 작동합니다. 감사합니다.
— n13

8

@Jessy와 @Michael McGuire의 결합 된 접근 방식

(허용되는 대답은 프레임 워크를 개발하는 동안 도움이되지 않습니다)

그래서 여기에 코드가 있습니다 :

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

1
이것은 훨씬 더 좋습니다! 더 짧고 프레임 워크 호환!
— blackjacx

스위프트 패키지에서 작업 중이
— D. Greg

4

다음은 단위 테스트를 위해 Swift 4 / Xcode 9에서 사용한 방법입니다. Jesse의 답변을 기반으로 합니다.

스토리 보드가로드되는 것을 막는 것은 쉽지 않지만 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 테스트에서는 이와 같은 작업을 수행해서는 안됩니다.)


3

여기서 스키마에 따라 앱에 런타임 인수를 전달할 수 있습니다.

여기에 이미지 설명 입력

그러나 나는 그것이 실제로 필요한지 아닌지 의문을 품습니다.


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


: 루프 같은 Swiftilized 할 수return threadDictionary.allKeys.anyMatch { ($0 as? String)?.split(separator: ".").contains("xctest") == true }
— 로저 오바

2

이러한 접근 방식 중 일부는 UITest에서 작동하지 않으며 기본적으로 앱 코드 자체로 테스트하는 경우 (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의 의견에 감사드립니다. 나는 그것을 예제로 확장했습니다.


2

내가 사용했던 방법은 Xcode 12 베타 1에서 작동하지 않습니다.이 질문에 대한 모든 빌드 기반 답변을 시도한 후 @ODB의 답변에 영감을 받았습니다. 다음은 실제 장치와 시뮬레이터 모두에서 작동하는 매우 간단한 솔루션의 Swift 버전입니다. 또한 상당히 "방출 증명"이어야합니다.

테스트 설정에 삽입 :

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

앱에 삽입 :

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

그것을 사용하려면 :

    if isTesting {
        // Only if testing
    } else {
        // Only if not testing
    }

0

나를 위해 일했다 :

목표 -C

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

빠른: ProcessInfo.processInfo.environment["DYLD_INSERT_LIBRARIES"]?.contains("libXCTTargetBootstrapInject") ?? false


0

먼저 테스트를 위해 변수를 추가합니다.

여기에 이미지 설명 입력

코드에서 사용하십시오.

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

0

분명히 Xcode12 에서는 새로운 기능을 사용하는 XCTestBundlePath대신 환경 키에서 검색해야합니다.XCTestConfigurationFilePathXCTestPlan

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.