단위 테스트 내부에서 코드가 번들 자원을 찾을 수없는 이유는 무엇입니까?


184

단위 테스트중인 일부 코드는 리소스 파일을로드해야합니다. 다음 줄이 포함되어 있습니다.

NSString *path = [[NSBundle mainBundle] pathForResource:@"foo" ofType:@"txt"];

응용 프로그램에서는 제대로 실행되지만 단위 테스트 프레임 워크 pathForResource:로 실행하면 nil을 반환하여 찾을 수 없음을 의미합니다 foo.txt.

유닛 테스트 대상 foo.txtCopy Bundle Resources 빌드 단계에 포함되어 있는지 확인했는데 왜 파일을 찾을 수 없습니까?

답변:


316

단위 테스트 장치가 코드를 실행할 때 단위 테스트 번들이 기본 번들 이 아닙니다 .

애플리케이션이 아닌 테스트를 실행하더라도 애플리케이션 번들은 여전히 ​​기본 번들입니다. (이것은 아마도 테스트중인 코드가 잘못된 번들을 검색하지 못하게합니다.) 따라서 단위 테스트 번들에 자원 파일을 추가하면 기본 번들을 검색해도 찾을 수 없습니다. 위의 줄을 다음과 같이 바꾸면 :

NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSString *path = [bundle pathForResource:@"foo" ofType:@"txt"];

그런 다음 코드는 단위 테스트 클래스가있는 번들을 검색하고 모든 것이 잘됩니다.


나를 위해 작동하지 않습니다. 여전히 테스트 번들이 아닌 빌드 번들입니다.
Chris

1
@Chris 샘플 라인 self에서 테스트 케이스 클래스가 아닌 기본 번들의 클래스를 참조 한다고 가정 합니다. [self class]기본 번들의 클래스로 교체하십시오 . 예제를 편집하겠습니다.
benzado

@benzado 번들은 여전히 ​​똑같습니다 (빌드). self 또는 AppDelegate를 사용할 때 둘 다 기본 번들에 있습니다. 주 대상의 빌드 단계를 확인하면 두 파일이 모두 있습니다. 그러나 런타임에 주 번들과 테스트 번들을 다르게하고 싶습니다. 번들이 필요한 코드는 기본 번들에 있습니다. 다음과 같은 문제가 있습니다. png 파일을로드하고 있습니다. 일반적으로이 파일은 사용자가 서버에서 파일을 다운로드하기 때문에 기본 번들에 없습니다. 그러나 테스트를 위해 테스트 번들의 파일을 기본 번들로 복사하지 않고 사용하고 싶습니다.
Chris

2
@Chris 나는 이전 편집과 실수를하고 답을 다시 편집했다. 테스트시 앱 번들은 여전히 ​​기본 번들입니다. 단위 테스트 번들에있는 자원 파일을로드하려면 단위 테스트 번들 bundleForClass:의 클래스와 함께 사용해야 합니다. 단위 테스트 코드에서 파일의 경로를 얻은 다음 경로 문자열을 다른 코드로 전달해야합니다.
benzado

이것은 효과가 있지만 실행 배포와 테스트 배포를 어떻게 구별 할 수 있습니까? 테스트 인 경우 기본 번들의 클래스에있는 테스트 번들의 리소스가 필요합니다. 정기적 인 '실행'인 경우 테스트 번들이 아닌 기본 번들의 리소스가 필요합니다. 어떤 생각?
Chris

80

스위프트 구현 :

스위프트 2

let testBundle = NSBundle(forClass: self.dynamicType)
let fileURL = testBundle.URLForResource("imageName", withExtension: "png")
XCTAssertNotNil(fileURL)

스위프트 3, 스위프트 4

let testBundle = Bundle(for: type(of: self))
let filePath = testBundle.path(forResource: "imageName", ofType: "png")
XCTAssertNotNil(filePath)

번들은 구성의 기본 및 테스트 경로를 발견하는 방법을 제공합니다.

@testable import Example

class ExampleTests: XCTestCase {

