Slide 1

Slide 1 text

CDC Testing on iOS Cookpad Tech Kitchen Vol2 @giginet

Slide 2

Slide 2 text

୭ʁ • @giginet • ٕज़෦ϞόΠϧج൫άϧʔϓ • ৽ଔ2೥໨

Slide 3

Slide 3 text

Agenda • CDC Testingͱ͸ • Pact • Phakchi • ஌ݟ

Slide 4

Slide 4 text

՝୊ • APIͷมߋʹΑΔαʔϏεͷഁյͷݕ஌ • αʔόʔɺΫϥΠΞϯτ։ൃऀؒͰͷ࢓༷ڞ ༗ͷඞཁੑ

Slide 5

Slide 5 text

CDC Testing

Slide 6

Slide 6 text

CDC Testing • Consumer-Driven Contract Testing • APIͱΫϥΠΞϯτؒͷ੔߹ੑΛςετ͢Δ ͨΊͷςετख๏

Slide 7

Slide 7 text

༻ޠ • Provider • APIΛఏڙ͢Δଆ = APIαʔόʔ • Consumer • APIΛར༻͢Δଆ = iOSΞϓϦ • Contract • APIͷ࢓༷

Slide 8

Slide 8 text

Pact • CDC TestingͷͨΊͷϑϨʔϜϫʔΫ • http://docs.pact.io/ • https://github.com/realestate-com-au/pact • ݩʑ͸αʔόʔؒ࿈ܞΛ૝ఆ

Slide 9

Slide 9 text

࣮ફ Pact:ϚΠΫϩαʔϏε࣌୅ͷςετπʔϧ http://techlife.cookpad.com/entry/ 2016/06/28/164247

Slide 10

Slide 10 text

$POTVNFS ϞόΠϧΞϓϦ 1SPWJEFS "1* #SPLFS HTTP Request HTTP Response Pact file ϦΫΤετͱϨεϙϯεΛBrokerʹه࿥͢Δ

Slide 11

Slide 11 text

Contract(ྫ) • GET /recipes?keyword=ण࢘ { "recipes":[ { "name":"େτϩ", "description":"ඒຯ͍" }, { "name":"͏ʹ", "description":"ඒຯ͍" } ] }

Slide 12

Slide 12 text

$POTVNFS ϞόΠϧΞϓϦ 1SPWJEFS "1* #SPLFS HTTP Request HTTP Response Pact file Pact fileʹهड़ͨ͠ϦΫΤετΛ౤͛ͯݕূ͢Δ

Slide 13

Slide 13 text

$POTVNFS ϞόΠϧΞϓϦ 1SPWJEFS "1* #SPLFS HTTP Request HTTP Response Pact file

Slide 14

Slide 14 text

6OJU5FTU 1BDU.PDL4FSWFS Ϣχοτςετͷىಈ࣌ʹϞο ΫαʔόʔΛ্ཱͪ͛Δ ىಈ

Slide 15

Slide 15 text

6OJU5FTU 1BDU.PDL4FSWFS ϞοΫαʔόʔʹϦΫΤετͱ Ϩεϙϯεͷ૊ JOUFSBDUJPO Λొ ࿥͢Δ ϦΫΤετͱ ظ଴͢ΔϨεϙϯεΛ ొ࿥

Slide 16

Slide 16 text

6OJU5FTU 1BDU.PDL4FSWFS Ϣχοτςετ͔ΒϞοΫαʔ όʔʹରͯ͠ϦΫΤετΛૹΓɺର ʹͳΔϨεϙϯεΛฦ͢ ϦΫΤετ Ϩεϙϯε

Slide 17

Slide 17 text

6OJU5FTU 1BDU.PDL4FSWFS ड͚औͬͨϨεϙϯεΛΫϥΠ ΞϯτଆͰݕࠪ͠ɺਖ਼͚͠Ε͹Ϟο Ϋαʔόʔʹ఻͑Δ 1BDUϑΝΠϧ͕ੜ੒ ऴྃΛ௨஌ ੜ੒ Pact file

Slide 18

Slide 18 text

#SPLFS ϞοΫαʔόʔ͕1BDUϑΝΠϧ Λੜ੒͢ΔͷͰ#SPLFSʹΞοϓ ϩʔυ͢Δ Ξοϓϩʔυ Pact file

Slide 19

Slide 19 text

cookpad/Phakchi https://github.com/cookpad/Phakchi

Slide 20

