UnitTestで標準入力の差し替え

過去問やってると、(Command+Uが染みついてて)UnitTestつかいたくなるし、かといって入力をいちいち手作業でリテラルにするのはおっくうで。

そこで!

struct AtCoderRunner {
    typealias Print = (String) -> Void
    typealias Solver = (Print) -> Void
    let solver: Solver
    func run(input: String) -> String {
        var output: [String] = []
        var buffer: [Int8] = input.utf8CString + [0]
        let count = buffer.count
        buffer.withUnsafeMutableBytes {
            let file = fmemopen($0.baseAddress, count, "r")
            assert(file != nil)
            let backup = stdin
            stdin = file!
            solver() { output.append($0) }
            stdin = backup
        }
        return output.joined(separator: "\n")
    }
}

例えばこんな解答関数があったとすると

func TemplateSolver(print: (String) -> Void) {
    let N = Int(Swift.readLine()!)!
    print("\(N)")
}

こんな風にテストできます。

final class AtCoderTemplateTests: XCTestCase {
    var stdinCopy: UnsafeMutablePointer<FILE>?
    override func setUpWithError() throws {
        // 念のため
        stdinCopy = stdin
        // でもテストが並列で動くとおじゃん
    }
    override func tearDownWithError() throws {
        // 念のため
        stdin = stdinCopy!
        // でもテストが並列で動くとおじゃん
    }

    // Greenだよー
    func testTemplate() throws {
        XCTAssertEqual(
            AtCoderRunner(solver: TemplateSolver)
                .run(input:
                     """
                     0
                     """),
                     """
                     0
                     """)
    }
}

提出するときは、雑に説明するとこんな感じです。

TemplateSolver {
    print($0)
}

どうぞご自由におつかいください。

(いまさらですが、過去記事も同様です)

あ、Swiftです。