¿Por qué el código dentro de las pruebas de unidad no puede encontrar recursos de paquete?

Algún código que estoy probando unidad necesita cargar un archivo de recursos. Contiene la siguiente línea:

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

En la aplicación funciona muy bien, pero cuando se ejecuta mediante el marco de prueba de la unidad pathForResource: devuelve nil, lo que significa que no pudo encontrar foo.txt .

Me he asegurado de que foo.txt esté incluido en la fase de comstackción Copy Bundle Resources del objective de la prueba unitaria, así que ¿por qué no puede encontrar el archivo?

Cuando el arnés de prueba de la unidad ejecuta su código, el paquete de prueba de la unidad NO es el paquete principal.

Aunque está ejecutando pruebas, no su aplicación, su paquete de aplicaciones sigue siendo el paquete principal. (Presumiblemente, esto evita que el código que está probando busque el paquete incorrecto). Por lo tanto, si agrega un archivo de recursos al conjunto de pruebas de la unidad, no lo encontrará si busca en el paquete principal. Si reemplaza la línea anterior con:

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

Luego, su código buscará en el paquete en el que se encuentra la clase de prueba de su unidad, y todo estará bien.

Una implementación 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) 

Bundle proporciona formas de descubrir las rutas principales y de prueba para su configuració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 

En Xcode 6 | 7 | 8 | 9, una ruta de paquete de prueba de unidad estará en Developer/Xcode/DerivedData algo así como …

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

… que está separado de la ruta del paquete regular Developer/CoreSimulator/Devices (sin unidad de prueba) :

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

También tenga en cuenta que el ejecutable de la prueba unitaria está, por defecto, vinculado con el código de la aplicación. Sin embargo, el código de prueba de la unidad solo debe tener Membresía objective solo en el paquete de prueba. El código de la aplicación solo debe tener Membresía objective en el paquete de la aplicación. En tiempo de ejecución, el paquete de destino de la prueba unitaria se inyecta en el paquete de aplicaciones para su ejecución .

Swift Package Manager (SPM) 4:

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

Nota: De manera predeterminada, la swift test línea de comando creará un paquete de prueba MyProjectPackageTests.xctest . Y, el swift package generate-xcodeproj creará un paquete de prueba MyProjectTests.xctest . Estos diferentes paquetes de prueba tienen diferentes caminos . Además, los diferentes paquetes de prueba pueden tener algunas diferencias internas de estructura de directorios y contenido .

En cualquier caso, .bundlePath y .bundleURL devolverán la ruta del paquete de prueba que se está ejecutando actualmente en macOS. Sin embargo, Bundle no está implementado actualmente para Ubuntu Linux.

Además, la swift build línea de comando y la swift test no proporcionan actualmente un mecanismo para copiar recursos.

Sin embargo, con cierto esfuerzo, es posible configurar procesos para usar Swift Package Manger con recursos en macOS Xcode, línea de comandos macOS y entornos de línea de comandos de Ubuntu. Un ejemplo se puede encontrar aquí: 004.4’2 SW Dev Swift Package Manager (SPM) con recursos Qref

Consulte también: Usar recursos en pruebas unitarias con Swift Package Manager

Swift Package Manager (SPM) 4.2

Swift Package Manager PackageDescription 4.2 introduce soporte de dependencias locales .

Las dependencias locales son paquetes en disco que se pueden derivar directamente utilizando sus rutas. Las dependencias locales solo están permitidas en el paquete raíz y anulan todas las dependencias con el mismo nombre en el gráfico del paquete.

Nota: Espero, pero aún no lo he probado, que algo similar a lo siguiente sea posible con 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"] ), ] ) 

Con swift Swift 3 la syntax self.dynamicType ha quedado en desuso, use esto en su lugar

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

o

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

Confirme que el recurso se agrega al objective de prueba.

enter image description here

Si tiene un objective múltiple en su proyecto, entonces necesita agregar recursos entre diferentes objectives disponibles en la Membresía de destino y es posible que necesite cambiar entre diferentes destinos como 3 pasos que se muestran en la figura a continuación.

enter image description here