Чому код всередині тесту одиниць не може знайти ресурси зв’язку?


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 Пакет все одно той самий (build), що правильно вважаю. Тому що, коли я використовую self або AppDelegate, обидва розміщені в основному пакеті. Коли я перевіряю фази збірки основної цілі, знаходяться обидва файли. Але те, що я хочу відрізняти між основним та тестовим пакетом під час виконання. Код, де мені потрібен пакет, знаходиться в основному пакеті. У мене є така проблема. Я завантажую png-файл. Зазвичай цей файл не знаходиться в основному пакеті, оскільки користувач завантажує його з сервера. Але для тесту я хочу використовувати файл із тестового пакета, не копіюючи його в основний пакет.
Кріс

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

Це працює, але як я можу розрізняти запуск-тест та тестовий розгортання? Виходячи з того, що це тест, мені потрібен ресурс з тестової групи в класі в основному пакеті. Якщо це регулярний "запуск", мені потрібен ресурс основного пакета, а не тестовий пакет. Будь-яка ідея?
Кріс

80

Швидка реалізація:

Швидкий 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)

Комплект надає способи виявлення основних та тестових шляхів для вашої конфігурації:

@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 testстворить MyProjectPackageTests.xctestтестовий пакет. І, swift package generate-xcodeprojбуде створити MyProjectTests.xctestтестовий пакет. Ці різні тестові групи мають різні шляхи . Крім того, різні тестові групи можуть мати певну внутрішню структуру каталогів та різницю вмісту .

В будь-якому випадку, .bundlePathі .bundleURLповерне шлях тестового набору, який зараз виконується на macOS. Однак Bundleнаразі не використовується для Ubuntu Linux.

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

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

Дивіться також: Використовуйте ресурси в одиничних тестах з 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 (for: type (of: self))
Rocket Garden

14

Зі швидким 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.