sourcecode

앱이 순수한 스위프트 프로젝트에서 유닛 테스트를 실행하는지 어떻게 알 수 있습니까?

copyscript 2023. 6. 3. 08:39
반응형

앱이 순수한 스위프트 프로젝트에서 유닛 테스트를 실행하는지 어떻게 알 수 있습니까?

Xcode 6.1에서 테스트를 실행할 때 한 가지 짜증나는 점은 전체 앱이 스토리보드와 루트 뷰 컨트롤러를 실행하고 실행해야 한다는 것입니다.내 앱에서 이것은 API 데이터를 가져오는 몇 가지 서버 호출을 실행합니다.하지만, 저는 앱이 테스트를 실행할 때 이것을 하지 않기를 원합니다.

프리프로세서 매크로가 사라졌는데, 제 프로젝트가 일반적인 실행이 아닌 테스트를 통해 시작되었다는 것을 인식하는 것이 가장 좋은 방법은 무엇입니까?저는 +와 봇으로 정상적으로 작동합니다.

의사 코드:

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

순수한 "논리 테스트"라고 불리는 것을 원한다면 Elvind의 대답은 나쁘지 않습니다.포함하는 호스트 응용 프로그램을 실행하면서도 테스트 실행 여부에 따라 조건부로 코드를 실행하거나 실행하지 않으려면 다음을 사용하여 테스트 번들이 주입되었는지 여부를 탐지할 수 있습니다.

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
}

응용 프로그램에서 사용합니다. 옵션으로 시작 완료:

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

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

여기에 이미지 설명 입력

다른 방법은, 제 생각에 더 간단합니다.

계획을 편집하여 부울 값을 실행 인수로 앱에 전달할 수 있습니다.다음과 같이:

Xcode에서 시작 인수 설정

으로 사용자의 됩니다.NSUserDefaults.

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

BOOL test = [[NSUserDefaults standardUserDefaults] boolForKey:@"isTest"];
var isRunningTests: Bool {
    return ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil
}

사용.

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

저는 여러분이 시험 안에서 뛰고 있는지 아닌지를 알고 싶어하는 것은 완전히 합법적이라고 믿습니다.그것이 도움이 될 수 있는 많은 이유가 있습니다.예를 들어, 테스트를 실행할 때는 App Delegate의 application-did/will-finish-launch 메서드에서 일찍 돌아와 단위 테스트와 동일하지 않은 코드에 대한 테스트를 더 빨리 시작합니다.하지만, 저는 많은 다른 이유로 순수한 "논리" 테스트를 할 수 없습니다.

저는 위의 @Michael McGuire가 설명한 훌륭한 기술을 사용하곤 했습니다.하지만 Xcode 6.4/iOS 8.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에서 허용하는 것처럼 기본 애플리케이션 구성표에서 테스트를 실행하면(즉, 테스트 구성표 중 하나를 선택하지 않음) 이 환경 변수 집합을 얻을 수 없다는 것입니다.

2022년 업데이트

/// Returns true if the thread is running an `XCTest`.
var isRunningXCTest: Bool {
  threadDictionary.allKeys
    .contains {
      ($0 as? String)?
        .range(of: "XCTest", options: .caseInsensitive) != nil
    }
  }

원답

이것이 그것을 하는 빠른 방법입니다.

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

@Jessy와 @Michael McGuire의 결합된 접근법

(승인된 답변은 프레임워크를 개발하는 동안 도움이 되지 않습니다.)

코드는 다음과 같습니다.

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

지금 사용할 수 있습니다.

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

이 옵션은 Xcode 12.5에서 더 이상 사용되지 않습니다.

먼저 테스트를 위한 변수 추가:

여기에 이미지 설명 입력

코드에 사용할 수 있습니다.

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

Swift 4 / Xcode 9에서 유닛 테스트를 위해 사용해온 방법입니다.제시의 대답을 바탕으로 한 것입니다.

스토리보드가 로드되는 것을 방지하는 것은 쉽지 않지만, 실행 완료를 시작할 때 이것을 추가하면 개발자들에게 무슨 일이 일어나고 있는지 매우 명확하게 알 수 있습니다.

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 테스트에서는 분명히 이와 같은 작업을 수행해서는 안 됩니다!)

여기의 체계에 따라 런타임 인수를 앱에 전달할 수 있습니다...

여기에 이미지 설명 입력

하지만 저는 그것이 실제로 필요한지 아닌지 의문이 듭니다.

제가 사용하던 방법이 Xcode 12 베타 1에서 작동하지 않게 되었습니다.이 질문에 대한 빌드 기반 답변을 모두 시도한 후 @ODB의 답변에 영감을 받았습니다.다음은 Real Devices와 Simulator 모두에서 작동하는 매우 간단한 솔루션의 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
    }

이러한 접근 방식 중 일부는 UIT 테스트와 함께 작동하지 않으며 기본적으로 앱 코드 자체를 사용하여 테스트하는 경우(UIT 테스트 대상에 특정 코드를 추가하는 것이 아님).

결국 테스트의 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의 의견에 감사드립니다. 저는 그것을 예시로 확장했습니다.

Xcode12에서는 환경 키를 검색해야 합니다.XCTestBundlePath대신에XCTestConfigurationFilePath새 제품을 사용하는 경우XCTestPlan

이걸 내 스위프트에 사용하고 있습니다.UIScene.body(Xcode 12.5):

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

나를 위해 일한 것:

목표-C

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

스위프트:ProcessInfo.processInfo.environment["DYLD_INSERT_LIBRARIES"]?.contains("libXCTTargetBootstrapInject") ?? false

Xcode 13.4.1 및 14.0b6 기준으로 실행과 테스트의 차이는 다음과 같습니다.

XCInjectBundleInto = unused
XCTestBundlePath = PlugIns/«Name of Your App»Tests.xctest
XCTestConfigurationFilePath = 
XCTestSessionIdentifier = 3B127C55-13E6-4F13-84BD-53FF9FBBC8B2

확실히 안정성을 보장하는 것은 아니지만 두 가지 주요 버전에서 일관성을 유지하는 것은 매우 좋습니다.

언급URL : https://stackoverflow.com/questions/27500940/how-to-let-the-app-know-if-its-running-unit-tests-in-a-pure-swift-project

반응형