Slide 20 text

Phakchi • PactͷςετέʔεΛָʹهड़͢ΔϥΠϒϥϦ • Swift੡ • XCTest͔Βར༻ • cf : https://github.com/DiUS/pact-consumer- swift

Slide 21

Slide 21 text

1. ϞοΫαʔόʔͷىಈ 2. interactionͷొ࿥ 3. ϦΫΤετΛૹΔ 4. Ϩεϙϯεͷݕࠪ 5. PactϑΝΠϧͷੜ੒

Slide 22

Slide 22 text

1. ϞοΫαʔόʔͷىಈ 2. interactionͷొ࿥ 3. ϦΫΤετΛૹΔ 4. Ϩεϙϯεͷݕࠪ 5. PactϑΝΠϧͷੜ੒

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

import Foundation import XCTest import Phakchi class RecipeSearchPact: XCTest { var session: Session! override func setUp() { super.setUp() let expectation = expectationWithDescription("Set up Pact environment") controlServer.startSession(withConsumerName: "Cookpad", providerName: "APIServer") { newSession in if let newSession = newSession { self.session = newSession expectation.fulfill() } } waitForExpectationsWithTimeout(3.0) } }

Slide 25

Slide 25 text

1. ϞοΫαʔόʔͷىಈ 2. interactionͷొ࿥ 3. ϦΫΤετΛૹΔ 4. Ϩεϙϯεͷݕࠪ 5. PactϑΝΠϧͷੜ੒

Slide 26

Slide 26 text

