Upgrade to Pro — share decks privately, control downloads, hide ads and more …

テストデータの自動生成方法の別解

Tatsuya Tanaka
December 18, 2018

 テストデータの自動生成方法の別解

Tatsuya Tanaka

December 18, 2018
Tweet

More Decks by Tatsuya Tanaka

Other Decks in Technology

Transcript

  1. ςετσʔλࣗಈੜ੒ํ๏ͷผղ
    ాதୡ໵ (@tattn)
    #potatotips 57

    View Slide

  2. ాத ୡ໵ / ͨͳͨͭ (@tattn)
    • Yahoo!৐׵Ҋ಺
    • iOSΞϓϦΤϯδχΞ
    @tattn
    @tanakasan2525
    @tattn

    View Slide

  3. Ϣχοτςετ༻ͷ

    ςετσʔλ

    View Slide

  4. APIϨεϙϯεͷςετσʔλ͕΄͍͠
    ϢχοτςετͰ MockAPIClient Λ࡞Δͱ͖ʹ
    σʔλΛ࡞Δͷ͕໘౗...

    View Slide

  5. ViewModelͷςετ
    self.viewModel = ViewModel(
    trainStatusInfoAPI: self.trainStatusInfoAPI,
    registrationAPI: self.registrationAPI,
    congestionAPI: self.congestionAPI
    )
    self.trainStatusInfoAPI.mock = TestData(...)
    self.registrationAPI.mock = TestData2(...)
    self.congestionAPI.mock = TestData3(...)
    self.viewModel.request()
    expect(self.viewModel.cellData.value).toEventually(equal(...))
    ←͕͜͜໘౗

    View Slide

  6. ςετσʔλͷॳظԽ
    TrainStatusInfo(statuses: [
    Status(railway:
    Railway(information:
    Information(
    code: 0,
    name: "",
    trainStatuses: [
    TrainStatus(
    impactRange: "impactRange",
    message: "message",
    statusTitle: "statusTitle",
    statusCode: "statusCode",
    updatedAt: "updatedAt",
    …,
    )
    ],
    displayName: "displayName",
    webURL: "webURL",
    fooCode: 0,
    barCode: 1,
    bazName: "bazName",
    quxName: "quxName",
    quuxID: 2,
    corge: “corge",
    grault: “grault",
    …,
    )
    )
    )], …: Hoge(…))
    ͜Μͳͷॻ͖ͨ͘ͳ͍

    ਓྨͷ࢓ࣄ͡Όͳ͍
    (´ɾωɾʆ)

    View Slide

  7. APIͷϨεϙϯεΛςετσʔλʹࠩ͠ସ͑Δํ๏
    • protocolͰࠩ͠ସ͑ͨMockAPIΫϥΠΞϯτʹ

    σʔλΛࠩ͠ࠐΉ
    • URLSessionΛϋοΫͯ͠ɺϨεϙϯεΛ

    ༻ҙͨ͠JSONϑΝΠϧʹࠩ͠ସ͑Δ

    View Slide

  8. APIͷϨεϙϯεΛςετσʔλʹࠩ͠ସ͑Δํ๏
    • protocolͰࠩ͠ସ͑ͨMockAPIΫϥΠΞϯτʹ

    σʔλΛࠩ͠ࠐΉ
    • URLSessionΛϋοΫͯ͠ɺϨεϙϯεΛ

    ༻ҙͨ͠JSONϑΝΠϧʹࠩ͠ସ͑Δ

    View Slide

  9. ཚ਺ͰॳظԽͯ͠

    ςετσʔλʹ͢Δ

    View Slide

  10. ཚ਺ͰॳظԽ͢ΔͨΊͷΞϓϩʔν
    • ✖ ίʔυੜ੒: ΞϦ͚ͩͲPure SwiftͰ΍Γ͍ͨ
    • ϦϑϨΫγϣϯ



    View Slide

  11. ཚ਺ͰॳظԽ͢ΔͨΊͷΞϓϩʔν
    • ✖ ίʔυੜ੒: ΞϦ͚ͩͲPure SwiftͰ΍Γ͍ͨ
    • ϦϑϨΫγϣϯ
    • ✖ Mirror: ࢖͏લʹΠϯελϯεԽ͕ඞཁ (㱻)

    View Slide

  12. ཚ਺ͰॳظԽ͢ΔͨΊͷΞϓϩʔν
    • ✖ ίʔυੜ੒: ΞϦ͚ͩͲPure SwiftͰ΍Γ͍ͨ
    • ϦϑϨΫγϣϯ
    • ✖ Mirror: ࢖͏લʹΠϯελϯεԽ͕ඞཁ (㱻)
    • Codable: ΠϯελϯεԽͳ͠Ͱ

    ܕͷߏ଄ΛಘΒΕΔ

    View Slide

  13. https://github.com/tattn/
    Randomizable

    View Slide

  14. Randomizable
    struct A: Decodable, Randomizable {
    let string: String
    let int: Int
    let child: Child
    struct Child: Decodable, Randomizable {
    let url: URL
    let array: [Double]
    }
    }
    print(A.randomValue())
    // A(string: "lgygox",
    int: Optional(7366465203208943133),
    child: Child(url: https://laoygisn.com,
    array: [4.67308519, 1.8293057, -6.64297, 7.797]))
    ↓ཚ਺ͰॳظԽͯ͘͠ΕΔ

    View Slide

  15. Randomizable
    struct ParentType: Decodable, Randomizable {
    let customType: CustomType
    struct CustomType: Decodable, Randomizable {
    let animal: String
    static func randomValue() -> A.Child.ChildChild {
    return .init(animal: ["", "", ""].randomElement()!)
    }
    }
    }
    ↓ϥϯμϜͷϧʔϧΛΧελϚΠζͰ͖Δ
    // ParentType(customType: CustomType(animal: ""))
    print(ParentType.randomValue())

    View Slide

  16. ࣮૷

    View Slide

  17. Randomizable
    public protocol Randomizable {
    static func randomValue() -> Self
    }
    public extension Randomizable where Self: Decodable {
    public static func randomValue() -> Self {
    let randomDecoder = RandomDecoder()
    return try! randomDecoder.decode(Self.self)
    }
    }
    Decodableʹconform͍ͯ͠Δ৔߹͸ϥϯμϜͷ࣮૷Λෆཁʹ

    View Slide

  18. Randomizableͷඪ४αϙʔτ
    extension Bool: Randomizable {}
    extension Int: Randomizable {}
    extension Int8: Randomizable {}
    extension Int16: Randomizable {}
    extension Int32: Randomizable {}
    extension Int64: Randomizable {}
    extension UInt: Randomizable {}
    extension UInt8: Randomizable {}
    extension UInt16: Randomizable {}
    extension UInt32: Randomizable {}
    extension UInt64: Randomizable {}
    extension Float: Randomizable {}
    extension Double: Randomizable {}
    extension String: Randomizable {}
    extension Optional: Randomizable where Wrapped: Randomizable {
    public static func randomValue() -> Optional {
    return Bool.random() ? nil : Wrapped.randomValue()
    }
    }
    extension URL: Randomizable {
    public static func randomValue() -> URL {
    return URL(string: "https://\(String.defaultRandom()).com")!
    }
    }

    View Slide

  19. RandomDecoderͷ࣮૷
    import Foundation
    open class RandomDecoder: Decoder {
    open var codingPath: [CodingKey]
    open var userInfo: [CodingUserInfoKey: Any] = [:]
    public init(codingPath: [CodingKey] = []) {
    self.codingPath = codingPath
    }
    open func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer {
    return KeyedDecodingContainer(KeyedContainer(decoder: self, codingPath: []))
    }
    open func unkeyedContainer() throws -> UnkeyedDecodingContainer {
    return UnkeyedContanier(decoder: self)
    }
    open func singleValueContainer() throws -> SingleValueDecodingContainer {
    return SingleValueContanier(decoder: self)
    }
    func random() throws -> T {
    if let randomType = T.self as? Randomizable.Type {
    return randomType.randomValue() as! T
    } else {
    return try T(from: self)
    }
    }
    }
    extension RandomDecoder {
    open func decode(_ type: T.Type) throws -> T {
    return try T(from: self)
    }
    }
    extension RandomDecoder {
    private class KeyedContainer: KeyedDecodingContainerProtocol {
    private var decoder: RandomDecoder
    private(set) var codingPath: [CodingKey]
    init(decoder: RandomDecoder, codingPath: [CodingKey]) {
    self.decoder = decoder
    self.codingPath = codingPath
    }
    var allKeys: [Key] { return [] }
    func contains(_ key: Key) -> Bool { return true }
    func decodeNil(forKey key: Key) throws -> Bool { return Bool.random() }
    func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { return .random() }
    func decode(_ type: Int.Type, forKey key: Key) throws -> Int { return .defaultRamdom() }
    func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { return .defaultRamdom() }
    func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { return .defaultRamdom() }
    func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { return .defaultRamdom() }
    func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { return .defaultRamdom() }
    func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { return .defaultRamdom() }
    func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { return .defaultRamdom() }
    func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { return .defaultRamdom() }
    func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { return .defaultRamdom() }
    func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { return .defaultRamdom() }
    func decode(_ type: Float.Type, forKey key: Key) throws -> Float { return .defaultRamdom() }
    func decode(_ type: Double.Type, forKey key: Key) throws -> Double { return .defaultRamdom() }
    func decode(_ type: String.Type, forKey key: Key) throws -> String { return .defaultRamdom() }
    func decode(_ type: T.Type, forKey key: Key) throws -> T {
    decoder.codingPath.append(key)
    defer { decoder.codingPath.removeLast() }
    return try decoder.random()
    }
    func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey {
    decoder.codingPath.append(key)
    defer { decoder.codingPath.removeLast() }
    return KeyedDecodingContainer(KeyedContainer(decoder: decoder, codingPath: []))
    }
    func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
    decoder.codingPath.append(key)
    defer { decoder.codingPath.removeLast() }
    return UnkeyedContanier(decoder: decoder)
    }
    func _superDecoder(forKey key: CodingKey = AnyCodingKey.super) throws -> Decoder {
    decoder.codingPath.append(key)
    defer { decoder.codingPath.removeLast() }
    return RandomDecoder()
    }
    func superDecoder() throws -> Decoder {
    return try _superDecoder()
    }
    func superDecoder(forKey key: Key) throws -> Decoder {
    return try _superDecoder(forKey: key)
    }
    }
    private class UnkeyedContanier: UnkeyedDecodingContainer {
    private var decoder: RandomDecoder
    private(set) var codingPath: [CodingKey]
    private(set) var count: Int?
    var isAtEnd: Bool { return currentIndex >= count! }
    private(set) var currentIndex: Int = 0
    private var currentCodingPath: [CodingKey] { return decoder.codingPath + [AnyCodingKey(index: currentIndex)] }
    init(decoder: RandomDecoder) {
    self.decoder = decoder
    self.codingPath = decoder.codingPath
    count = Int.random(in: 2...4)
    }
    func ramdom(_ type: T.Type) throws -> T {
    decoder.codingPath.append(AnyCodingKey(index: currentIndex))
    defer {
    decoder.codingPath.removeLast()
    currentIndex += 1
    }
    return T.defaultRamdom()
    }
    func decodeNil() throws -> Bool { return false }
    func decode(_ type: Bool.Type) throws -> Bool { return try ramdom(type) }
    func decode(_ type: Int.Type) throws -> Int { return try ramdom(type) }
    func decode(_ type: Int8.Type) throws -> Int8 { return try ramdom(type) }
    func decode(_ type: Int16.Type) throws -> Int16 { return try ramdom(type) }
    func decode(_ type: Int32.Type) throws -> Int32 { return try ramdom(type) }
    func decode(_ type: Int64.Type) throws -> Int64 { return try ramdom(type) }
    func decode(_ type: UInt.Type) throws -> UInt { return try ramdom(type) }
    func decode(_ type: UInt8.Type) throws -> UInt8 { return try ramdom(type) }
    func decode(_ type: UInt16.Type) throws -> UInt16 { return try ramdom(type) }
    func decode(_ type: UInt32.Type) throws -> UInt32 { return try ramdom(type) }
    func decode(_ type: UInt64.Type) throws -> UInt64 { return try ramdom(type) }
    func decode(_ type: Float.Type) throws -> Float { return try ramdom(type) }
    func decode(_ type: Double.Type) throws -> Double { return try ramdom(type) }
    func decode(_ type: String.Type) throws -> String { return try ramdom(type) }
    func decode(_ type: T.Type) throws -> T {
    decoder.codingPath.append(AnyCodingKey(index: currentIndex))
    defer {
    decoder.codingPath.removeLast()
    currentIndex += 1
    }
    return try decoder.random()
    }
    func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer {
    decoder.codingPath.append(AnyCodingKey(index: currentIndex))
    defer {
    decoder.codingPath.removeLast()
    currentIndex += 1
    }
    return KeyedDecodingContainer(KeyedContainer(decoder: decoder, codingPath: []))
    }
    func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer {
    decoder.codingPath.append(AnyCodingKey(index: currentIndex))
    defer {
    decoder.codingPath.removeLast()
    currentIndex += 1
    }
    return UnkeyedContanier(decoder: decoder)
    }
    func superDecoder() throws -> Decoder {
    decoder.codingPath.append(AnyCodingKey(index: currentIndex))
    defer {
    decoder.codingPath.removeLast()
    currentIndex += 1
    }
    return RandomDecoder(codingPath: decoder.codingPath)
    }
    }
    private class SingleValueContanier: SingleValueDecodingContainer {
    private var decoder: RandomDecoder
    var codingPath: [CodingKey] { return decoder.codingPath }
    init(decoder: RandomDecoder) {
    self.decoder = decoder
    }
    func decodeNil() -> Bool { return true }
    func decode(_ type: Bool.Type) throws -> Bool { return .defaultRamdom() }
    func decode(_ type: Int.Type) throws -> Int { return .defaultRamdom() }
    func decode(_ type: Int8.Type) throws -> Int8 { return .defaultRamdom() }
    func decode(_ type: Int16.Type) throws -> Int16 { return .defaultRamdom() }
    func decode(_ type: Int32.Type) throws -> Int32 { return .defaultRamdom() }
    func decode(_ type: Int64.Type) throws -> Int64 { return .defaultRamdom() }
    func decode(_ type: UInt.Type) throws -> UInt { return .defaultRamdom() }
    func decode(_ type: UInt8.Type) throws -> UInt8 { return .defaultRamdom() }
    func decode(_ type: UInt16.Type) throws -> UInt16 { return .defaultRamdom() }
    func decode(_ type: UInt32.Type) throws -> UInt32 { return .defaultRamdom() }
    func decode(_ type: UInt64.Type) throws -> UInt64 { return .defaultRamdom() }
    func decode(_ type: Float.Type) throws -> Float { return .defaultRamdom() }
    func decode(_ type: Double.Type) throws -> Double { return .defaultRamdom() }
    func decode(_ type: String.Type) throws -> String { return .defaultRamdom() }
    func decode(_ type: T.Type) throws -> T { return try decoder.random() }
    }
    }

    View Slide

  20. RandomDecoderͷ࣮૷ͷϐοΫΞοϓ
    extension RandomDecoder {
    private class KeyedContainer: KeyedDecodingContainerProtocol {
    private var decoder: RandomDecoder
    private(set) var codingPath: [CodingKey]
    init(decoder: RandomDecoder, codingPath: [CodingKey]) {
    self.decoder = decoder
    self.codingPath = codingPath
    }
    func decodeNil(forKey key: Key) throws -> Bool { return .random() }
    func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { return .random() }
    func decode(_ type: Int.Type, forKey key: Key) throws -> Int { return .defaultRandom() }
    func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { return .defaultRandom() }
    func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { return .defaultRandom() }
    func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { return .defaultRandom() }
    func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { return .defaultRandom() }
    func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { return .defaultRandom() }
    func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { return .defaultRandom() }
    func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { return .defaultRandom() }
    func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { return .defaultRandom() }
    func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { return .defaultRandom() }
    func decode(_ type: Float.Type, forKey key: Key) throws -> Float { return .defaultRandom() }
    func decode(_ type: Double.Type, forKey key: Key) throws -> Double { return .defaultRandom() }
    func decode(_ type: String.Type, forKey key: Key) throws -> String { return .defaultRandom() }
    func decode(_ type: T.Type, forKey key: Key) throws -> T {
    decoder.codingPath.append(key)
    defer { decoder.codingPath.removeLast() }
    return try decoder.random()
    }
    ͱʹ͔͘ཚ਺Λฦͯ͠Δ͚ͩ

    View Slide

  21. ͦͷଞ׆༻ํ๏
    struct JSON: Codable, Randomizable {
    let value: String
    }
    let json = JSON.randomValue()
    let jsonData = try JSONEncoder().encode(json)
    let jsonString = String(data: jsonData, encoding: .utf8)
    ϥϯμϜͳJSONจࣈྻͷ࡞੒

    View Slide

  22. Swift Package ManagerରԠ
    ίϚϯυϥΠϯπʔϧͰ׆༻Ͱ͖ͦ͏ͳͷͰɺ

    Swift Package ManagerʹରԠ͠·ͨ͠
    import PackageDescription
    let package = Package(
    name: "Hoge",
    dependencies: [
    .package(url: "https://github.com/tattn/Randomizable.git", from: “1.0.3"),
    .package(url: "https://github.com/tattn/MoreCodable.git", from: “0.2.2")
    ],
    targets: [
    .target(
    name: “Hoge",
    dependencies: ["Randomizable", "MoreCodable"])
    ]
    )
    IUUQTHJUIVCDPNUBUUO.PSF$PEBCMF ͱͷ૊Έ߹Θ͕͓ͤ͢͢Ί

    View Slide

  23. ·ͱΊ

    View Slide

  24. Codableେ޷͖ਓ͔ؒΒͷҰݴ
    • Codable͸ϦϑϨΫγϣϯʹ΋࢖͑Δ
    • ࠇຐज़΍ϦϑϨΫγϣϯɺίʔυੜ੒ͳͲɺ

    ࢖͑Δ΋ͷ͸͢΂ͯ࢖ָͬͯʹςετ͠Α͏

    View Slide