Как предоставить внутренние методы пакета Swift, написанного на Objective-C, для тестов, написанных на Swift?

#ios #objective-c #swift #testing #swift-package-manager

#iOS #objective-c #swift #тестирование #swift-package-manager

Вопрос:

Я переношу устаревшую Objective-C библиотеку в пакет Swift, в котором написаны модульные тесты Swift . Ранее эта библиотека была написана с использованием стандартного подхода: Framework target / Xcode project, поэтому цель тестирования могла импортировать внутренние заголовки (используя заголовок bridging). Похоже, что Swift Package Manager не предлагает ничего подобного, поэтому тестирование внутренних компонентов выглядит проблематичным (без их публичного раскрытия).

Кстати: @testable import ... не работает для Objective-C кода.

Я также пытался обмануть его, выставив их с помощью:

 cSettings: [.headerSearchPath("../ObjcPackage/Source/Internal")]
  

Но это не работает.

Я использую swift-tools-version:5.3

Репозиторий, демонстрирующий проблему:

https://github.com/lukaszmargielewski/ObjcPackage

Некоторые фрагменты:

1. Определение пакета:

 // swift-tools-version:5.3
// Package.swift

import PackageDescription

let package = Package(
    name: "ObjcPackage",
    platforms: [
        .iOS(.v10)
    ],
    products: [
        .library(name: "ObjcPackage", targets: ["ObjcPackage"]),
    ],
    targets: [
        .target(
            name: "ObjcPackage",
            path: "ObjcPackage/Source",
            publicHeadersPath: "Public",
            cSettings: [
                .headerSearchPath("Public"),
                .headerSearchPath("Internal"),
            ]
        ),
        .testTarget(
            name: "ObjcPackageTests",
            dependencies: ["ObjcPackage"],
            path: "ObjcPackageTests",
            sources: ["Source"],
            cSettings: [
                .headerSearchPath("../ObjcPackage/Source/Internal")
            ]
        )
    ]
)
  

2. Общедоступный заголовок — ObjcPackage

 // ObjcPackage.h
#import <Foundation/Foundation.h>

@interface ObjcPackage : NSObject

- (nullable instancetype)initWithTitle:(nonnull NSString *)title NS_DESIGNATED_INITIALIZER;

- (nonnull instancetype)init NS_UNAVAILABLE;

@property (nonnull, readonly, nonatomic, copy) NSString *title;

@end
  

3. Внутренний заголовок — ObjcPackage

 // ObjcPackage Internal.h
#import <Foundation/Foundation.h>
#import "ObjcPackage.h"

/// Internal methods of ObjcPackage
@interface ObjcPackage ()

- (NSString *)generateInternalSecret;

@end
  

4. Файл реализации — ObjcPackage

 // ObjcPackage.m
#import "ObjcPackage.h"
#import "ObjcPackage Internal.h"

@interface ObjcPackage()

@property (nonatomic, readwrite, copy) NSString *title;

@end

@implementation ObjcPackage

- (instancetype)initWithTitle:(NSString *)title {
    self = [super init];
    if (self != nil) {
        self.title = title;
    }
    return self;
}

- (NSString *)generateInternalSecret {
    return [NSString stringWithFormat:@"%@_internal_secret", self.title];
}

@end
  

5. Тестовый пример (ObjcPackageTests)

 // ObjcPackageTests.swift
import Foundation
import XCTest
@testable import ObjcPackage

class ObjcPackageTests: XCTestCase {

    private lazy var objcPackage = ObjcPackage(title: "A")

    func testPublic() {
        XCTAssertEqual(objcPackage?.title, "A")
    }

    func testInternal() {
        // How to expose internal Objective-C header to the test package, 
        // without making it public?
        // !!! - COMPILATION ERROR: - !!!
        XCTAssertEqual(objcPackage.generateInternalSecret, "A_internal_secret")
    }
}
  

Комментарии:

1. Насколько я помню, objc на самом деле не имеет частного механизма. (Неоптимальным) решением было бы в любом случае поместить generateInternalSecret общедоступный заголовок и использовать _ префикс, чтобы указать, что это не должно вызываться вне пакета.