Tại sao mã không thể bên trong các bài kiểm tra đơn vị tìm thấy tài nguyên bó?


182

Một số mã tôi đang kiểm tra đơn vị cần tải một tệp tài nguyên. Nó chứa dòng sau:

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

Trong ứng dụng, nó chỉ chạy tốt, nhưng khi được chạy bởi khung kiểm tra đơn vị pathForResource:sẽ trả về con số không, nghĩa là nó không thể định vị được foo.txt.

Tôi đã chắc chắn rằng nó foo.txtđược bao gồm trong giai đoạn xây dựng Tài nguyên Gói Sao chép của mục tiêu thử nghiệm đơn vị, vậy tại sao nó không thể tìm thấy tệp?

Câu trả lời:


315

Khi khai thác kiểm tra đơn vị chạy mã của bạn, gói kiểm tra đơn vị của bạn KHÔNG phải là gói chính.

Mặc dù bạn đang chạy thử nghiệm, không phải ứng dụng của bạn, gói ứng dụng của bạn vẫn là gói chính. (Có lẽ, điều này ngăn mã bạn đang kiểm tra tìm kiếm gói sai.) Vì vậy, nếu bạn thêm tệp tài nguyên vào gói kiểm tra đơn vị, bạn sẽ không tìm thấy mã nếu tìm kiếm gói chính. Nếu bạn thay thế dòng trên bằng:

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

Sau đó, mã của bạn sẽ tìm kiếm gói mà lớp kiểm tra đơn vị của bạn đang ở và mọi thứ sẽ ổn.


Không làm việc cho tôi. Vẫn là gói xây dựng và không phải là gói thử nghiệm.
Chris

1
@Chris Trong dòng mẫu tôi giả sử selfđề cập đến một lớp trong gói chính, không phải lớp trường hợp thử nghiệm. Thay thế [self class]bằng bất kỳ lớp nào trong gói chính của bạn. Tôi sẽ chỉnh sửa ví dụ của tôi.
benzado

@benzado Các gói vẫn giống nhau (xây dựng), đó là chính xác tôi nghĩ. Bởi vì khi tôi đang sử dụng self hoặc AppDelegate, cả hai đều nằm trong gói chính. Khi tôi kiểm tra các Pha xây dựng của mục tiêu chính, cả hai tệp đều nằm trong. Nhưng những gì tôi muốn khác nhau giữa gói chính và gói thử nghiệm trong thời gian chạy. Mã nơi tôi cần gói nằm trong gói chính. Tôi có một vấn đề sau đây. Tôi đang tải một tập tin png. Thông thường tệp này không nằm trong gói chính do người dùng tải xuống từ máy chủ. Nhưng đối với thử nghiệm tôi muốn sử dụng một tệp từ gói thử nghiệm mà không sao chép nó vào gói chính.
Chris

2
@Chris Tôi đã mắc lỗi với lần chỉnh sửa trước đó và chỉnh sửa lại câu trả lời. Tại thời điểm thử nghiệm, gói ứng dụng vẫn là gói chính. Nếu bạn muốn tải một tệp tài nguyên trong gói thử nghiệm đơn vị, bạn cần sử dụng bundleForClass:với một lớp trong gói thử nghiệm đơn vị. Bạn sẽ nhận được đường dẫn của tệp trong mã kiểm tra đơn vị của mình, sau đó chuyển chuỗi đường dẫn cùng với mã khác của bạn.
benzado

Điều này hoạt động nhưng làm thế nào tôi có thể phân biệt giữa triển khai chạy và triển khai thử nghiệm? Dựa trên thực tế nếu đó là một thử nghiệm, tôi cần một nguồn tài nguyên từ gói thử nghiệm trong một lớp trong gói chính. Nếu đó là một 'chạy' thông thường, tôi cần một tài nguyên từ gói chính chứ không phải gói thử nghiệm. Bất kỳ ý tưởng?
Chris

77

Một triển khai Swift:

Swift 2

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

Swift 3, Swift 4

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

Gói cung cấp các cách để khám phá các đường dẫn chính và thử nghiệm cho cấu hình của bạn:

@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

