Является ли использование генериков действительными в подклассах XCTestCase?

У меня есть подкласс XCTestCase, который выглядит примерно так. Для краткости я удалил методы setup() и tearDown:

class ViewControllerTests <T : UIViewController>: XCTestCase {
    var viewController : T!

    final func loadControllerWithNibName(string:String) {
        viewController  = T(nibName: string, bundle: NSBundle(forClass: ViewControllerTests.self)) 
        if #available(iOS 9.0, *) {
            viewController.loadViewIfNeeded()
        } else {
            viewController.view.alpha = 1
        }
    }
}

И его подкласс, который выглядит примерно так:

class WelcomeViewControllerTests : ViewControllerTests<WelcomeViewController> {
    override func setUp() {
        super.setUp()
        self.loadControllerWithNibName("welcomeViewController")
        // Put setup code here. This method is called before the invocation of each test method in the class.
    }

    func testName() {
       let value =  self.viewController.firstNameTextField.text
        if value == "" {
            XCTFail()
        }
    }
}

Теоретически это должно работать как ожидалось - компилятор ни о чем не жалуется. Но это просто, когда я запускаю тестовые примеры, метод setup() даже не вызван. Но в нем говорится, что тесты прошли, когда метод testName() должен завершиться неудачно.

Является ли использование дженериков проблемой? Я легко могу думать о многих не общих подходах, но я бы очень хотел написать свои тестовые примеры. Является ли совместимость XCTest между Objective-C и Swift проблемой здесь?

Ответ 1

XCTestCase использует среду выполнения Objective-C для загрузки тестовых классов и поиска методов тестирования и т.д.

Родовые классы Swift не совместимы с Objective-C. См. https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithObjective-CAPIs.html#//apple_ref/doc/uid/TP40014216-CH4-ID53:

Когда вы создаете класс Swift, который спускается из класса Objective-C, класс и его элементы-свойства, методы, индексы и инициализаторы, которые совместимы с Objective-C, автоматически доступны из Objective-C. Это исключает возможности Swift, такие как перечисленные здесь:

  • Дженерики

...

Ergo ваш общий подкласс XCTestCase не может использоваться XCTest.

Ответ 2

Ну, на самом деле это полностью выполнимо. Вам просто нужно создать класс, который будет своего рода тестовым бегуном. Например:

class TestRunner: XCTestCase {
    override class func initialize() {
        super.initialize()
        let case = XCTestSuite(forTestCaseClass: ViewControllerTests<WelcomeViewController>.self)
        case.runTest()
    }
}

Таким образом вы можете выполнить все свои общие тесты.

Ответ 3

У меня получилось работать хакерским способом с протоколами и связанными типами:

import XCTest

// If your xCode doesn't compile on this line, download the lastest toolchain as of 30 november 2018
// or change to where Self: XCTestCase, and do it also in the protocol extension.
protocol MyGenericTestProtocol: XCTestCase {
    associatedtype SomeType

    func testCallRunAllTests()
}

extension MyGenericTestProtocol {
// You are free to use the associated type here in any way you want.
// You can constraint the associated type to be of a specific kind
// and than you can run your generic tests in this protocol extension!
    func startAllTests() {
        for _ in 0...100 {
            testPrintType()
        }
    }

    func testPrintType() {
        print(SomeType.self)
    }
}

class SomeGenericTestInt: XCTestCase, MyGenericTestProtocol {
    typealias SomeType = Int

    func testCallRunAllTests() {
        startAllTests()
    }
}

class SomeGenericTestString: XCTestCase, MyGenericTestProtocol {
    typealias SomeType = String

    func testCallRunAllTests() {
        startAllTests()
    }
}