순수한 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.