¿Por qué el código dentro de las pruebas unitarias no encuentra recursos de paquete?

184

Algunos códigos que estoy probando en unidades necesitan cargar un archivo de recursos. Contiene la siguiente línea:

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

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

Me he asegurado de que foo.txtesté incluido en la fase de compilación Copiar recursos de paquete del objetivo de prueba de unidad, entonces, ¿por qué no puede encontrar el archivo?

benzado
fuente

Respuestas:

316

Cuando el arnés de prueba de la unidad ejecuta su código, su 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 paquete de prueba unitaria, no lo encontrará si busca 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á el paquete en el que se encuentra su clase de prueba unitaria, y todo estará bien.

benzado
fuente
No funciona para mi Sigue siendo el paquete de compilación y no el paquete de prueba.
Chris
1
@Chris En la línea de muestra que supongo se selfrefiere a una clase en el paquete principal, no a la clase de caso de prueba. Reemplace [self class]con cualquier clase en su paquete principal. Editaré mi ejemplo.
benzado
@benzado El paquete sigue siendo el mismo (compilación), lo cual es correcto, creo. Porque cuando estoy usando self o AppDelegate, ambos se encuentran en el paquete principal. Cuando verifico las Fases de compilación del objetivo principal, ambos archivos están dentro. Pero quiero diferenciar entre el paquete principal y el paquete de prueba en tiempo de ejecución. El código donde necesito el paquete está en el paquete principal. Tengo el siguiente problema. Estoy cargando un archivo png. Normalmente este archivo no está en el paquete principal debido a que el usuario lo descarga desde un servidor. Pero para una prueba, quiero usar un archivo del paquete de prueba sin copiarlo en el paquete principal.
Chris
2
@ Chris Cometí un error con mi edición anterior y edité la respuesta nuevamente. En el momento de la prueba, el paquete de aplicaciones sigue siendo el paquete principal. Si desea cargar un archivo de recursos que está en el paquete de prueba unitaria, debe usarlo bundleForClass:con una clase en el paquete de prueba unitaria. Debería obtener la ruta del archivo en el código de prueba de su unidad, luego pasar la cadena de ruta a su otro código.
benzado
Esto funciona, pero ¿cómo puedo distinguir entre una implementación de ejecución y una implementación de prueba? Basado en el hecho de que si es una prueba, necesito un recurso del paquete de prueba en una clase en el paquete principal. Si se trata de una "ejecución" regular, necesito un recurso del paquete principal y no del paquete de prueba. ¿Alguna idea?
Chris
80

Una implementación rápida:

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 unitaria estará en Developer/Xcode/DerivedDataalgo así como ...

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

... que es independiente de la Developer/CoreSimulator/Devices ruta del paquete normal (sin prueba unitaria) :

/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 la Membresía de destino solo en el paquete de prueba. El código de la aplicación solo debe tener Membresía de destino en el paquete de la aplicación. En tiempo de ejecución, el paquete de destino de prueba de unidad se inyecta en el paquete de aplicación para su ejecución .

Swift Package Manager (SPM) 4:

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

Nota: Por defecto, la línea de comando swift testcreará un MyProjectPackageTests.xctestpaquete de prueba. Y, swift package generate-xcodeprojcreará un MyProjectTests.xctestpaquete de prueba. Estos diferentes paquetes de prueba tienen diferentes caminos . Además, los diferentes paquetes de prueba pueden tener alguna estructura interna de directorio y diferencias de contenido .

En cualquier caso, .bundlePathy .bundleURLdevolverá la ruta del paquete de prueba que se está ejecutando actualmente en macOS. Sin embargo, Bundleactualmente no está implementado para Ubuntu Linux.

Además, la línea de comandos swift buildy swift testactualmente no proporcionan un mecanismo para copiar recursos.

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

Ver 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 el disco que se pueden referir directamente usando 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 he probado, que algo como 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"]
        ),
    ]
)
revs l --mar l
fuente
1
Para Swift 4 también, puede usar Bundle (para: tipo (de: self))
Rocket Garden
14

Con Swift Swift 3 la sintaxis self.dynamicTypeha 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")
MarkHim
fuente
4

Confirme que el recurso se agrega al objetivo de prueba.

ingrese la descripción de la imagen aquí

mishimay
fuente
2
Agregar recursos al paquete de prueba hace que los resultados de la prueba sean en gran medida inválidos. Después de todo, un recurso podría estar fácilmente en el objetivo de prueba pero no en el objetivo de la aplicación, y todas sus pruebas pasarían, pero la aplicación estallaría en llamas.
dgatwood
1

Si tiene un objetivo múltiple en su proyecto, entonces necesita agregar recursos entre diferentes objetivos disponibles en la Membresía de Target y puede necesitar cambiar entre diferentes Target como 3 pasos que se muestran en la figura a continuación

ingrese la descripción de la imagen aquí

Sultan Ali
fuente
0

Tenía que asegurarme de que esta casilla de verificación Pruebas generales estuviera establecida esta casilla de verificación Pruebas generales se estableció

dibujó..
fuente