Trong Xcode 6 | 7 | 8 | 9, đường dẫn gói kiểm tra đơn vị sẽ ở Developer/Xcode/DerivedDatadạng ...

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

... tách biệt với Developer/CoreSimulator/Devices đường dẫn gói thông thường (không kiểm tra đơn vị) :

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

Cũng lưu ý rằng thực thi kiểm tra đơn vị, theo mặc định, được liên kết với mã ứng dụng. Tuy nhiên, mã kiểm tra đơn vị chỉ nên có Tư cách thành viên mục tiêu chỉ trong gói thử nghiệm. Mã ứng dụng chỉ nên có Tư cách thành viên trong gói ứng dụng. Khi chạy, gói mục tiêu thử nghiệm đơn vị được đưa vào gói ứng dụng để thực thi .

Trình quản lý gói Swift (SPM) 4:

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

Lưu ý: Theo mặc định, dòng lệnh swift testsẽ tạo một MyProjectPackageTests.xctestgói thử nghiệm. Và, swift package generate-xcodeprojsẽ tạo ra một MyProjectTests.xctestgói thử nghiệm. Những gói thử nghiệm khác nhauđường dẫn khác nhau . Ngoài ra, các gói thử nghiệm khác nhau có thể có một số khác biệt về cấu trúc thư mục và nội dung .

Trong cả hai trường hợp, .bundlePath.bundleURLsẽ trả về đường dẫn của gói thử nghiệm hiện đang được chạy trên macOS. Tuy nhiên, Bundlehiện không được triển khai cho Ubuntu Linux.

Ngoài ra, dòng lệnh swift buildswift testhiện không cung cấp cơ chế sao chép tài nguyên.

Tuy nhiên, với một số nỗ lực, có thể thiết lập các quy trình sử dụng Swift Gói Manger với các tài nguyên trong macOS Xcode, dòng lệnh macOS và môi trường dòng lệnh Ubuntu. Một ví dụ có thể được tìm thấy ở đây: 004.4'2 SW Dev Swift Gói Manager (SPM) với Tài nguyên Qref

Xem thêm: Sử dụng tài nguyên trong các bài kiểm tra đơn vị với Trình quản lý gói Swift

Trình quản lý gói Swift (SPM) 4.2

Trình quản lý gói Swift Gói mô tả 4.2 giới thiệu hỗ trợ các phụ thuộc cục bộ .

Phụ thuộc cục bộ là các gói trên đĩa có thể được gọi trực tiếp bằng đường dẫn của chúng. Các phụ thuộc cục bộ chỉ được phép trong gói gốc và chúng ghi đè tất cả các phụ thuộc có cùng tên trong biểu đồ gói.

Lưu ý: Tôi hy vọng, nhưng chưa được thử nghiệm, rằng điều gì đó như sau có thể xảy ra với 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
Đối với Swift 4 cũng vậy, bạn có thể sử dụng Gói (cho: loại (của: bản thân))
Rocket Garden

14

Với Swift 3, cú pháp self.dynamicTypeđã không được chấp nhận, thay vào đó hãy sử dụng cú pháp này

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

hoặc là

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

4

Xác nhận rằng tài nguyên được thêm vào mục tiêu thử nghiệm.

nhập mô tả hình ảnh ở đây


2
Thêm tài nguyên vào gói kiểm tra làm cho kết quả kiểm tra phần lớn không hợp lệ. Rốt cuộc, một tài nguyên có thể dễ dàng nằm trong mục tiêu thử nghiệm nhưng không phải trong mục tiêu ứng dụng và tất cả các thử nghiệm của bạn sẽ vượt qua, nhưng ứng dụng sẽ bùng cháy.
dgatwood

1

nếu bạn có nhiều mục tiêu trong dự án thì bạn cần thêm tài nguyên giữa các mục tiêu khác nhau có sẵn trong Tư cách thành viên mục tiêu và bạn có thể cần phải chuyển đổi giữa các mục tiêu khác nhau như 3 bước được hiển thị trong hình bên dưới

nhập mô tả hình ảnh ở đây


0

Tôi phải đảm bảo hộp kiểm Thử nghiệm chung này đã được đặt hộp kiểm tra tổng quát này đã được đặt

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.