Swift の Mirror API を触って、動作を XCTest で確認した

今日やってた勉強内容。

github.com

動かし方

$ git clone https://github.com/sinsoku/study.git
$ cd study/swift
$ bin/run swift build -C PlayGround
$ bin/run swift test -C PlayGround

ソースコード

// PlayGround/Sources/PlayGround/SimpleClass.swift
class BaseClass {
  let number: Int = 0
  let string: String? = nil
}

class SimpleClass: BaseClass {
}

テストコード

// PlayGround/Tests/PlayGround/SimpleClassTests.swift
import XCTest
@testable import PlayGround

class SimpleClassTests: XCTestCase {
    func testSimpleClassBehavior() {
        let obj = SimpleClass()
        // refs: https://stackoverflow.com/questions/25725033/swift-too-smart-checking-an-objects-type-when-testing-with-xctest
        XCTAssertTrue((obj as Any) is SimpleClass)
        XCTAssertTrue((obj as Any) is BaseClass)
    }

    // refs: https://github.com/apple/swift/blob/swift-DEVELOPMENT-SNAPSHOT-2016-06-06-a/stdlib/public/core/Mirror.swift#L137
    func testReflectionBehavior() {
        let playGround = Mirror(reflecting: PlayGround())
        XCTAssertEqual(playGround.description, "Mirror for PlayGround")
        XCTAssertEqual(playGround.displayStyle, Mirror.DisplayStyle.struct)
        XCTAssertTrue(playGround.subjectType == PlayGround.self)

        let base = Mirror(reflecting: BaseClass())
        XCTAssertEqual(base.description, "Mirror for BaseClass")
        XCTAssertEqual(base.displayStyle, Mirror.DisplayStyle.class)
        XCTAssertTrue(base.subjectType == BaseClass.self)

        let simple = Mirror(reflecting: SimpleClass())
        XCTAssertEqual(simple.description, "Mirror for SimpleClass")
        XCTAssertEqual(simple.displayStyle, Mirror.DisplayStyle.class)
        XCTAssertTrue(simple.subjectType == SimpleClass.self)

        let parent = simple.superclassMirror
        XCTAssertEqual(parent?.description, "Mirror for BaseClass")
        XCTAssertEqual(parent?.displayStyle, Mirror.DisplayStyle.class)
        XCTAssertTrue(parent?.subjectType == BaseClass.self)

        let defNumber = base.children.filter { $0.label == "number" }[0]
        XCTAssertEqual((defNumber.value as! Int), 0)
        let defString = base.children.filter { $0.label == "string" }[0]
        // FIXME: the assertion is failed. (XCTAssertNil failed: "nil")
        // XCTAssertNil(defString.value)
        XCTAssertEqual(String(defString.value), "nil")
    }

    static var allTests : [(String, (SimpleClassTests) -> () throws -> Void)] {
        return [
            ("testSimpleClassBehavior", testSimpleClassBehavior),
            ("testReflectionBehavior", testReflectionBehavior),
        ]
    }
}

FIXME について

value の型が Optional<String> にはなっているっぽいのに、なぜか nil にはならずにテストが通らなかった。 結局、よく分からないまま・・・。

XCode でも調べてみた

class Foo {
    let number: Int? = nil
}

let m = Mirror(reflecting: Foo())
let defNumber = m.children.filter { $0.label == "number" }[0]
defNumber.value == nil
// Playground execution failed: MyPlayground.playground:1:17: error: value of type 'Any' (aka 'protocol<>') can never be nil, comparison isn't allowed
// defNumber.value == nil
//~~~~~~~~~~~~~~~ ^

エラーでるけど、結局 value の返す値が何なのかが不明・・・。

参考にしたページ