Dlaczego kod wewnątrz testów jednostkowych nie może znaleźć zasobów pakietu?


184

Część kodu, który testuję jednostkowo, wymaga załadowania pliku zasobów. Zawiera następujący wiersz:

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

W aplikacji działa dobrze, ale po uruchomieniu przez jednostkę do testów jednostkowych pathForResource:zwraca zero, co oznacza, że ​​nie można jej zlokalizować foo.txt.

Upewniłem się, że foo.txtjest to uwzględnione w fazie kompilacji zasobów pakietu kopii docelowego testu jednostkowego, więc dlaczego nie może znaleźć pliku?

Odpowiedzi:


316

Gdy testowy zespół przewodów uruchomi kod, pakiet testów jednostkowych NIE jest pakietem głównym.

Pomimo przeprowadzania testów, a nie aplikacji, pakiet aplikacji jest nadal głównym pakietem. (Prawdopodobnie uniemożliwia to testowanemu kodowi wyszukiwanie niewłaściwego pakietu.) Tak więc, jeśli dodasz plik zasobów do pakietu testu jednostkowego, nie znajdziesz go, jeśli przeszukasz główny pakiet. Jeśli zastąpisz powyższą linię:

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

Następnie kod przeszuka pakiet, w którym znajduje się klasa testów jednostkowych, i wszystko będzie dobrze.


Nie działa dla mnie. Nadal pakiet kompilacji, a nie pakiet testowy.
Chris

1
@Chris W linii próbnej zakładam, że selfodnosi się do klasy w pakiecie głównym, a nie do klasy przypadków testowych. Zastąp [self class]dowolną klasą w głównym pakiecie. Zmienię mój przykład.
benzado

@benzado Pakiet jest nadal taki sam (kompilacja), co wydaje mi się poprawne. Ponieważ gdy używam self lub AppDelegate, oba znajdują się w głównym pakiecie. Gdy sprawdzam fazy kompilacji głównego obiektu docelowego, oba pliki są w środku. Ale to, co chcę różnić w pakiecie głównym i testowym w czasie wykonywania. Kod, w którym potrzebuję pakietu, znajduje się w pakiecie głównym. Mam następujący problem. Ładuję plik png. Zwykle ten plik nie znajduje się w głównym pakiecie, ponieważ użytkownik pobiera go z serwera. Ale do testu chcę użyć pliku z pakietu testowego bez kopiowania go do pakietu głównego.
Chris

2
@Chris Popełniłem błąd podczas mojej poprzedniej edycji i ponownie zredagowałem odpowiedź. W czasie testu pakiet aplikacji jest nadal głównym pakietem. Jeśli chcesz załadować plik zasobów, który znajduje się w pakiecie testu jednostkowego, musisz użyć go bundleForClass:z klasą w pakiecie testu jednostkowego. Powinieneś uzyskać ścieżkę do pliku w kodzie testu jednostkowego, a następnie przekazać ciąg ścieżki do drugiego kodu.
benzado

To działa, ale jak mogę rozróżnić między uruchomieniem i uruchomieniem testowym? Na podstawie faktu, że jest to test, potrzebuję zasobu z pakietu testowego w klasie w pakiecie głównym. Jeśli jest to zwykłe „uruchamianie”, potrzebuję zasobów z pakietu głównego, a nie pakietu testowego. Dowolny pomysł?
Chris

80

Szybkie wdrożenie:

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)

Pakiet zapewnia sposoby odkrywania głównych i testowych ścieżek konfiguracji:

@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

W Xcode 6 | 7 | 8 | 9 ścieżka pakietu testów jednostkowych będzie Developer/Xcode/DerivedDatawyglądała jak ...

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

... który jest niezależny od Developer/CoreSimulator/Devices zwykłej ścieżki pakietu (nie w ramach testu jednostkowego) :

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

Należy również pamiętać, że plik wykonywalny testu jednostkowego jest domyślnie powiązany z kodem aplikacji. Jednak kod testu jednostkowego powinien mieć docelowe członkostwo tylko w pakiecie testowym. Kod aplikacji powinien mieć tylko docelowe członkostwo w pakiecie aplikacji. W czasie wykonywania pakiet docelowego testu jednostkowego jest wstrzykiwany do pakietu aplikacji w celu wykonania .

Swift Package Manager (SPM) 4:

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

Uwaga: Domyślnie wiersz poleceń swift testutworzy MyProjectPackageTests.xctestpakiet testowy. I swift package generate-xcodeprojstworzy MyProjectTests.xctestpakiet testowy. Te różne pakiety testowe mają różne ścieżki . Różne pakiety testowe mogą mieć także wewnętrzną strukturę katalogów i różnice w zawartości .

W obu przypadkach, .bundlePathi .bundleURLzwróci ścieżkę pakietu testowego obecnie uruchomionego na macOS. Jednak Bundlenie jest obecnie zaimplementowany dla Ubuntu Linux.

Ponadto wiersz polecenia swift buildi swift testobecnie nie zapewniają mechanizmu kopiowania zasobów.

Jednak przy pewnym wysiłku możliwe jest skonfigurowanie procesów do używania Swift Package Manger z zasobami w środowisku macOS Xcode, wierszu poleceń macOS i środowisku wiersza poleceń Ubuntu. Jeden przykład można znaleźć tutaj: 004.4'2 SW Dev Swift Package Manager (SPM) z zasobami Qref

Zobacz także: Używaj zasobów w testach jednostkowych za pomocą Swift Package Manager

Swift Package Manager (SPM) 4.2

Swift Package Manager PackageDescription 4.2 wprowadza obsługę lokalnych zależności .

Zależności lokalne to pakiety na dysku, do których można odwoływać się bezpośrednio przy użyciu ich ścieżek. Zależności lokalne są dozwolone tylko w pakiecie głównym i zastępują wszystkie zależności o tej samej nazwie na wykresie pakietu.

Uwaga: spodziewam się, ale jeszcze nie przetestowałem, że w SPM 4.2 powinno być możliwe coś takiego:

// 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
W przypadku Swift 4 możesz także użyć Pakietu (dla: type (of: self))
Rocket Garden

14

W swift Swift 3 składnia self.dynamicTypejest przestarzała, użyj jej zamiast tego

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

lub

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

4

Potwierdź, że zasób został dodany do celu testowego.

wprowadź opis zdjęcia tutaj


2
Dodanie zasobów do pakietu testowego powoduje, że wyniki testu są w dużej mierze nieprawidłowe. W końcu zasób może łatwo znajdować się w celu testowym, ale nie w celu aplikacji, a wszystkie testy przejdą pomyślnie, ale aplikacja wybuchnie.
dgatwood

1

jeśli masz wiele celów w swoim projekcie, musisz dodać zasoby między różnymi celami dostępnymi w członkostwie docelowym i może być konieczne przełączanie między różnymi celami, jak pokazano 3 kroki na poniższym rysunku

wprowadź opis zdjęcia tutaj


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.