Your SlideShare is downloading. ×
iOSビヘイビア駆動開発
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×

Introducing the official SlideShare app

Stunning, full-screen experience for iPhone and Android

Text the download link to your phone

Standard text messaging rates apply

iOSビヘイビア駆動開発

9,144
views

Published on

簡単なサンプルアプリをBDDで開発しましょう。KiwiとNocillaを使って簡潔で速いテストの書き方を説明します。

簡単なサンプルアプリをBDDで開発しましょう。KiwiとNocillaを使って簡潔で速いテストの書き方を説明します。

Published in: Technology

0 Comments
3 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
9,144
On Slideshare
0
From Embeds
0
Number of Embeds
8
Actions
Shares
0
Downloads
10
Comments
0
Likes
3
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. iOS ビヘイビア駆動開発 KiwiとNocillaでRESTfulなアプリのテスト 2014年3月9日 Brian Gesiak 研究生、東京大学 @modocache #startup_ios
  • 2. 内容 • ビヘイビア駆動開発(BDD)とは • iOSでBDDを実践する例 • Kiwi • 非同期通信(HTTP)のテスト方法 • Nocilla
  • 3. 通信のBDD サンプルアプリ • あるGitHubユーザのレポジトリを 表示するアプリを作りたい • GitHub APIからJSONをGETできる • https://api.github.com/users/ {{ username }}/repos.json
  • 4. 通信のBDD サンプルアプリ /// GET /users/:username/repos ! [ { "id": 1296269, "name": "Hello-World", "description": "My first repo!", /* ... */ } ]
  • 5. BDDで作ってみましょう Kiwiを使ってBDD実践 • ビヘイビア駆動開発とはテスト駆動開発から派生し た概念
  • 6. テスト駆動開発とは Red-Green-Refactorサイクル • Red: ! • Green: ! • Refactor:
  • 7. テスト駆動開発とは Red-Green-Refactorサイクル • Red: 失敗するテストを書く ! • Green: ! • Refactor:
  • 8. テスト駆動開発とは Red-Green-Refactorサイクル • Red: 失敗するテストを書く ! • Green: テストが成功するようにコードを書く ! • Refactor:
  • 9. テスト駆動開発とは Red-Green-Refactorサイクル • Red: 失敗するテストを書く ! • Green: テストが成功するようにコードを書く ! • Refactor: 重複をなくす
  • 10. テスト駆動開発とは Red-Green-Refactorサイクル • Red: 失敗するテストを書く ! • Green: テストが成功するようにコードを書く ! • Refactor: 重複をなくす 繰り返す
  • 11. XCTestを使ったiOS TDDの一例
  • 12. XCTestを使ったiOS TDDの一例 // Create an XCTestCase subclass @interface GHVRepoCollectionTests : XCTestCase // The subject of our tests @property (nonatomic, strong) GHVCollection *collection; @end ! @implementation GHVRepoCollectionTests // Set up the `repos` collection for each test - (void)setUp { [super setUp]; self.collection = [GHVCollection new]; } // Add a test - (void)testAddingARepo { // Add a repo [self.collection addRepo:[GHVRepo new]]; ! // Assert the number of repos is now one XCTAssertEqual([self.collection.repos count], 1, @"Expected one repository"); } @end
  • 13. XCTestを使ったiOS TDDの一例 // Create an XCTestCase subclass @interface GHVRepoCollectionTests : XCTestCase // The subject of our tests @property (nonatomic, strong) GHVCollection *collection; @end ! @implementation GHVRepoCollectionTests // Set up the `repos` collection for each test - (void)setUp { [super setUp]; self.collection = [GHVCollection new]; } // Add a test - (void)testAddingARepo { // Add a repo [self.collection addRepo:[GHVRepo new]]; ! // Assert the number of repos is now one XCTAssertEqual([self.collection.repos count], 1, @"Expected one repository"); } @end
  • 14. XCTestを使ったiOS TDDの一例 // Create an XCTestCase subclass @interface GHVRepoCollectionTests : XCTestCase // The subject of our tests @property (nonatomic, strong) GHVCollection *collection; @end ! @implementation GHVRepoCollectionTests // Set up the `repos` collection for each test - (void)setUp { [super setUp]; self.collection = [GHVCollection new]; } // Add a test - (void)testAddingARepo { // Add a repo [self.collection addRepo:[GHVRepo new]]; ! // Assert the number of repos is now one XCTAssertEqual([self.collection.repos count], 1, @"Expected one repository"); } @end
  • 15. XCTestを使ったiOS TDDの一例 // Create an XCTestCase subclass @interface GHVRepoCollectionTests : XCTestCase // The subject of our tests @property (nonatomic, strong) GHVCollection *collection; @end ! @implementation GHVRepoCollectionTests // Set up the `repos` collection for each test - (void)setUp { [super setUp]; self.collection = [GHVCollection new]; } // Add a test - (void)testAddingARepo { // Add a repo [self.collection addRepo:[GHVRepo new]]; ! // Assert the number of repos is now one XCTAssertEqual([self.collection.repos count], 1, @"Expected one repository"); } @end
  • 16. XCTestを使ったiOS TDDの一例 // Create an XCTestCase subclass @interface GHVRepoCollectionTests : XCTestCase // The subject of our tests @property (nonatomic, strong) GHVCollection *collection; @end ! @implementation GHVRepoCollectionTests // Set up the `repos` collection for each test - (void)setUp { [super setUp]; self.collection = [GHVCollection new]; } // Add a test - (void)testAddingARepo { // Add a repo [self.collection addRepo:[GHVRepo new]]; ! // Assert the number of repos is now one XCTAssertEqual([self.collection.repos count], 1, @"Expected one repository"); } @end
  • 17. XCTestを使ったiOS TDDの一例 // Create an XCTestCase subclass @interface GHVRepoCollectionTests : XCTestCase // The subject of our tests @property (nonatomic, strong) GHVCollection *collection; @end ! @implementation GHVRepoCollectionTests // Set up the `repos` collection for each test - (void)setUp { [super setUp]; self.collection = [GHVCollection new]; } // Add a test - (void)testAddingARepo { // Add a repo [self.collection addRepo:[GHVRepo new]]; ! // Assert the number of repos is now one XCTAssertEqual([self.collection.repos count], 1, @"Expected one repository"); } @end
  • 18. XCTestを使ったiOS TDDの一例 あえて言おう! カスであると!
  • 19. ビヘイビア駆動開発(BDD) • 「何をテストすればいいのか」 • コードをテストするのではなく、求めている挙動を 説明(specify)する
  • 20. KiwIを使ったiOS BDDの一例
  • 21. KiwIを使ったiOS BDDの一例 // Describe how the `GHVCollection` class behaves describe(@"GHVCollection", ^{ // Setup up the collection for each test __block GHVCollection *collection = nil; beforeEach(^{ collection = [GHVCollection new]; }); ! // Describe how the `-addRepo:` method behaves describe(@"-addRepo:", ^{ context(@"after adding a repo", ^{ // Add a repo before each test beforeEach(^{ [collection addRepo:[GHVRepo new]]; }); // Test the method behaves correctly it(@"has a repo count of one", ^{ [[collection.repos should] haveCountOf:1]; }); }); }); });
  • 22. KiwIを使ったiOS BDDの一例 // Describe how the `GHVCollection` class behaves describe(@"GHVCollection", ^{ // Setup up the collection for each test __block GHVCollection *collection = nil; beforeEach(^{ collection = [GHVCollection new]; }); ! // Describe how the `-addRepo:` method behaves describe(@"-addRepo:", ^{ context(@"after adding a repo", ^{ // Add a repo before each test beforeEach(^{ [collection addRepo:[GHVRepo new]]; }); // Test the method behaves correctly it(@"has a repo count of one", ^{ [[collection.repos should] haveCountOf:1]; }); }); }); });
  • 23. KiwIを使ったiOS BDDの一例 // Describe how the `GHVCollection` class behaves describe(@"GHVCollection", ^{ // Setup up the collection for each test __block GHVCollection *collection = nil; beforeEach(^{ collection = [GHVCollection new]; }); ! // Describe how the `-addRepo:` method behaves describe(@"-addRepo:", ^{ context(@"after adding a repo", ^{ // Add a repo before each test beforeEach(^{ [collection addRepo:[GHVRepo new]]; }); // Test the method behaves correctly it(@"has a repo count of one", ^{ [[collection.repos should] haveCountOf:1]; }); }); }); });
  • 24. KiwIを使ったiOS BDDの一例 // Describe how the `GHVCollection` class behaves describe(@"GHVCollection", ^{ // Setup up the collection for each test __block GHVCollection *collection = nil; beforeEach(^{ collection = [GHVCollection new]; }); ! // Describe how the `-addRepo:` method behaves describe(@"-addRepo:", ^{ context(@"after adding a repo", ^{ // Add a repo before each test beforeEach(^{ [collection addRepo:[GHVRepo new]]; }); // Test the method behaves correctly it(@"has a repo count of one", ^{ [[collection.repos should] haveCountOf:1]; }); }); }); });
  • 25. KiwIを使ったiOS BDDの一例 // Describe how the `GHVCollection` class behaves describe(@"GHVCollection", ^{ // Setup up the collection for each test __block GHVCollection *collection = nil; beforeEach(^{ collection = [GHVCollection new]; }); ! // Describe how the `-addRepo:` method behaves describe(@"-addRepo:", ^{ context(@"after adding a repo", ^{ // Add a repo before each test beforeEach(^{ [collection addRepo:[GHVRepo new]]; }); // Test the method behaves correctly it(@"has a repo count of one", ^{ [[collection.repos should] haveCountOf:1]; }); }); }); });
  • 26. KiwIを使ったiOS BDDの一例 // Describe how the `GHVCollection` class behaves describe(@"GHVCollection", ^{ // Setup up the collection for each test __block GHVCollection *collection = nil; beforeEach(^{ collection = [GHVCollection new]; }); ! // Describe how the `-addRepo:` method behaves describe(@"-addRepo:", ^{ context(@"after adding a repo", ^{ // Add a repo before each test beforeEach(^{ [collection addRepo:[GHVRepo new]]; }); // Test the method behaves correctly it(@"has a repo count of one", ^{ [[collection.repos should] haveCountOf:1]; }); }); }); });
  • 27. Kiwiのメリット
  • 28. Kiwiのメリット • Setup、teardownを無限にネストできる
  • 29. Kiwiのメリット • Setup、teardownを無限にネストできる beforeEach(^{ beforeAll(^{ afterEach(^{ afterAll(^{ /* /* /* /* ... ... ... ... */ */ */ */ }); }); }); });
  • 30. Kiwiのメリット • Setup、teardownを無限にネストできる beforeEach(^{ beforeAll(^{ afterEach(^{ afterAll(^{ • Mock、stubも入っている /* /* /* /* ... ... ... ... */ */ */ */ }); }); }); });
  • 31. Kiwiのメリット • Setup、teardownを無限にネストできる beforeEach(^{ beforeAll(^{ afterEach(^{ afterAll(^{ /* /* /* /* ... ... ... ... */ */ */ */ }); }); }); }); • Mock、stubも入っている [collection stub:@selector(addRepo:)];
  • 32. Kiwiのメリット • Setup、teardownを無限にネストできる beforeEach(^{ beforeAll(^{ afterEach(^{ afterAll(^{ /* /* /* /* ... ... ... ... */ */ */ */ }); }); }); }); • Mock、stubも入っている [collection stub:@selector(addRepo:)]; • 非同期テストをサポート
  • 33. Kiwiのメリット • Setup、teardownを無限にネストできる beforeEach(^{ beforeAll(^{ afterEach(^{ afterAll(^{ /* /* /* /* ... ... ... ... */ */ */ */ }); }); }); }); • Mock、stubも入っている [collection stub:@selector(addRepo:)]; • 非同期テストをサポート [[collection.repos shouldEventually] haveCountOf:2];
  • 34. Kiwiのメリット • Setup、teardownを無限にネストできる beforeEach(^{ beforeAll(^{ afterEach(^{ afterAll(^{ /* /* /* /* ... ... ... ... */ */ */ */ }); }); }); }); • Mock、stubも入っている [collection stub:@selector(addRepo:)]; • 非同期テストをサポート [[collection.repos shouldEventually] haveCountOf:2]; • XCTestより読みやすい
  • 35. まずは失敗するテストを
  • 36. まずは失敗するテストを /// GHVAPIClientSpec.m ! it(@"gets repositories", ^{ // The repos returned by the API __block NSArray *allRepos = nil; ! // Fetch the repos from the API [client allRepositoriesForUsername:@"modocache" success:^(NSArray *repos) { // Set the repos allRepos = repos; } failure:nil]; ! // Assert that the repos have been set [[expectFutureValue(allRepos) shouldEventually] haveCountOf:1]; });
  • 37. まずは失敗するテストを /// GHVAPIClientSpec.m ! it(@"gets repositories", ^{ // The repos returned by the API __block NSArray *allRepos = nil; ! // Fetch the repos from the API [client allRepositoriesForUsername:@"modocache" success:^(NSArray *repos) { // Set the repos allRepos = repos; } failure:nil]; ! // Assert that the repos have been set [[expectFutureValue(allRepos) shouldEventually] haveCountOf:1]; });
  • 38. まずは失敗するテストを /// GHVAPIClientSpec.m ! it(@"gets repositories", ^{ // The repos returned by the API __block NSArray *allRepos = nil; ! // Fetch the repos from the API [client allRepositoriesForUsername:@"modocache" success:^(NSArray *repos) { // Set the repos allRepos = repos; } failure:nil]; ! // Assert that the repos have been set [[expectFutureValue(allRepos) shouldEventually] haveCountOf:1]; });
  • 39. まずは失敗するテストを /// GHVAPIClientSpec.m ! it(@"gets repositories", ^{ // The repos returned by the API __block NSArray *allRepos = nil; ! // Fetch the repos from the API [client allRepositoriesForUsername:@"modocache" success:^(NSArray *repos) { // Set the repos allRepos = repos; } failure:nil]; ! // Assert that the repos have been set [[expectFutureValue(allRepos) shouldEventually] haveCountOf:1]; });
  • 40. まずは失敗するテストを /// GHVAPIClientSpec.m ! it(@"gets repositories", ^{ // The repos returned by the API __block NSArray *allRepos = nil; ! // Fetch the repos from the API [client allRepositoriesForUsername:@"modocache" success:^(NSArray *repos) { // Set the repos allRepos = repos; } failure:nil]; ! // Assert that the repos have been set [[expectFutureValue(allRepos) shouldEventually] haveCountOf:1]; });
  • 41. まずは失敗するテストを /// GHVAPIClientSpec.m ! it(@"gets repositories", ^{ // The repos returned by the API __block NSArray *allRepos = nil; ! // Fetch the repos from the API [client allRepositoriesForUsername:@"modocache" success:^(NSArray *repos) { // Set the repos allRepos = repos; } failure:nil]; ! // Assert that the repos have been set [[expectFutureValue(allRepos) shouldEventually] haveCountOf:1]; });
  • 42. テストを成功させる
  • 43. テストを成功させる /// GHVAPIClient.m ! // Create a request operation manager pointing at the GitHub API NSString *urlString = @"https://api.github.com/"; NSURL *baseURL = [NSURL URLWithString:urlString]; AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:baseURL]; ! // The manager should serialize the response as JSON manager.requestSerializer = [AFJSONRequestSerializer serializer]; ! // Send a request to GET /users/:username/repos [manager GET:[NSString stringWithFormat:@"users/%@/repos", username] parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) { // Send response object to success block success(responseObject); } failure:nil]; }
  • 44. テストを成功させる /// GHVAPIClient.m ! // Create a request operation manager pointing at the GitHub API NSString *urlString = @"https://api.github.com/"; NSURL *baseURL = [NSURL URLWithString:urlString]; AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:baseURL]; ! // The manager should serialize the response as JSON manager.requestSerializer = [AFJSONRequestSerializer serializer]; ! // Send a request to GET /users/:username/repos [manager GET:[NSString stringWithFormat:@"users/%@/repos", username] parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) { // Send response object to success block success(responseObject); } failure:nil]; }
  • 45. テストを成功させる /// GHVAPIClient.m ! // Create a request operation manager pointing at the GitHub API NSString *urlString = @"https://api.github.com/"; NSURL *baseURL = [NSURL URLWithString:urlString]; AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:baseURL]; ! // The manager should serialize the response as JSON manager.requestSerializer = [AFJSONRequestSerializer serializer]; ! // Send a request to GET /users/:username/repos [manager GET:[NSString stringWithFormat:@"users/%@/repos", username] parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) { // Send response object to success block success(responseObject); } failure:nil]; }
  • 46. テストを成功させる /// GHVAPIClient.m ! // Create a request operation manager pointing at the GitHub API NSString *urlString = @"https://api.github.com/"; NSURL *baseURL = [NSURL URLWithString:urlString]; AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:baseURL]; ! // The manager should serialize the response as JSON manager.requestSerializer = [AFJSONRequestSerializer serializer]; ! // Send a request to GET /users/:username/repos [manager GET:[NSString stringWithFormat:@"users/%@/repos", username] parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) { // Send response object to success block success(responseObject); } failure:nil]; }
  • 47. テストを成功させる /// GHVAPIClient.m ! // Create a request operation manager pointing at the GitHub API NSString *urlString = @"https://api.github.com/"; NSURL *baseURL = [NSURL URLWithString:urlString]; AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:baseURL]; ! // The manager should serialize the response as JSON manager.requestSerializer = [AFJSONRequestSerializer serializer]; ! // Send a request to GET /users/:username/repos [manager GET:[NSString stringWithFormat:@"users/%@/repos", username] parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) { // Send response object to success block success(responseObject); } failure:nil]; }
  • 48. このテストの問題点 • 外部依存 • GitHub APIが落ちていたらテストが失敗する • ネットに繋がっていなかったら失敗する • レスポンスが遅いと失敗する • 遅い • テストを実行するたびにリクエストが送られる
  • 49. NocillaによるHTTP Stubbing 外部依存の除去 stubRequest(@"GET", @“https://api.github.com/" @“users/modocache/repos") .andReturn(200) .withHeaders(@{@"Content-Type": @"application/json"}) .withBody(@"["repo-1"]"); ! GHVAPIClient *client = [GHVAPIClient new]; ! // ... ! [[expectFutureValue(allRepos) shouldEventually] haveCountOf:1];
  • 50. NocillaによるHTTP Stubbing 外部依存の除去 stubRequest(@"GET", @“https://api.github.com/" @“users/modocache/repos") .andReturn(200) .withHeaders(@{@"Content-Type": @"application/json"}) .withBody(@"["repo-1"]"); ! GHVAPIClient *client = [GHVAPIClient new]; ! // ... ! [[expectFutureValue(allRepos) shouldEventually] haveCountOf:1];
  • 51. NocillaによるHTTP Stubbing 外部依存の除去 stubRequest(@"GET", @“https://api.github.com/" @“users/modocache/repos") .andReturn(200) .withHeaders(@{@"Content-Type": @"application/json"}) .withBody(@"["repo-1"]"); ! GHVAPIClient *client = [GHVAPIClient new]; ! // ... ! [[expectFutureValue(allRepos) shouldEventually] haveCountOf:1];
  • 52. NocillaによるHTTP Stubbing 外部依存の除去 stubRequest(@"GET", @“https://api.github.com/" @“users/modocache/repos") .andReturn(200) .withHeaders(@{@"Content-Type": @"application/json"}) .withBody(@"["repo-1"]"); ! GHVAPIClient *client = [GHVAPIClient new]; ! // ... ! [[expectFutureValue(allRepos) shouldEventually] haveCountOf:1];
  • 53. NocillaによるHTTP Stubbing 外部依存の除去 stubRequest(@"GET", @“https://api.github.com/" @“users/modocache/repos") .andReturn(200) .withHeaders(@{@"Content-Type": @"application/json"}) .withBody(@"["repo-1"]"); ! GHVAPIClient *client = [GHVAPIClient new]; ! // ... ! [[expectFutureValue(allRepos) shouldEventually] haveCountOf:1];
  • 54. NocillaによるHTTP Stubbing 外部依存の除去 stubRequest(@"GET", @“https://api.github.com/" @“users/modocache/repos") .andReturn(200) .withHeaders(@{@"Content-Type": @"application/json"}) .withBody(@"["repo-1"]"); ! GHVAPIClient *client = [GHVAPIClient new]; ! // ... ! [[expectFutureValue(allRepos) shouldEventually] haveCountOf:1];
  • 55. NocillaによるHTTP Stubbing 当たらなければ どうということはない
  • 56. Nocillaで解決できた問題点 • 外部依存 • GitHub APIが落ちていたらテストが失敗する • ネットに繋がっていなかったら失敗する • レスポンスが遅いと失敗する • 遅い • テストを実行するたびにリクエストが送られる
  • 57. Nocillaで解決できた問題点 • 外部依存 ✓• GitHub APIが落ちていたらテストが失敗する • ネットに繋がっていなかったら失敗する • レスポンスが遅いと失敗する • 遅い • テストを実行するたびにリクエストが送られる
  • 58. Nocillaで解決できた問題点 • 外部依存 ✓• GitHub APIが落ちていたらテストが失敗する ✓• ネットに繋がっていなかったら失敗する • レスポンスが遅いと失敗する • 遅い • テストを実行するたびにリクエストが送られる
  • 59. Nocillaで解決できた問題点 • 外部依存 ✓• GitHub APIが落ちていたらテストが失敗する ✓• ネットに繋がっていなかったら失敗する ✓• レスポンスが遅いと失敗する • 遅い • テストを実行するたびにリクエストが送られる
  • 60. Nocillaで解決できた問題点 • 外部依存 ✓• GitHub APIが落ちていたらテストが失敗する ✓• ネットに繋がっていなかったら失敗する ✓• レスポンスが遅いと失敗する • 遅い ✓• テストを実行するたびにリクエストが送られる
  • 61. 他のNocillaの機能
  • 62. 他のNocillaの機能 • 正規表現を使ってHTTP requestのstub
  • 63. 他のNocillaの機能 • 正規表現を使ってHTTP requestのstub stubRequest(@"GET", @"https://api.github.com/" @"users/(.*?)/repos".regex)
  • 64. 他のNocillaの機能 • 正規表現を使ってHTTP requestのstub stubRequest(@"GET", @"https://api.github.com/" @"users/(.*?)/repos".regex) • ネットに繋がっていないときのエラーをテストする こともできる
  • 65. 他のNocillaの機能 • 正規表現を使ってHTTP requestのstub stubRequest(@"GET", @"https://api.github.com/" @"users/(.*?)/repos".regex) • ネットに繋がっていないときのエラーをテストする こともできる NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:29 userInfo:@{NSLocalizedDescriptionKey: @"Uh-oh!"}]; stubRequest(@"GET", @"...") .andFailWithError(error);
  • 66. 要約
  • 67. 要約 • 読みやすくて、非同期コードでも使えるBDDフレー ムワークKiwi • https://github.com/allending/Kiwi
  • 68. 要約 • 読みやすくて、非同期コードでも使えるBDDフレー ムワークKiwi • https://github.com/allending/Kiwi pod "Kiwi/XCTest"
  • 69. 要約 • 読みやすくて、非同期コードでも使えるBDDフレー ムワークKiwi • https://github.com/allending/Kiwi pod "Kiwi/XCTest" • 通信に依存するコードのテストに役立つNocilla • https://github.com/luisobo/Nocilla
  • 70. 要約 • 読みやすくて、非同期コードでも使えるBDDフレー ムワークKiwi • https://github.com/allending/Kiwi pod "Kiwi/XCTest" • 通信に依存するコードのテストに役立つNocilla • https://github.com/luisobo/Nocilla pod "Nocilla"
  • 71. 質疑応答 @modocache #startup_ios
  • 72. 質疑応答 @modocache #startup_ios describe(@"このLT", ^{ context(@"スライドが終わったら", ^{ it(@"質疑応答に移る", ^{ }); }); }); [[you should] askQuestions]; [[you shouldEventually] receive:@selector(stop)];
  • 73. 質疑応答 @modocache #startup_ios describe(@"このLT", ^{ context(@"スライドが終わったら", ^{ it(@"質疑応答に移る", ^{ }); }); }); [[you should] askQuestions]; [[you shouldEventually] receive:@selector(stop)];
  • 74. 質疑応答 @modocache #startup_ios describe(@"このLT", ^{ context(@"スライドが終わったら", ^{ it(@"質疑応答に移る", ^{ }); }); }); [[you should] askQuestions]; [[you shouldEventually] receive:@selector(stop)];
  • 75. 質疑応答 @modocache #startup_ios describe(@"このLT", ^{ context(@"スライドが終わったら", ^{ it(@"質疑応答に移る", ^{ }); }); }); [[you should] askQuestions]; [[you shouldEventually] receive:@selector(stop)];
  • 76. 質疑応答 @modocache #startup_ios describe(@"このLT", ^{ context(@"スライドが終わったら", ^{ it(@"質疑応答に移る", ^{ }); }); }); [[you should] askQuestions]; [[you shouldEventually] receive:@selector(stop)];