    func testExample() {
        let bundleMain = Bundle.main
        let bundleDoingTest = Bundle(for: type(of: self ))
        let bundleBeingTested = Bundle(identifier: "com.example.Example")!

        print("bundleMain.bundlePath : \(bundleMain.bundlePath)")
        // …/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Xcode/Agents
        print("bundleDoingTest.bundlePath : \(bundleDoingTest.bundlePath)")
        // …/PATH/TO/Debug/ExampleTests.xctest
        print("bundleBeingTested.bundlePath : \(bundleBeingTested.bundlePath)")
        // …/PATH/TO/Debug/Example.app

        print("bundleMain = " + bundleMain.description) // Xcode Test Agent
        print("bundleDoingTest = " + bundleDoingTest.description) // Test Case Bundle
        print("bundleUnderTest = " + bundleBeingTested.description) // App Bundle

Xcode 6 | 7 | 8 | 9에서 단위 테스트 번들 경로Developer/Xcode/DerivedData다음과 같습니다.

/Users/
  UserName/
    Library/
      Developer/
        Xcode/
          DerivedData/
            App-qwertyuiop.../
              Build/
                Products/
                  Debug-iphonesimulator/
                    AppTests.xctest/
                      foo.txt

... Developer/CoreSimulator/Devices 일반 (비 테스트) 번들 경로 와 별도입니다 .

/Users/
  UserName/
    Library/
    Developer/
      CoreSimulator/
        Devices/
          _UUID_/
            data/
              Containers/
                Bundle/
                  Application/
                    _UUID_/
                      App.app/

또한 단위 테스트 실행 파일은 기본적으로 응용 프로그램 코드와 연결되어 있습니다. 그러나 단위 테스트 코드는 테스트 번들에만 대상 멤버십이 있어야합니다. 응용 프로그램 코드는 응용 프로그램 번들에 대상 멤버 자격 만 있어야합니다. 런타임시, 유닛 테스트 대상 번들은 실행을 위해 애플리케이션 번들에 주입됩니다 .

스위프트 패키지 관리자 (SPM) 4 :

let testBundle = Bundle(for: type(of: self)) 
print("testBundle.bundlePath = \(testBundle.bundlePath) ")

참고 : 기본적으로 명령 행 swift testMyProjectPackageTests.xctest테스트 번들 을 작성합니다 . 그리고 테스트 번들 swift package generate-xcodeproj을 만듭니다 MyProjectTests.xctest. 이러한 다른 테스트 번들에는 다른 경로가 있습니다. 또한 다른 테스트 번들은 내부 디렉토리 구조 및 내용 차이 가있을 수 있습니다 .

두 경우 모두, .bundlePath그리고 .bundleURL현재 맥 OS에서 실행되는 테스트 번들의 경로를 반환합니다. 그러나 Bundle현재 Ubuntu Linux에는 구현되어 있지 않습니다.

또한, 명령 줄 swift buildswift test현재 자원을 복사하는 메커니즘을 제공하지 않습니다.

그러나 약간의 노력으로 macOS Xcode, macOS 명령 행 및 Ubuntu 명령 행 환경의 자원과 함께 Swift Package Manger를 사용하기위한 프로세스를 설정할 수 있습니다. 한 가지 예는 여기에서 찾을 수 있습니다 : 004.4'2 SW 개발자 스위프트 패키지 관리자 (SPM)을 자원 Qref으로

참조 : 사용 자원을 스위프트 패키지 관리자와 단위 테스트에

스위프트 패키지 관리자 (SPM) 4.2

Swift Package Manager PackageDescription 4.2 에는 로컬 종속성 지원이 도입되었습니다 .

로컬 종속성은 경로를 사용하여 직접 참조 할 수있는 디스크의 패키지입니다. 로컬 종속성은 루트 패키지에서만 허용되며 패키지 그래프에서 이름이 같은 모든 종속성을 재정의합니다.

참고 : SPM 4.2에서는 다음과 같은 것이 가능해야하지만 아직 테스트하지는 않았습니다.

// swift-tools-version:4.2
import PackageDescription

let package = Package(
    name: "MyPackageTestResources",
    dependencies: [
        .package(path: "../test-resources"),
    ],
    targets: [
        // ...
        .testTarget(
            name: "MyPackageTests",
            dependencies: ["MyPackage", "MyPackageTestResources"]
        ),
    ]
)

1
스위프트 4의 경우 번들을 사용할 수 있습니다 (for : type (of : self))
Rocket Garden

14

swift Swift 3에서는 구문 self.dynamicType이 더 이상 사용되지 않습니다. 대신 이것을 사용하십시오

let testBundle = Bundle(for: type(of: self))
let fooTxtPath = testBundle.path(forResource: "foo", ofType: "txt")

또는

let fooTxtURL = testBundle.url(forResource: "foo", withExtension: "txt")

4

자원이 테스트 대상에 추가되었는지 확인하십시오.

여기에 이미지 설명을 입력하십시오


2
테스트 번들에 자원을 추가하면 테스트 결과가 크게 무효화됩니다. 결국 리소스는 쉽게 테스트 대상에 있지만 앱 대상에는 없을 수 있으며 테스트는 모두 통과하지만 앱은 화염에 휩싸 일 것입니다.
dgatwood

1

프로젝트에 여러 대상이있는 경우 대상 멤버십 에서 사용 가능한 다른 대상간에 자원을 추가 해야하며 아래 그림에 표시된 3 단계로 다른 대상 간에 전환해야 할 수도 있습니다.

여기에 이미지 설명을 입력하십시오


0

이 일반 테스트 확인란이 설정되어 있는지 확인해야합니다 이 일반 테스트 확인란이 설정되었습니다

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