NSURL к пути к файлу в тестовом комплекте с XCTest

Я пытаюсь написать приложение для iOS, используя TDD и новую платформу XCTest. Один из моих методов извлекает файл из Интернета (с учетом объекта NSURL) и сохраняет его в документах пользователя. Сигнатура метода похожа на:

- (void) fetchAndStoreImage:(NSURL *)imageUrl

Я пытаюсь написать тест для этого метода таким образом, чтобы он не потерпел неудачу, если нет подключения к Интернету. Мой подход (взят из предыдущего вопроса) заключается в вызове метода с использованием NSURL для изображения в локальной файловой системе.

Когда создается новый проект с включенными модульными тестами, в каталоге Tests появляется подкаталог с именем "Supporting Files". Я полагаю, что именно туда должны идти мои тестовые изображения. У меня вопрос, как я могу получить объект NSURL, который указывает на изображение в этом каталоге, так как я не хотел бы, чтобы тестовые изображения были связаны с приложением. Любая помощь приветствуется.

6 ответов

Решение

На самом деле, [NSBundle mainBundle] при запуске UnitTest это не путь вашего приложения, а /Developer/usr/bin, так что это не будет работать.

Способ получить ресурсы в модульном тесте здесь: OCUnit & NSBundle

Короче говоря, используйте:

[[NSBundle bundleForClass:[self class]] resourcePath]

или в вашем случае:

[[NSBundle bundleForClass:[self class]] resourceURL]

Свифт 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 fileURL = testBundle.url(forResource: "imageName", withExtension: "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

URL Xcode 6|7|8 будет в Developer/Xcode/DerivedData что-то вроде...

file:///Users/
  UserName/
    Library/
      Developer/
        Xcode/
          DerivedData/
            App-qwertyuiop.../
              Build/
                Products/
                  Debug-iphonesimulator/
                    AppTests.xctest/
                      imageName.png

... который отделен от Developer/CoreSimulator/Devices URL

file:///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.

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

Однако, приложив некоторые усилия, можно настроить процессы для использования менеджера пакетов Swift с ресурсами в средах macOS Xcode, macOS и Ubuntu. Один пример можно найти здесь: 004.4'2 SW Dev Swift Package Manager (SPM) с ресурсами 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"]
        ),
    ]
)

Чтобы добавить правильный ответ, вот пример того, как получить filePath для файла в ваших UnitTests/Supporting Files:

NSString *filePath = [[[NSBundle bundleForClass:[self class]] resourcePath] stringByAppendingPathComponent:@"YourFileName.json"];
XCTAssertNotNil(filePath);

Это, вероятно, будет полезно для кого-то.

  1. Вы можете ссылаться на файлы комплекта, как в любой цели.
  2. Проверьте, копируется ли файл в фазе сборки Copy Bundle Resources (вашей тестовой цели)
  3. Для доступа к локальному файлу:

    NSURL*imageUrl=[[NSBundle mainBundle]URLForResource:@"imageName" withExtension:@"png"];
    

Вы можете сделать асинхронный доступ и ждать ответа, используя: https://github.com/travisjeffery/TRVSMonitor

Если вы добавили: dataset1.json в контрольной цели (2):

NSString *p=[[NSBundle mainBundle] pathForResource:@"dataset1" ofType:@"json"];
NSLog(@"%@",p);

2013-10-29 15: 49: 30.547 PlayerSample [13771: 70b] WT (0): / Users / bpds / Библиотека / Поддержка приложений /iPhone Simulator/7.0/Applications/7F78780B-684A-40E0-AA35-A3B5D8AA9DBD/PlayerSample.app.app/dataset1.json

Swift версия на основе принятого ответа:

let url = URL(fileURLWithPath: Bundle(for: type(of: self)).path(forResource: "my", ofType: "json") ?? "TODO: Proper checking for file")

Проблема, с которой я столкнулся, заключалась в том, что код приложения пытался получить доступ к главному пакету для таких вещей, как bundleIdentifier, и, поскольку основной пакет не был моим проектом модульного тестирования, он возвращал ноль.

Хак, который работает как в Swift 3.0.1, так и в Objective-C, заключается в создании категории Objective-C в NSBundle и включении ее в ваш проект модульного тестирования. Вам не нужен соединительный заголовок или что-то еще. Эта категория будет загружена, и теперь, когда код вашего приложения запрашивает основной пакет, ваша категория вернет пакет модульного тестирования.

@interface NSBundle (MainBundle)

+(NSBundle *)mainBundle;

@end

@implementation NSBundle (Main)

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
+(NSBundle *)mainBundle
{
    return [NSBundle bundleForClass:[SomeUnitTest class]];
}
#pragma clang diagnostic pop

@end

Вот Swift-версия этого, Xcode 7, iOS 9 и т. Д.

let testBundle = NSBundle(forClass: self.dynamicType)
let path = testBundle.pathForResource("someImage", ofType: "jpg")
XCTAssertNotNil(path)

Примечание: someImage.jpg должен быть включен в ваш тестовый объект.

Swift 5

let testBundle = Bundle(for: self)

С помощью let testBundle = Bundle(for: type(of: self)), найденный в некоторых из приведенных выше ответов, не компилировался и вместо этого постоянно вызывал ошибку Segmentation fault: 11 для меня.

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