Pro Yearly is on sale from $80 to $50! »

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

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

0620564f0125b8b3b7f4fe40c10b8b4e?s=128

Tatsuya Tanaka

December 18, 2018
Tweet

Transcript

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

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

    @tanakasan2525 @tattn
  3. Ϣχοτςετ༻ͷ
 ςετσʔλ

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

  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(...)) ←͕͜͜໘౗
  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(…)) ͜Μͳͷॻ͖ͨ͘ͳ͍
 ਓྨͷ࢓ࣄ͡Όͳ͍ (´ɾωɾʆ)
  7. APIͷϨεϙϯεΛςετσʔλʹࠩ͠ସ͑Δํ๏ • protocolͰࠩ͠ସ͑ͨMockAPIΫϥΠΞϯτʹ
 σʔλΛࠩ͠ࠐΉ • URLSessionΛϋοΫͯ͠ɺϨεϙϯεΛ
 ༻ҙͨ͠JSONϑΝΠϧʹࠩ͠ସ͑Δ

  8. APIͷϨεϙϯεΛςετσʔλʹࠩ͠ସ͑Δํ๏ • protocolͰࠩ͠ସ͑ͨMockAPIΫϥΠΞϯτʹ
 σʔλΛࠩ͠ࠐΉ • URLSessionΛϋοΫͯ͠ɺϨεϙϯεΛ
 ༻ҙͨ͠JSONϑΝΠϧʹࠩ͠ସ͑Δ

  9. ཚ਺ͰॳظԽͯ͠
 ςετσʔλʹ͢Δ

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


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

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

    Mirror: ࢖͏લʹΠϯελϯεԽ͕ඞཁ (㱻) • Codable: ΠϯελϯεԽͳ͠Ͱ
 ܕͷߏ଄ΛಘΒΕΔ
  13. https://github.com/tattn/ Randomizable

  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])) ↓ཚ਺ͰॳظԽͯ͘͠ΕΔ
  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())
  16. ࣮૷

  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͍ͯ͠Δ৔߹͸ϥϯμϜͷ࣮૷Λෆཁʹ
  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<Wrapped> { return Bool.random() ? nil : Wrapped.randomValue() } } extension URL: Randomizable { public static func randomValue() -> URL { return URL(string: "https://\(String.defaultRandom()).com")! } }
  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<Key: CodingKey>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> { return KeyedDecodingContainer(KeyedContainer<Key>(decoder: self, codingPath: [])) } open func unkeyedContainer() throws -> UnkeyedDecodingContainer { return UnkeyedContanier(decoder: self) } open func singleValueContainer() throws -> SingleValueDecodingContainer { return SingleValueContanier(decoder: self) } func random<T: Decodable>() 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<T : Decodable>(_ type: T.Type) throws -> T { return try T(from: self) } } extension RandomDecoder { private class KeyedContainer<Key: CodingKey>: 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<T: Decodable>(_ type: T.Type, forKey key: Key) throws -> T { decoder.codingPath.append(key) defer { decoder.codingPath.removeLast() } return try decoder.random() } func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey { decoder.codingPath.append(key) defer { decoder.codingPath.removeLast() } return KeyedDecodingContainer(KeyedContainer<NestedKey>(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<T: DefaultRamdom>(_ 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<T: Decodable>(_ type: T.Type) throws -> T { decoder.codingPath.append(AnyCodingKey(index: currentIndex)) defer { decoder.codingPath.removeLast() currentIndex += 1 } return try decoder.random() } func nestedContainer<NestedKey: CodingKey>(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer<NestedKey> { decoder.codingPath.append(AnyCodingKey(index: currentIndex)) defer { decoder.codingPath.removeLast() currentIndex += 1 } return KeyedDecodingContainer(KeyedContainer<NestedKey>(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<T: Decodable>(_ type: T.Type) throws -> T { return try decoder.random() } } }
  20. RandomDecoderͷ࣮૷ͷϐοΫΞοϓ extension RandomDecoder { private class KeyedContainer<Key: CodingKey>: 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<T: Decodable>(_ type: T.Type, forKey key: Key) throws -> T { decoder.codingPath.append(key) defer { decoder.codingPath.removeLast() } return try decoder.random() } ͱʹ͔͘ཚ਺Λฦͯ͠Δ͚ͩ
  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จࣈྻͷ࡞੒
  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 ͱͷ૊Έ߹Θ͕͓ͤ͢͢Ί
  23. ·ͱΊ

  24. Codableେ޷͖ਓ͔ؒΒͷҰݴ • Codable͸ϦϑϨΫγϣϯʹ΋࢖͑Δ • ࠇຐज़΍ϦϑϨΫγϣϯɺίʔυੜ੒ͳͲɺ
 ࢖͑Δ΋ͷ͸͢΂ͯ࢖ָͬͯʹςετ͠Α͏