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


184

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

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

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

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

Ответы:


316

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

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

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

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


Не работает для меня. По-прежнему сборочный комплект, а не тестовый комплект.
Крис

1
@Chris В строке примера я предполагаю, selfчто это класс в основном комплекте, а не класс тестового примера. Замените [self class]на любой класс в вашем основном комплекте. Я отредактирую свой пример.
Бензадо

@benzado Пакет все тот же (сборка), что я считаю правильным. Потому что, когда я использую self или AppDelegate, оба находятся в основном комплекте. Когда я проверяю этапы сборки основной цели, в ней находятся оба файла. Но я хочу отличить основной пакет от тестового пакета во время выполнения. Код, где мне нужен комплект, находится в основном комплекте. У меня есть следующая проблема. Я загружаю файл PNG. Обычно этот файл не входит в основной комплект, поскольку пользователь загружает его с сервера. Но для теста я хочу использовать файл из тестового комплекта, не копируя его в основной комплект.
Крис

2
@Chris Я ошибся с предыдущим редактированием и снова отредактировал ответ. Во время тестирования пакет приложений по-прежнему остается основным. Если вы хотите загрузить файл ресурсов, который находится в комплекте модульного теста, вам необходимо использовать bundleForClass:класс в комплекте модульного теста. Вы должны получить путь к файлу в коде вашего модульного теста, а затем передать строку пути вместе с другим вашим кодом.
Бензадо

Это работает, но как я могу различить запуск-развертывание и тест-развертывание? Исходя из того, что это тест, мне нужен ресурс из тестового комплекта в классе основного комплекта. Если это обычный запуск, мне нужен ресурс из основного комплекта, а не тестовый комплект. Любая идея?
Крис

80

Реализация 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"]
        ),
    ]
)

1
Для Swift 4 вы также можете использовать Bundle (для: 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 шага, показанных на рисунке ниже.

введите описание изображения здесь


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.