Почему код внутри модульных тестов не может найти ресурсы пакета?

Некоторый код, который я тестирую, должен загрузить файл ресурсов. Он содержит следующую строку:

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

В приложении все работает нормально, но при запуске в модуле модульного тестирования pathForResource: возвращает ноль, то есть не может найти foo.txt,

Я убедился, что foo.txt входит в фазу сборки Copy Bundle Resources цели модульного теста, так почему же он не может найти файл?

8 ответов

Решение

Когда ваш модуль запускает модульный тест, ваш пакет НЕ является основным.

Даже если вы запускаете тесты, а не ваше приложение, ваш пакет приложений по-прежнему остается основным. (Предположительно, это препятствует тому, чтобы код, который вы тестируете, искал неправильный пакет.) Таким образом, если вы добавите файл ресурсов в пакет модульного теста, вы не найдете его, если будете искать в основном пакете. Если вы замените вышеуказанную строку на:

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

Затем ваш код будет искать пакет, в котором находится ваш класс модульного тестирования, и все будет хорошо.

Реализация Swift:

Swift 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)

Bundle предоставляет способы обнаружения основных и тестовых путей для вашей конфигурации:

@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/

Также обратите внимание, что исполняемый файл модульного теста по умолчанию связан с кодом приложения. Однако код модульного теста должен иметь только целевое членство только в комплекте тестов. Код приложения должен иметь только целевое членство в комплекте приложений. Во время выполнения целевой пакет модульного теста вводится в комплект приложения для выполнения.

Swift Package Manager (SPM) 4:

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

Примечание: по умолчанию командная строка swift test создаст MyProjectPackageTests.xctest тестовый комплект. И, swift package generate-xcodeproj создаст MyProjectTests.xctest тестовый комплект. Эти разные тестовые пакеты имеют разные пути. Кроме того, разные тестовые пакеты могут иметь некоторые внутренние структуры каталогов и различия в содержимом.

В любом случае, .bundlePath а также .bundleURL вернет путь к тестовому пакету, который в данный момент выполняется на macOS. Тем не мение, Bundle в настоящее время не реализовано для Ubuntu Linux.

Также командная строка swift build а также swift test в настоящее время не предоставляют механизм для копирования ресурсов.

Однако, приложив некоторые усилия, можно настроить процессы для использования менеджера пакетов Swift с ресурсами в средах macOS Xcode, macOS и Ubuntu. Один пример можно найти здесь: 004.4'2 SW Dev Swift Package Manager (SPM) с ресурсами Qref

См. Также: Использование ресурсов в модульных тестах с Swift Package Manager.

Swift Package Manager (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"]
        ),
    ]
)

С Свифт Свифт 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")

Убедитесь, что ресурс добавлен к цели тестирования.

Если у вас есть несколько целей в вашем проекте, вам нужно добавить ресурсы между разными целями, доступными в целевом членстве, и вам может потребоваться переключиться между разными целями, как 3 шага, показанные на рисунке ниже

Я должен был убедиться, что этот флажок General Testing был установлен

Только для таких, как я, которые пропустили этот момент в исходном посте:

Убедитесь, что foo.md включен в фазу сборки Copy Bundle Resources цели модульного теста.

Есть код, который находит файл в:Как проверить, существует ли файл в каталоге «Документы» в Swift?

Я использовал его в тесте следующим образом, который проверяет, создан ли мой файл, и указывает его местоположение для дальнейшего тестирования.

              let fileFound = XCTestExpectation (description: "Checking for DB File Found")
    
    
    
    let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
    let url = NSURL(fileURLWithPath: path)
    if let pathComponent = url.appendingPathComponent("filename.ext") {
        let filePath = pathComponent.path
        let fileManager = FileManager.default
        if fileManager.fileExists(atPath: filePath) {
            fileFound.fulfill()
            print("DB FILE AVAILABLE")
        } else {
            print("DB FILE NOT AVAILABLE")
        }
    } else {
        print("DB FILE PATH NOT AVAILABLE")
    }
    
    wait(for: [fileFound], timeout: 5)

Это не проверка того, что файл создан в нужном месте, а только то, что он создан.

Другие вопросы по тегам