ทำไมรหัสในการทดสอบหน่วยไม่สามารถหาทรัพยากรบันเดิลได้


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"];

จากนั้นโค้ดของคุณจะค้นหาบันเดิลที่คลาสทดสอบหน่วยของคุณอยู่และทุกอย่างจะเรียบร้อย


ใช้งานไม่ได้สำหรับฉัน ยังเป็นชุดบิลด์และไม่ใช่ชุดทดสอบ
Chris

1
@Chris ในบรรทัดตัวอย่างฉันสมมติว่าselfหมายถึงชั้นเรียนในกลุ่มหลักไม่ใช่ชั้นเรียนทดสอบ แทนที่[self class]ด้วยคลาสใด ๆ ในชุดข้อมูลหลักของคุณ ฉันจะแก้ไขตัวอย่างของฉัน
benzado

@benzado ชุดยังคงเหมือนเดิม (build) ซึ่งถูกต้องฉันคิดว่า เพราะเมื่อฉันใช้ตัวเองหรือ AppDelegate ทั้งคู่จะอยู่ในกลุ่มหลัก เมื่อฉันตรวจสอบ Build Phases ของเป้าหมายหลักทั้งสองไฟล์จะเข้า แต่สิ่งที่ฉันต้องการแตกต่างระหว่าง main และมัดทดสอบในเวลาทำงาน รหัสที่ฉันต้องการมัดอยู่ในกลุ่มหลัก ฉันมีปัญหาดังต่อไปนี้ ฉันกำลังโหลดไฟล์ png โดยปกติไฟล์นี้ไม่ได้อยู่ในชุดข้อมูลหลักเนื่องจากผู้ใช้ดาวน์โหลดไฟล์จากเซิร์ฟเวอร์ แต่สำหรับการทดสอบฉันต้องการใช้ไฟล์จากชุดทดสอบโดยไม่คัดลอกลงในชุดข้อมูลหลัก
Chris

2
@ Chris ฉันทำผิดพลาดกับการแก้ไขก่อนหน้าของฉันและแก้ไขคำตอบอีกครั้ง ในเวลาทดสอบชุดแอพยังคงเป็นชุดหลัก หากคุณต้องการโหลดไฟล์ทรัพยากรที่อยู่ในชุดทดสอบหน่วยคุณต้องใช้bundleForClass:กับชั้นเรียนในชุดทดสอบหน่วย คุณควรได้รับพา ธ ของไฟล์ในโค้ดทดสอบหน่วยของคุณจากนั้นส่งสตริงพา ธ ไปยังโค้ดอื่นของคุณ
benzado

ทำงานได้ แต่ฉันจะแยกความแตกต่างระหว่างการปรับใช้กับการทดสอบที่ใช้งานได้อย่างไร ตามความเป็นจริงหากเป็นการทดสอบฉันต้องการทรัพยากรจากชุดทดสอบในคลาสในชุดหลัก หากเป็น 'การเรียกใช้' ปกติฉันต้องการทรัพยากรจากชุดข้อมูลหลักไม่ใช่ชุดทดสอบ ความคิดใด ๆ
คริส

80

การติดตั้ง 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 มีวิธีการค้นพบเส้นทางหลักและเส้นทางทดสอบสำหรับการกำหนดค่าของคุณ:

@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/

นอกจากนี้โปรดทราบว่าการปฏิบัติการทดสอบหน่วยนั้นจะเชื่อมโยงกับรหัสแอปพลิเคชันตามค่าเริ่มต้น อย่างไรก็ตามรหัสทดสอบหน่วยควรมีเป้าหมายสมาชิกในชุดทดสอบเท่านั้น รหัสแอปพลิเคชันควรมีเป้าหมายเป็นสมาชิกในกลุ่มแอปพลิเคชันเท่านั้น ที่รันไทม์หน่วยกำเป้าหมายการทดสอบจะฉีดเข้าไปในกำแอพลิเคชันสำหรับการดำเนินการ

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 Linux

นอกจากนี้บรรทัดคำสั่งswift buildและswift testไม่ได้จัดเตรียมกลไกสำหรับการคัดลอกทรัพยากร

อย่างไรก็ตามด้วยความพยายามบางอย่างคุณสามารถตั้งค่ากระบวนการสำหรับการใช้ Swanger Package Manger กับทรัพยากรใน macOS Xcode, บรรทัดคำสั่ง macOS และสภาพแวดล้อมบรรทัดคำสั่งของ Ubuntu ตัวอย่างหนึ่งสามารถพบได้ที่นี่: 004.4'2 SW Dev Swift Package Manager (SPM) พร้อมทรัพยากร Qref

ดูเพิ่มเติม: ใช้ทรัพยากรในการทดสอบหน่วยด้วย Swift Package Manager

Swift Package Manager (SPM) 4.2

สวิฟท์แพคเกจผู้จัดการPackageDescription 4.2สนับสนุนการเปิดตัวของการอ้างอิงในท้องถิ่น

Local dependencies เป็นแพ็คเกจบนดิสก์ที่สามารถอ้างอิงโดยตรงโดยใช้พา ธ การอ้างอิงโลคัลได้รับอนุญาตเฉพาะในแพ็คเกจรูทและจะแทนที่การขึ้นต่อกันทั้งหมดที่มีชื่อเดียวกันในกราฟแพ็กเกจ

หมายเหตุ: ฉันคาดหวัง แต่ยังไม่ได้ทดสอบว่าควรมีสิ่งต่อไปนี้ใน 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 (สำหรับ: ประเภท (ของ: ตัวเอง))
Rocket Garden

14

ด้วย swift 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 ขั้นตอนดังแสดงในภาพด้านล่าง

ป้อนคำอธิบายรูปภาพที่นี่


0

ฉันต้องตรวจสอบให้แน่ใจว่าได้ทำเครื่องหมายในช่องการทดสอบทั่วไปแล้ว ช่องทำเครื่องหมายการทดสอบทั่วไปนี้ได้รับการตั้งค่าแล้ว

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.