func testSearchRecipe() { let manager = RecipeManager(baseURL: session.baseURL) let expected = [ "recipes": [ ["name": "େτϩ", "description": "ඒຯ͍"], ["name": "͏ʹ", "description": “ඒຯ͍"], ] ] // Interactionͷొ࿥ let expectation = expectationWithDescription("recipes are found") session.given("some recipes exist") // Provider State .uponReceiving("a request for recipes") .with(method: .GET, path: "/recipes", query: [ "keyword": "ण࢘", ]) .willRespondWith(status: 200, body: expected)

Slide 27

Slide 27 text

func testSearchRecipe() { let manager = RecipeManager(baseURL: session.baseURL) let expected = [ "recipes": [ ["name": "େτϩ", "description": "ඒຯ͍"], ["name": "͏ʹ", "description": “ඒຯ͍"], ] ] // Interactionͷొ࿥ let expectation = expectationWithDescription("recipes are found") session.given("some recipes exist") // Provider State .uponReceiving("a request for recipes") .with(method: .GET, path: "/recipes", query: [ "keyword": "ण࢘", ]) .willRespondWith(status: 200, body: expected)

Slide 28

Slide 28 text

func testSearchRecipe() { let manager = RecipeManager(baseURL: session.baseURL) let expected = [ "recipes": [ ["name": "େτϩ", "description": "ඒຯ͍"], ["name": "͏ʹ", "description": “ඒຯ͍"], ] ] // Interactionͷొ࿥ let expectation = expectationWithDescription("recipes are found") session.given("some recipes exist") // Provider State .uponReceiving("a request for recipes") .with(method: .GET, path: "/recipes", query: [ "keyword": "ण࢘", ]) .willRespondWith(status: 200, body: expected)

Slide 29

Slide 29 text

1. ϞοΫαʔόʔͷىಈ 2. interactionͷొ࿥ 3. ϦΫΤετΛૹΔ 4. Ϩεϙϯεͷݕࠪ 5. PactϑΝΠϧͷੜ੒

Slide 30

Slide 30 text

session.run(completionBlock: { isValid in // completeTest࣮ߦޙʹݺ͹ΕΔ XCTAssertTrue(isValid) expectation.fulfill() }) { completeTest in // APIϦΫΤετΛૹΔ manager.fetchRecipes(from: ”ण࢘") { (recipes, error) in XCTAssertEqual(recipes.count, 2) XCTAssertNil(error) XCTAssertEqual(recipes[0].name, "େτϩ") XCTAssertEqual(recipes[0].description, "ඒຯ͍") XCTAssertEqual(recipes[1].name, "͏ʹ") XCTAssertEqual(recipes[1].description, "ඒຯ͍") completeTest() // Mock ServerʹϨεϙϯε͕ਖ਼͍͜͠ͱΛ௨஌ } }) waitForExpectationsWithTimeout(3.0) }

Slide 31

Slide 31 text

session.run(completionBlock: { isValid in // completeTest࣮ߦޙʹݺ͹ΕΔ XCTAssertTrue(isValid) expectation.fulfill() }) { completeTest in // APIϦΫΤετΛૹΔ manager.fetchRecipes(from: ”ण࢘") { (recipes, error) in XCTAssertEqual(recipes.count, 2) XCTAssertNil(error) XCTAssertEqual(recipes[0].name, "େτϩ") XCTAssertEqual(recipes[0].description, "ඒຯ͍") XCTAssertEqual(recipes[1].name, "͏ʹ") XCTAssertEqual(recipes[1].description, "ඒຯ͍") completeTest() // Mock ServerʹϨεϙϯε͕ਖ਼͍͜͠ͱΛ௨஌ } }) waitForExpectationsWithTimeout(3.0) }

Slide 32

Slide 32 text

session.run(completionBlock: { isValid in // completeTest࣮ߦޙʹݺ͹ΕΔ XCTAssertTrue(isValid) expectation.fulfill() }) { completeTest in // APIϦΫΤετΛૹΔ manager.fetchRecipes(from: ”ण࢘") { (recipes, error) in XCTAssertEqual(recipes.count, 2) XCTAssertNil(error) XCTAssertEqual(recipes[0].name, "େτϩ") XCTAssertEqual(recipes[0].description, "ඒຯ͍") XCTAssertEqual(recipes[1].name, "͏ʹ") XCTAssertEqual(recipes[1].description, "ඒຯ͍") completeTest() // Mock ServerʹϨεϙϯε͕ਖ਼͍͜͠ͱΛ௨஌ } }) waitForExpectationsWithTimeout(3.0) }

Slide 33

Slide 33 text

import Foundation import XCTest import Phakchi class RecipeSearchPact: XCTest { var session: Session! override func setUp() { super.setUp() let expectation = expectationWithDescription("Set up Pact environment") // ϞοΫαʔόʔͷॳظԽ controlServer.startSession(withConsumerName: "Cookpad", providerName: "APIServer") { newSession in if let newSession = newSession { self.session = newSession expectation.fulfill() } } waitForExpectationsWithTimeout(3.0) } func testSearchRecipe() { let manager = RecipeManager(baseURL: session.baseURL) let expected = [ "recipes": [ ["name": "େτϩ", "description": "ඒຯ͍"], ["name": "͏ʹ", "description": "ඒຯ͍"], ] ] // Interactionͷొ࿥ let expectation = expectationWithDescription("recipes are found") session.given("some recipes exist") // Provider State .uponReceiving("a request for recipes") .with(method: .GET, path: "/recipes", query: [ "keyword": "ण࢘", ]) .willRespondWith(status: 200, body: expected) session.run(completionBlock: { isValid in // completeTest࣮ߦޙʹݺ͹ΕΔ XCTAssertTrue(isValid) expectation.fulfill() }) { completeTest in // APIϦΫΤετΛૹΔ manager.fetchRecipesFromKeyword("ण࢘") { (recipes, error) in XCTAssertEqual(recipes.count, 2) XCTAssertNil(error) XCTAssertEqual(recipes[0].name, "େτϩ") XCTAssertEqual(recipes[0].description, "ඒຯ͍") XCTAssertEqual(recipes[1].name, "͏ʹ") XCTAssertEqual(recipes[1].description, "ඒຯ͍") completeTest() // Mock ServerʹϨεϙϯε͕ਖ਼͍͜͠ͱΛ௨஌ } }) waitForExpectationsWithTimeout(3.0) } }

Slide 34

Slide 34 text

$POTVNFS ϞόΠϧΞϓϦ 1SPWJEFS "1* #SPLFS HTTP Request HTTP Response Pact file Pact fileʹهड़ͨ͠ϦΫΤετΛ౤͛ͯݕূ͢Δ

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

ಋೖͯ͠Έͯ

Slide 37

Slide 37 text

ಋೖͯ͠Έͯ • ҰԠ͸APIͷมߋʹؾ͚ͮΔΑ͏ʹͳͬͨ • ·ͩ·ͩςετέʔε͕গͳ͍ͷͰະ஌਺

Slide 38

Slide 38 text

໰୊఺ • CIͷӡ༻ • ϚʔδλΠϛϯάͷ໰୊ • ڊେͳϨεϙϯε΁ͷରԠ͕೉͍͠

Slide 39

Slide 39 text

Any Questions?