├── .gitignore ├── .travis.yml ├── Frank └── features │ ├── add_first_customer.feature │ ├── add_first_customer_ja.feature │ ├── add_many_customers.feature │ └── step_definitions │ ├── pickerview_step.rb │ ├── segmented_steps.rb │ ├── tableviewcell_steps.rb │ ├── take_screenshot_step.rb │ └── textfield_steps.rb ├── GHUnitTests ├── CustomerDetailViewTest.m ├── CustomerTest.m ├── GHAssertionTest.m ├── GHUnitTests-Info.plist ├── GHUnitTests-Prefix.pch ├── GravatarAccessorTest.m ├── Scripts │ ├── CopyTestImages.sh │ ├── LICENSE │ ├── PrepareUITests.sh │ ├── RunIPhoneSecurityd.sh │ └── RunTests.sh ├── en.lproj │ └── InfoPlist.strings └── main.m ├── HelloTesting.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── HelloTesting.xcscheme ├── HelloTesting.xcworkspace └── contents.xcworkspacedata ├── HelloTesting ├── AppDelegate.h ├── AppDelegate.m ├── Base.lproj │ ├── Main_iPad.storyboard │ └── Main_iPhone.storyboard ├── Customer.m ├── CustomerDetailView.h ├── CustomerDetailView.m ├── CustomerDetailView.xib ├── CustomerPreview.html ├── DetailViewController.h ├── DetailViewController.m ├── HelloTesting-Info.plist ├── HelloTesting-Prefix.pch ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json ├── MasterViewController.h ├── MasterViewController.m ├── PreviewViewController.h ├── PreviewViewController.m ├── en.lproj │ └── InfoPlist.strings ├── main.m ├── models │ ├── Customer.h │ ├── Customer.m │ ├── Customer2.h │ ├── Customer2.m │ ├── GravatarAccessor.h │ └── GravatarAccessor.m └── noavatar.png ├── HelloTestingTests ├── Customer2Test.m ├── CustomerTest.m ├── CustomerTestExtracted.m ├── GravatarAccessorTest.m ├── HelloTestingTests-Info.plist ├── XCTAssertionTest.m └── en.lproj │ └── InfoPlist.strings ├── KIFTests ├── AddFirstCustomer.m ├── KIFTests-Info.plist ├── KIFTests-Prefix.pch └── en.lproj │ └── InfoPlist.strings ├── KiwiTests ├── CustomerDetailViewSpec.m ├── CustomerSpec.m ├── ExpectationsSpec.m ├── GravatarAccessorAsynchronousSpec.m ├── GravatarAccessorSpec.m ├── KiwiTests-Info.plist ├── KiwiTests-Prefix.pch ├── en.lproj │ └── InfoPlist.strings └── testavatar.png ├── LICENSE ├── Makefile ├── MonkeyTalk ├── .project ├── .settings │ └── org.eclipse.core.resources.prefs ├── add_customer.mt ├── add_first_customer.mt └── add_many_customers.mt ├── NLTHTTPStubServerTests ├── GravatarAccessorTest.m ├── NLTHTTPStubServerTests-Info.plist ├── NLTHTTPStubServerTests-Prefix.pch ├── en.lproj │ └── InfoPlist.strings ├── main.m └── testavatar.png ├── OCHamcrestTests ├── CustomerTest.m ├── OCHamcrestTests-Info.plist ├── OCHamcrestTests-Prefix.pch └── en.lproj │ └── InfoPlist.strings ├── OCMockTests ├── Customer2Test.m ├── CustomerDetailViewTest.m ├── GravatarAccessorTest.m ├── OCMockTests-Info.plist ├── OCMockTests-Prefix.pch ├── en.lproj │ └── InfoPlist.strings └── testavatar.png ├── OCMockitoTests ├── CustomerDetailViewTest.m ├── GravatarAccessorTest.m ├── OCMockitoTests-Info.plist ├── OCMockitoTests-Prefix.pch ├── en.lproj │ └── InfoPlist.strings └── testavatar.png ├── Podfile ├── Podfile.lock ├── README.md ├── UIAutomation └── script │ ├── add_first_customer.js │ └── other.js └── distribution └── HelloTesting.plist /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | profile 14 | *.moved-aside 15 | DerivedData 16 | .idea/ 17 | *.hmap 18 | *.xccheckout 19 | ObjectiveC.gcno 20 | ObjectiveC.gcda 21 | 22 | #CocoaPods 23 | Pods 24 | 25 | #UI Automation 26 | instrumentscli*.trace/ 27 | 28 | #Frank 29 | Frank/* 30 | !Frank/features 31 | 32 | #MonkeyTalk 33 | libMonkeyTalk*.a 34 | MonkeyTalk/libs/ 35 | MonkeyTalk/reports/ 36 | MonkeyTalk/screenshots/ 37 | 38 | #Reports 39 | *-reports/ 40 | compile_commands.json 41 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode7 3 | cache: cocoapods 4 | 5 | before_install: 6 | - sudo easy_install cpp-coveralls 7 | - gem install cocoapods 8 | 9 | script: 10 | - make test REPORTER=pretty 11 | 12 | after_success: 13 | - make send-to-coveralls 14 | 15 | notifications: 16 | slack: 17 | secure: VSGP2XC0rtcQQ4VTOoNjb81+FA1y6NDW8Ul/OZMNtWiRRDyjUossBNGD5MQgzHuyPYjOi6vLivg5pH1kbpIP+0ZFvzxroz8Ogr/WQPjR7ajENuw7Dl6zsWGFMTTT0gQlI1Fx25g3d1CHLcj85xvUgacEwG1GKTIs3aglenEuxz4= 18 | -------------------------------------------------------------------------------- /Frank/features/add_first_customer.feature: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | 3 | Feature: 4 | 顧客を追加できること。一覧画面には氏名とマーケティング区分が表示されること 5 | 6 | Scenario: 7 | "Add"ボタンをタップすると顧客を1件追加し、編集画面に遷移する 8 | 9 | Given I launch the app 10 | When I touch the button marked "Add" 11 | Then I wait to see a navigation bar titled "Detail" 12 | 13 | Scenario: 14 | 顧客は男性・35歳に設定。一覧に戻ると、マーケティング区分はM2層となること 15 | 16 | Given I should see a navigation bar titled "Detail" 17 | And I wait for 0.5 seconds 18 | When I type "Newton Geizler" into the "name" text field using the keyboard 19 | And I select gender to "男性" 20 | And I select age to "35" 21 | And I navigate back 22 | Then I wait to see a navigation bar titled "Master" 23 | And I should see a cell name "Newton Geizler" and division "M2層" 24 | -------------------------------------------------------------------------------- /Frank/features/add_first_customer_ja.feature: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | #language: ja 3 | 4 | 機能: 5 | 顧客を追加できること。一覧画面には氏名とマーケティング区分が表示されること 6 | 7 | シナリオ: 8 | "Add"ボタンをタップすると顧客を1件追加し、編集画面に遷移する 9 | 10 | 前提 I launch the app 11 | もし I touch the button marked "Add" 12 | ならば I wait to see a navigation bar titled "Detail" 13 | 14 | シナリオ: 15 | 顧客は男性・35歳に設定。一覧に戻ると、マーケティング区分はM2層となること 16 | 17 | 前提 I should see a navigation bar titled "Detail" 18 | かつ I wait for 0.5 seconds 19 | もし I type "Newton Geizler" into the "name" text field using the keyboard 20 | かつ I select gender to "男性" 21 | かつ I select age to "35" 22 | かつ I navigate back 23 | ならば I wait to see a navigation bar titled "Master" 24 | かつ I should see a cell name "Newton Geizler" and division "M2層" 25 | -------------------------------------------------------------------------------- /Frank/features/add_many_customers.feature: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | 3 | Feature: 4 | 顧客を複数追加できること 5 | 6 | Background: 7 | Given I launch the app 8 | 9 | Scenario Outline: 10 | 顧客をn件追加する 11 | 12 | Given I touch the button marked "Add" 13 | And I wait to see a navigation bar titled "Detail" 14 | And I wait for 0.5 seconds 15 | When I type "" into the "name" text field using the keyboard 16 | And I select gender to "" 17 | And I select age to "" 18 | And I navigate back 19 | Then I wait to see a navigation bar titled "Master" 20 | And I should see a cell name "" and division "" 21 | 22 | Examples: 23 | | name | gender | age | division | 24 | | Newton Geizler | 男性 | 35 | M2層 | 25 | | Hermann Gottlieb | 男性 | 34 | M1層 | 26 | | Mako Mori | 女性 | 22 | F1層 | 27 | -------------------------------------------------------------------------------- /Frank/features/step_definitions/pickerview_step.rb: -------------------------------------------------------------------------------- 1 | When /^I select age to "(\d*)"$/ do | age_text | 2 | selector = "view:'UIPickerView' marked:'age'" 3 | row_index = age_text.to_i - 4 4 | views_switched = frankly_map( selector, 'selectRow:inComponent:animated:', row_index, 0, true ) 5 | raise "could not find anything matching [#{age_text}.#{row_index}] to switch" if views_switched.empty? 6 | sleep 0.5 7 | end 8 | -------------------------------------------------------------------------------- /Frank/features/step_definitions/segmented_steps.rb: -------------------------------------------------------------------------------- 1 | When /^I select gender to "([^"]*)"$/ do |gender_text| 2 | selector = "view:'UISegment' marked:'#{gender_text}'" 3 | touch( selector ) 4 | end 5 | -------------------------------------------------------------------------------- /Frank/features/step_definitions/tableviewcell_steps.rb: -------------------------------------------------------------------------------- 1 | Then /^I should see a cell name "([^\"]*)" and division "([^\"]*)"$/ do | name, division | 2 | cell_label = "view:'UIButton' marked:'More info, '#{name}', '#{division}'" 3 | view_with_mark_exists( cell_label ) 4 | end 5 | -------------------------------------------------------------------------------- /Frank/features/step_definitions/take_screenshot_step.rb: -------------------------------------------------------------------------------- 1 | Then /^I save a screenshot with prefix "(\w+)"$/ do |prefix| 2 | filename = "cucumber-reports/" + prefix + Time.now.to_i.to_s 3 | %x[screencapture #{filename}.png] 4 | end 5 | -------------------------------------------------------------------------------- /Frank/features/step_definitions/textfield_steps.rb: -------------------------------------------------------------------------------- 1 | When /^I type "([^"]*)" into the "([^"]*)" text field using the keyboard$/ do |text_to_type, placeholder| 2 | text_field_selector = "view marked:'#{placeholder}'" 3 | check_element_exists( text_field_selector ) 4 | touch( text_field_selector ) 5 | frankly_map( text_field_selector, 'setText:', text_to_type ) 6 | frankly_map( text_field_selector, 'endEditing:', true ) 7 | end 8 | -------------------------------------------------------------------------------- /GHUnitTests/CustomerDetailViewTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | #import "CustomerDetailView.h" 9 | #import "Customer.h" 10 | 11 | //UIViewの表示テストを行なう場合にはGHViewTestCaseを継承します 12 | @interface CustomerDetailViewTest : GHViewTestCase 13 | 14 | @end 15 | 16 | @implementation CustomerDetailViewTest 17 | 18 | - (void)testView{ 19 | //Viewを構築 20 | UINib* nib = [UINib nibWithNibName:@"CustomerDetailView" bundle:nil]; 21 | NSArray* array = [nib instantiateWithOwner:nil options:nil]; 22 | CustomerDetailView *sut = [array objectAtIndex:0]; 23 | 24 | //表示する顧客オブジェクトをセット 25 | Customer *customer = [[Customer alloc] init]; 26 | customer.name = @"岩田 貫一"; 27 | customer.mail = @"myemailaddress@example.com"; 28 | customer.gender = GenderMale; 29 | customer.age = 33; 30 | [sut setCustomer:customer]; 31 | 32 | //Viewと「期待される画像」との比較・検証を行なう 33 | GHVerifyView(sut); 34 | } 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /GHUnitTests/CustomerTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | #import "Customer.h" 9 | 10 | /** Customerテストクラス */ 11 | @interface CustomerTest : GHTestCase 12 | 13 | @end 14 | 15 | @implementation CustomerTest 16 | { 17 | /** テスト対象オブジェクト */ 18 | Customer *_sut; 19 | } 20 | 21 | /** 22 | * UIのテストや、メインスレッドでコールバックを受ける非同期のテストなど、テストを 23 | * メインスレッドで実行する必要がある場合に、YESを返すようオーバーライドします。 24 | * デフォルト(オーバーライドしない)はNOです。 25 | */ 26 | - (BOOL)shouldRunOnMainThread { 27 | return NO; 28 | } 29 | 30 | /** テストクラスのインスタンス生成時に一度だけ実行されます */ 31 | - (void)setUpClass 32 | { 33 | [super setUpClass]; 34 | } 35 | 36 | /** テストクラスのインスタンスが破棄されるとき実行されます */ 37 | - (void)tearDownClass 38 | { 39 | [super tearDownClass]; 40 | } 41 | 42 | /** 各テストメソッドの実行前に都度実行されます */ 43 | - (void)setUp 44 | { 45 | [super setUp]; 46 | _sut = [[Customer alloc] init]; 47 | } 48 | 49 | /** 各テストメソッドの実行後に都度実行されます */ 50 | - (void)tearDown 51 | { 52 | [super tearDown]; 53 | } 54 | 55 | 56 | /** C層となるケース */ 57 | - (void)testDivision_C 58 | { 59 | _sut.gender = GenderMale; 60 | _sut.age = 4; 61 | GHAssertEquals(DivisionC, [_sut division], @"C層であること"); 62 | 63 | _sut.age = 12; 64 | GHAssertEquals(DivisionC, [_sut division], @"C層であること"); 65 | 66 | _sut.gender = GenderFemale; 67 | _sut.age = 4; 68 | GHAssertEquals(DivisionC, [_sut division], @"C層であること"); 69 | 70 | _sut.age = 12; 71 | GHAssertEquals(DivisionC, [_sut division], @"C層であること"); 72 | } 73 | 74 | /** T層となるケース */ 75 | - (void)testDivision_T 76 | { 77 | _sut.gender = GenderMale; 78 | _sut.age = 13; 79 | GHAssertEquals(DivisionT, [_sut division], @"T層であること"); 80 | 81 | _sut.age = 19; 82 | GHAssertEquals(DivisionT, [_sut division], @"T層であること"); 83 | 84 | _sut.gender = GenderFemale; 85 | _sut.age = 13; 86 | GHAssertEquals(DivisionT, [_sut division], @"T層であること"); 87 | 88 | _sut.age = 19; 89 | GHAssertEquals(DivisionT, [_sut division], @"T層であること"); 90 | } 91 | 92 | /** M1層となるケース */ 93 | - (void)testDivision_M1 94 | { 95 | _sut.gender = GenderMale; 96 | _sut.age = 20; 97 | GHAssertEquals(DivisionM1, [_sut division], @"M1層であること"); 98 | 99 | _sut.age = 34; 100 | GHAssertEquals(DivisionM1, [_sut division], @"M1層であること"); 101 | } 102 | 103 | /** M2層となるケース */ 104 | - (void)testDivision_M2 105 | { 106 | _sut.gender = GenderMale; 107 | _sut.age = 35; 108 | GHAssertEquals(DivisionM2, [_sut division], @"M2層であること"); 109 | 110 | _sut.age = 49; 111 | GHAssertEquals(DivisionM2, [_sut division], @"M2層であること"); 112 | } 113 | 114 | /** M3層となるケース */ 115 | - (void)testDivision_M3 116 | { 117 | _sut.gender = GenderMale; 118 | _sut.age = 50; 119 | GHAssertEquals(DivisionM3, [_sut division], @"M3層であること"); 120 | 121 | _sut.age = 51; 122 | GHAssertEquals(DivisionM3, [_sut division], @"M3層であること"); 123 | } 124 | 125 | /** F1層となるケース */ 126 | - (void)testDivision_F1 127 | { 128 | _sut.gender = GenderFemale; 129 | _sut.age = 20; 130 | GHAssertEquals(DivisionF1, [_sut division], @"F1層であること"); 131 | 132 | _sut.age = 34; 133 | GHAssertEquals(DivisionF1, [_sut division], @"F1層であること"); 134 | } 135 | 136 | /** F2層となるケース */ 137 | - (void)testDivision_F2 138 | { 139 | _sut.gender = GenderFemale; 140 | _sut.age = 35; 141 | GHAssertEquals(DivisionF2, [_sut division], @"F2層であること"); 142 | 143 | _sut.age = 49; 144 | GHAssertEquals(DivisionF2, [_sut division], @"F2層であること"); 145 | } 146 | 147 | /** F3層となるケース */ 148 | - (void)testDivision_F3 149 | { 150 | _sut.gender = GenderFemale; 151 | _sut.age = 50; 152 | GHAssertEquals(DivisionF3, [_sut division], @"F3層であること"); 153 | 154 | _sut.age = 51; 155 | GHAssertEquals(DivisionF3, [_sut division], @"F3層であること"); 156 | } 157 | 158 | /** 分類外となるケース */ 159 | - (void)testDivision_None 160 | { 161 | _sut.gender = GenderMale; 162 | _sut.age = 3; 163 | GHAssertEquals(DivisionNone, [_sut division], @"分類外であること"); 164 | 165 | _sut.gender = GenderFemale; 166 | _sut.age = 3; 167 | GHAssertEquals(DivisionNone, [_sut division], @"分類外であること"); 168 | } 169 | 170 | @end 171 | -------------------------------------------------------------------------------- /GHUnitTests/GHAssertionTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | 9 | /** 検証に使用する例外クラス定義 */ 10 | @interface OriginalException : NSException 11 | @end 12 | @implementation OriginalException 13 | @end 14 | 15 | 16 | /** GHAssertの動作サンプル */ 17 | @interface GHAssertionTest : GHTestCase 18 | 19 | @end 20 | 21 | @implementation GHAssertionTest 22 | 23 | - (void)testAssertErr 24 | { 25 | OSErr noError = noErr; 26 | OSErr a1 = -4; 27 | OSErr a2 = -4; 28 | 29 | GHAssertNoErr(noError, nil); 30 | GHAssertErr(a1, a2, nil); 31 | } 32 | 33 | - (void)testAssertNULL 34 | { 35 | char *sutNil = NULL; 36 | char *sutNotNil = "aaaa"; 37 | 38 | GHAssertNULL(sutNil, nil); 39 | GHAssertNotNULL(sutNotNil, nil); 40 | } 41 | 42 | - (void)testAssertNil 43 | { 44 | NSObject *sutNil = nil; 45 | NSObject *sutNotNil = [[NSObject alloc] init]; 46 | 47 | GHAssertNil(sutNil, nil); 48 | GHAssertNotNil(sutNotNil, nil); 49 | } 50 | 51 | - (void)testAssertEquals 52 | { 53 | NSInteger a1 = 1000; 54 | NSInteger a2same = 1000; 55 | NSInteger a2notSame = 9999; 56 | 57 | GHAssertEquals(a1, a2same, nil); 58 | GHAssertNotEquals(a1, a2notSame, nil); 59 | } 60 | 61 | - (void)testAssertEqualObjects 62 | { 63 | NSString *a1 = @"abcdef"; 64 | NSString *a2same = @"abcdef"; 65 | NSString *a2notSame = @"012345"; 66 | 67 | GHAssertEqualObjects(a1, a2same, nil); 68 | GHAssertNotEqualObjects(a1, a2notSame, nil); 69 | } 70 | 71 | - (void)testAssertOperation 72 | { 73 | NSInteger a1 = 1000; 74 | NSInteger a2same = 1000; 75 | NSInteger a2notSame = 9999; 76 | 77 | GHAssertOperation(a1, a2same, =, nil); 78 | GHAssertOperation(a1, a2notSame, !=, nil); 79 | } 80 | 81 | - (void)testAssertGreaterThan 82 | { 83 | NSInteger a1 = 1001; 84 | NSInteger a2 = 1000; 85 | 86 | GHAssertGreaterThan(a1, a2, nil); 87 | GHAssertGreaterThanOrEqual(a1, a1, nil); 88 | GHAssertGreaterThanOrEqual(a1, a2, nil); 89 | } 90 | 91 | - (void)testAssertLessThan 92 | { 93 | NSInteger a1 = 1000; 94 | NSInteger a2 = 1001; 95 | 96 | GHAssertLessThan(a1, a2, nil); 97 | GHAssertLessThanOrEqual(a1, a1, nil); 98 | GHAssertLessThanOrEqual(a1, a2, nil); 99 | } 100 | 101 | - (void)testAssertEqualStrings 102 | { 103 | NSString *a1 = @"abcdef"; 104 | NSString *a2same = @"abcdef"; 105 | NSString *a2notSame = @"012345"; 106 | 107 | GHAssertEqualStrings(a1, a2same, nil); 108 | GHAssertNotEqualStrings(a1, a2notSame, nil); 109 | } 110 | 111 | - (void)testAssertEqualCStrings 112 | { 113 | const char *a1 = "abcdef"; 114 | const char *a2same = "abcdef"; 115 | const char *a2notSame = "012345"; 116 | 117 | GHAssertEqualCStrings(a1, a2same, nil); 118 | GHAssertNotEqualCStrings(a1, a2notSame, nil); 119 | } 120 | 121 | - (void)testAssertEqualWithAccuracy 122 | { 123 | double a1 = 0.021; 124 | double a2 = 0.071 - 0.05; 125 | 126 | GHAssertEqualsWithAccuracy(a1, a2, 0.0001, @"丸め誤差が出ても成功します"); 127 | // GHAssertEquals(a1, a2, @"このテストは失敗します"); 128 | } 129 | 130 | - (void)testAssertTrue 131 | { 132 | GHAssertTrue(true, nil); 133 | GHAssertTrueNoThrow(true, nil); 134 | } 135 | 136 | - (void)testAssertFalse 137 | { 138 | GHAssertFalse(false, nil); 139 | GHAssertFalseNoThrow(false, nil); 140 | } 141 | 142 | 143 | - (void)testAssertThrows 144 | { 145 | GHAssertThrows([@"abcdef" substringWithRange:NSMakeRange(5,9)], nil); 146 | // GHAssertThrows([@"abcdef" substringWithRange:NSMakeRange(5,1)], @"このテストは失敗します"); 147 | } 148 | 149 | - (void)testAssertThrowsSpecific 150 | { 151 | GHAssertThrowsSpecific([@"abcdef" substringWithRange:NSMakeRange(5,9)], NSException, nil); 152 | // GHAssertThrowsSpecific([@"abcdef" substringWithRange:NSMakeRange(5,9)], OriginalException, @"このテストは失敗します"); 153 | // GHAssertThrowsSpecific([@"abcdef" substringWithRange:NSMakeRange(5,1)], NSException, @"このテストは失敗します"); 154 | } 155 | 156 | - (void)testAssertThrowsSpecificNamed 157 | { 158 | GHAssertThrowsSpecificNamed([@"abcdef" substringWithRange:NSMakeRange(5,9)], NSException, NSRangeException, nil); 159 | // GHAssertThrowsSpecificNamed([@"abcdef" substringWithRange:NSMakeRange(5,9)], NSException, NSInvalidArgumentException, @"このテストは失敗します"); 160 | // GHAssertThrowsSpecificNamed([@"abcdef" substringWithRange:NSMakeRange(5,1)], NSException, NSRangeException, @"このテストは失敗します"); 161 | } 162 | 163 | - (void)testAssertNoThrow 164 | { 165 | // GHAssertNoThrow([@"abcdef" substringWithRange:NSMakeRange(5,9)], @"このテストは失敗します"); 166 | GHAssertNoThrow([@"abcdef" substringWithRange:NSMakeRange(5,1)], nil); 167 | } 168 | 169 | - (void)testAssertNoThrowsSpecific 170 | { 171 | // GHAssertNoThrowSpecific([@"abcdef" substringWithRange:NSMakeRange(5,9)], NSException, @"このテストは失敗します"); 172 | GHAssertNoThrowSpecific([@"abcdef" substringWithRange:NSMakeRange(5,9)], OriginalException, nil); 173 | GHAssertNoThrowSpecific([@"abcdef" substringWithRange:NSMakeRange(5,1)], NSException, nil); 174 | } 175 | 176 | - (void)testAssertNoThrowsSpecificNamed 177 | { 178 | // GHAssertNoThrowSpecificNamed([@"abcdef" substringWithRange:NSMakeRange(5,9)], NSException, NSRangeException, @"このテストは失敗します"); 179 | GHAssertNoThrowSpecificNamed([@"abcdef" substringWithRange:NSMakeRange(5,9)], NSException, NSInvalidArgumentException, nil); 180 | GHAssertNoThrowSpecificNamed([@"abcdef" substringWithRange:NSMakeRange(5,1)], NSException, NSRangeException, nil); 181 | } 182 | 183 | @end 184 | -------------------------------------------------------------------------------- /GHUnitTests/GHUnitTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIApplicationExitsOnSuspend 28 | 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /GHUnitTests/GHUnitTests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import "HelloTesting-Prefix.pch" 8 | -------------------------------------------------------------------------------- /GHUnitTests/GravatarAccessorTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | #import "GravatarAccessor.h" 9 | 10 | //非同期のテストを行なう場合にはGHAsyncTestCaseを継承します 11 | @interface GravatarAccessorTest : GHAsyncTestCase 12 | 13 | @end 14 | 15 | @implementation GravatarAccessorTest 16 | 17 | - (void)testRequestAvatar{ 18 | //非同期処理実行前にはこの[prepare]メソッドを必ず呼びます 19 | [self prepare]; 20 | 21 | //非同期処理を実行 22 | GravatarAccessor *sut = [[GravatarAccessor alloc] initWithMail:@"myemailaddress@example.com" delegate:self]; 23 | [sut requestAvatar]; 24 | 25 | //非同期処理が完了(成功)するのを待機します。timeout秒が経過するとテストは失敗します 26 | [self waitForStatus:kGHUnitWaitStatusSuccess timeout:10.0]; 27 | } 28 | 29 | 30 | #pragma mark - GravatarAvatarDelegate methods 31 | 32 | //取得成功(GravatarAvatarDelegateのメソッド) 33 | - (void)responseAvatar:(UIImage*)avatar{ 34 | //テスト成功を通知 35 | [self notify:kGHUnitWaitStatusSuccess forSelector:@selector(testRequestAvatar)]; 36 | } 37 | 38 | //取得失敗(GravatarAvatarDelegateのメソッド) 39 | - (void)responseError:(NSString*)message{ 40 | GHTestLog(message); 41 | //テスト失敗を通知 42 | [self notify:kGHUnitWaitStatusFailure forSelector:@selector(testRequestAvatar)]; 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /GHUnitTests/Scripts/CopyTestImages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Script to copy approved images from GHViewTestCases back to the project 4 | # 5 | # This script should be run at the command line after approving 6 | # any UI changes from the simulator 7 | # 8 | # Created by John Boiles on 10/19/10. 9 | 10 | TEST_APP_NAME="GHUnitTests" 11 | UI_TEST_IMAGES_DIR="$PWD/GHUnitTests/TestImages" 12 | 13 | # Find the most recent simulator install of the app 14 | SIM_INSTANCE_DIR=`find "/Users/$USER/Library/Application Support/iPhone Simulator" -type d -name "$TEST_APP_NAME.app" -print0 | xargs -0 ls -td | head -1` 15 | # Find the location of the documents for the app 16 | SIM_DOCUMENTS_DIR=`dirname "$SIM_INSTANCE_DIR"`/Documents/TestImages 17 | echo "Found simulator documents dir at $SIM_DOCUMENTS_DIR" 18 | 19 | # Create the images directory if not already created 20 | mkdir -p "$UI_TEST_IMAGES_DIR" 21 | 22 | if [[ -d "$SIM_DOCUMENTS_DIR" && $(ls -1A "$SIM_DOCUMENTS_DIR") ]]; then 23 | echo "Found the following files:" 24 | ls "$SIM_DOCUMENTS_DIR"/*.png 25 | # Copy any saved images from the app's documents to the test images folder 26 | echo "Saving images to $UI_TEST_IMAGES_DIR" 27 | cp "$SIM_DOCUMENTS_DIR"/*.png "$UI_TEST_IMAGES_DIR" 28 | else 29 | echo "No saved test images found" 30 | fi 31 | -------------------------------------------------------------------------------- /GHUnitTests/Scripts/LICENSE: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Gabriel Handford on 3/1/09. 3 | // Copyright 2009-2013. All rights reserved. 4 | // Modified by Felix Schulze on 2/11/13. 5 | // Copyright 2013. All rights reserved. 6 | // 7 | // Permission is hereby granted, free of charge, to any person 8 | // obtaining a copy of this software and associated documentation 9 | // files (the "Software"), to deal in the Software without 10 | // restriction, including without limitation the rights to use, 11 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the 13 | // Software is furnished to do so, subject to the following 14 | // conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be 17 | // included in all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 21 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 23 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 24 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 26 | // OTHER DEALINGS IN THE SOFTWARE. 27 | // -------------------------------------------------------------------------------- /GHUnitTests/Scripts/PrepareUITests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Script to copy saved UI test images to the product. Needs to be run as 4 | # part of the build process. 5 | # 6 | # Created by John Boiles on 10/19/10. 7 | 8 | UI_TEST_IMAGES_DIR="$SRCROOT/GHUnitTests/TestImages" 9 | 10 | if [ "$(ls -A $UI_TEST_IMAGES_DIR)" ]; then 11 | echo "Copying images from $UI_TEST_IMAGES_DIR to $TARGET_BUILD_DIR/$PRODUCT_NAME.app" 12 | cp -v "$UI_TEST_IMAGES_DIR"/*.png "$TARGET_BUILD_DIR/$PRODUCT_NAME.app/" 13 | else 14 | echo "No test images found in $UI_TEST_IMAGES_DIR" 15 | fi 16 | -------------------------------------------------------------------------------- /GHUnitTests/Scripts/RunIPhoneSecurityd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | set -u 5 | 6 | export DYLD_ROOT_PATH="$1" 7 | export IPHONE_SIMULATOR_ROOT="$1" 8 | export CFFIXED_USER_HOME="$2" 9 | 10 | "$IPHONE_SIMULATOR_ROOT"/usr/libexec/securityd 11 | -------------------------------------------------------------------------------- /GHUnitTests/Scripts/RunTests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # If we aren't running from the command line, then exit 4 | if [ "$GHUNIT_CLI" = "" ] && [ "$GHUNIT_AUTORUN" = "" ]; then 5 | exit 0 6 | fi 7 | 8 | export DYLD_ROOT_PATH="$SDKROOT" 9 | export DYLD_FRAMEWORK_PATH="$CONFIGURATION_BUILD_DIR" 10 | export IPHONE_SIMULATOR_ROOT="$SDKROOT" 11 | export CFFIXED_USER_HOME="$TEMP_FILES_DIR/iPhone Simulator User Dir" # Be compatible with google-toolbox-for-mac 12 | 13 | if [ -d $"CFFIXED_USER_HOME" ]; then 14 | rm -rf "$CFFIXED_USER_HOME" 15 | fi 16 | mkdir -p "$CFFIXED_USER_HOME" 17 | 18 | export NSDebugEnabled=YES 19 | export NSZombieEnabled=YES 20 | export NSDeallocateZombies=NO 21 | export NSHangOnUncaughtException=YES 22 | export NSAutoreleaseFreedObjectCheckEnabled=YES 23 | 24 | export DYLD_FRAMEWORK_PATH="$CONFIGURATION_BUILD_DIR" 25 | 26 | TEST_TARGET_EXECUTABLE_PATH="$TARGET_BUILD_DIR/$EXECUTABLE_PATH" 27 | 28 | if [ ! -e "$TEST_TARGET_EXECUTABLE_PATH" ]; then 29 | echo "" 30 | echo " ------------------------------------------------------------------------" 31 | echo " Missing executable path: " 32 | echo " $TEST_TARGET_EXECUTABLE_PATH." 33 | echo " The product may have failed to build or could have an old xcodebuild in your path (from 3.x instead of 4.x)." 34 | echo " ------------------------------------------------------------------------" 35 | echo "" 36 | exit 1 37 | fi 38 | 39 | # If trapping fails, make sure we kill any running securityd 40 | # TODO: Can we remove that code? Why is it used? 41 | #launchctl list | grep GHUNIT_RunIPhoneSecurityd && launchctl remove GHUNIT_RunIPhoneSecurityd 42 | #SCRIPTS_PATH=`cd $(dirname $0); pwd` 43 | #launchctl submit -l GHUNIT_RunIPhoneSecurityd -- "$SCRIPTS_PATH"/RunIPhoneSecurityd.sh $IPHONE_SIMULATOR_ROOT $CFFIXED_USER_HOME 44 | #trap "launchctl remove GHUNIT_RunIPhoneSecurityd" EXIT TERM INT 45 | 46 | RUN_CMD="\"$TEST_TARGET_EXECUTABLE_PATH\" -RegisterForSystemEvents" 47 | 48 | echo "Running: $RUN_CMD" 49 | set +o errexit # Disable exiting on error so script continues if tests fail 50 | eval $RUN_CMD 51 | RETVAL=$? 52 | set -o errexit 53 | 54 | unset DYLD_ROOT_PATH 55 | unset DYLD_FRAMEWORK_PATH 56 | unset IPHONE_SIMULATOR_ROOT 57 | 58 | if [ -n "$WRITE_JUNIT_XML" ]; then 59 | MY_TMPDIR=`/usr/bin/getconf DARWIN_USER_TEMP_DIR` 60 | RESULTS_DIR="${MY_TMPDIR}test-results" 61 | 62 | if [ -d "$RESULTS_DIR" ]; then 63 | `$CP -r "$RESULTS_DIR" "$BUILD_DIR" && rm -r "$RESULTS_DIR"` 64 | fi 65 | fi 66 | 67 | exit $RETVAL 68 | -------------------------------------------------------------------------------- /GHUnitTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /GHUnitTests/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | 9 | #import "AppDelegate.h" 10 | 11 | int main(int argc, char * argv[]) 12 | { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, @"GHUnitIOSAppDelegate"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /HelloTesting.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /HelloTesting.xcodeproj/xcshareddata/xcschemes/HelloTesting.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 53 | 59 | 60 | 61 | 63 | 69 | 70 | 71 | 73 | 79 | 80 | 81 | 83 | 89 | 90 | 91 | 92 | 93 | 99 | 100 | 101 | 102 | 103 | 104 | 114 | 116 | 122 | 123 | 124 | 125 | 126 | 127 | 133 | 135 | 141 | 142 | 143 | 144 | 146 | 147 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /HelloTesting.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /HelloTesting/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | 9 | @interface AppDelegate : UIResponder 10 | 11 | @property (strong, nonatomic) UIWindow *window; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /HelloTesting/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import "AppDelegate.h" 8 | 9 | @implementation AppDelegate 10 | 11 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 12 | { 13 | // Override point for customization after application launch. 14 | if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { 15 | UISplitViewController *splitViewController = (UISplitViewController *)self.window.rootViewController; 16 | UINavigationController *navigationController = [splitViewController.viewControllers lastObject]; 17 | splitViewController.delegate = (id)navigationController.topViewController; 18 | } 19 | return YES; 20 | } 21 | 22 | - (void)applicationWillResignActive:(UIApplication *)application 23 | { 24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 25 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application 29 | { 30 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 31 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 32 | } 33 | 34 | - (void)applicationWillEnterForeground:(UIApplication *)application 35 | { 36 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 37 | } 38 | 39 | - (void)applicationDidBecomeActive:(UIApplication *)application 40 | { 41 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 42 | } 43 | 44 | - (void)applicationWillTerminate:(UIApplication *)application 45 | { 46 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 47 | } 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /HelloTesting/Base.lproj/Main_iPad.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /HelloTesting/Base.lproj/Main_iPhone.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 46 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 95 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | -------------------------------------------------------------------------------- /HelloTesting/Customer.m: -------------------------------------------------------------------------------- 1 | // 2 | // Customer.m 3 | // HelloTesting 4 | // 5 | // Created by Koji Hasegawa on 2013/12/07. 6 | // Copyright (c) 2013年 Koji Hasegawa. All rights reserved. 7 | // 8 | 9 | #import "Customer.h" 10 | 11 | @implementation Customer 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /HelloTesting/CustomerDetailView.h: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | #import "Customer.h" 9 | #import "GravatarAccessor.h" 10 | 11 | @interface CustomerDetailView : UIView 12 | 13 | @property (weak, nonatomic) IBOutlet UILabel *nameLabel; 14 | @property (weak, nonatomic) IBOutlet UILabel *mailLabel; 15 | @property (weak, nonatomic) IBOutlet UILabel *genderLabel; 16 | @property (weak, nonatomic) IBOutlet UILabel *ageLabel; 17 | @property (weak, nonatomic) IBOutlet UILabel *marketDivisionLabel; 18 | @property (weak, nonatomic) IBOutlet UIImageView *avatarImage; 19 | 20 | /** 顧客情報をセットする */ 21 | - (void)setCustomer:(Customer*)customer; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /HelloTesting/CustomerDetailView.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import "CustomerDetailView.h" 8 | 9 | @implementation CustomerDetailView 10 | 11 | - (id)initWithFrame:(CGRect)frame 12 | { 13 | self = [super initWithFrame:frame]; 14 | if (self) { 15 | // Initialization code 16 | } 17 | return self; 18 | } 19 | 20 | /** 顧客情報をセットする */ 21 | - (void)setCustomer:(Customer*)customer{ 22 | self.nameLabel.text = customer.name; 23 | self.mailLabel.text = customer.mail; 24 | self.genderLabel.text = customer.genderString; 25 | self.ageLabel.text = [NSString stringWithFormat:@"%zd", customer.age]; 26 | self.marketDivisionLabel.text = customer.divisionString; 27 | 28 | GravatarAccessor *gravatar = [[GravatarAccessor alloc] initWithMail:customer.mail delegate:self]; 29 | [gravatar requestAvatar]; 30 | } 31 | 32 | 33 | #pragma mark - GravatarAvatarDelegate methods 34 | 35 | /** アバター取得完了 */ 36 | - (void)responseAvatar:(UIImage*)avatar{ 37 | if([NSThread isMainThread]){ 38 | [self.avatarImage setImage:avatar]; 39 | }else{ 40 | [self.avatarImage performSelectorOnMainThread:@selector(setImage:) withObject:avatar waitUntilDone:NO]; 41 | } 42 | } 43 | 44 | /** アバター取得失敗 */ 45 | - (void)responseError:(NSString*)message{ 46 | //nop 47 | } 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /HelloTesting/CustomerDetailView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 21 | 28 | 35 | 42 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /HelloTesting/CustomerPreview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

%@

9 | メール
%@

10 | 性別
%@

11 | 年齢
%@

12 | 区分
%@
13 | 14 | -------------------------------------------------------------------------------- /HelloTesting/DetailViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | #import "Customer.h" 9 | 10 | @interface DetailViewController : UIViewController 11 | 12 | @property (strong, nonatomic) Customer* detailItem; 13 | 14 | /** 氏名 */ 15 | @property (weak, nonatomic) IBOutlet UITextField *name; 16 | 17 | /** メールアドレス */ 18 | @property (weak, nonatomic) IBOutlet UITextField *mail; 19 | 20 | /** 性別 */ 21 | @property (weak, nonatomic) IBOutlet UISegmentedControl *gender; 22 | 23 | /** 年齢 */ 24 | @property (weak, nonatomic) IBOutlet UIPickerView *age; 25 | 26 | /** 性別の変更 */ 27 | - (IBAction)changeGender:(id)sender; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /HelloTesting/DetailViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import "DetailViewController.h" 8 | 9 | @interface DetailViewController () 10 | @property (strong, nonatomic) UIPopoverController *masterPopoverController; 11 | - (void)configureView; 12 | @end 13 | 14 | @implementation DetailViewController 15 | 16 | #pragma mark - Managing the detail item 17 | 18 | - (void)setDetailItem:(Customer*)newDetailItem 19 | { 20 | if (_detailItem != newDetailItem) { 21 | _detailItem = newDetailItem; 22 | 23 | // Update the view. 24 | [self configureView]; 25 | } 26 | 27 | if (self.masterPopoverController != nil) { 28 | [self.masterPopoverController dismissPopoverAnimated:YES]; 29 | } 30 | } 31 | 32 | - (void)configureView 33 | { 34 | // Update the user interface for the detail item. 35 | 36 | if (self.detailItem) { 37 | self.name.text = self.detailItem.name; 38 | self.mail.text = self.detailItem.mail; 39 | switch (self.detailItem.gender) { 40 | case GenderMale: 41 | [self.gender setSelectedSegmentIndex:0]; 42 | break; 43 | case GenderFemale: 44 | [self.gender setSelectedSegmentIndex:1]; 45 | break; 46 | } 47 | if(self.detailItem.age>=4){ 48 | [self.age selectRow:self.detailItem.age-4 inComponent:0 animated:NO]; 49 | } 50 | } 51 | } 52 | 53 | - (void)viewDidLoad 54 | { 55 | [super viewDidLoad]; 56 | // Do any additional setup after loading the view, typically from a nib. 57 | self.name.accessibilityIdentifier = @"name textfield"; 58 | self.name.clearButtonMode = UITextFieldViewModeWhileEditing; 59 | self.name.delegate = self; 60 | self.mail.accessibilityIdentifier = @"mail textfield"; 61 | self.mail.clearButtonMode = UITextFieldViewModeWhileEditing; 62 | self.mail.delegate = self; 63 | self.gender.accessibilityIdentifier = @"gender segmentedcontrol"; 64 | self.age.accessibilityIdentifier = @"age pickerview"; 65 | self.age.dataSource = self; 66 | self.age.delegate = self; 67 | [self configureView]; 68 | } 69 | 70 | - (void)didReceiveMemoryWarning 71 | { 72 | [super didReceiveMemoryWarning]; 73 | // Dispose of any resources that can be recreated. 74 | } 75 | 76 | - (void)viewWillDisappear:(BOOL)animated 77 | { 78 | //ツールによって、[UIPickerViewDelegate pickerView:didSelectRow:inComponent:]が呼ばれないものがあるため、 79 | //ここでdetailItemへの設定を行なう 80 | self.detailItem.age = [self.age selectedRowInComponent:0] + 4; 81 | } 82 | 83 | 84 | #pragma mark - UI events 85 | 86 | - (IBAction)changeGender:(id)sender{ 87 | if(self.gender.selectedSegmentIndex==0){ 88 | self.detailItem.gender = GenderMale; 89 | }else{ 90 | self.detailItem.gender = GenderFemale; 91 | } 92 | } 93 | 94 | 95 | #pragma mark - UITextFieldDelegate methods 96 | 97 | - (BOOL)textFieldShouldBeginEditing:(UITextField *)textField{ 98 | return YES; 99 | } 100 | 101 | - (BOOL)textFieldShouldEndEditing:(UITextField *)textField{ 102 | return YES; 103 | } 104 | 105 | - (void)textFieldDidEndEditing:(UITextField *)textField{ 106 | if(textField==self.name){ 107 | self.detailItem.name = textField.text; 108 | }else{ 109 | self.detailItem.mail = textField.text; 110 | } 111 | } 112 | 113 | - (BOOL)textFieldShouldClear:(UITextField *)textField{ 114 | return YES; 115 | } 116 | 117 | - (BOOL)textFieldShouldReturn:(UITextField *)textField{ 118 | if(textField==self.name){ 119 | self.detailItem.name = textField.text; 120 | }else{ 121 | self.detailItem.mail = textField.text; 122 | } 123 | [textField resignFirstResponder]; 124 | return YES; 125 | } 126 | 127 | 128 | #pragma mark - UIPickerViewDataSource methods 129 | 130 | - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{ 131 | return 1; 132 | } 133 | 134 | - (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{ 135 | return 120-3; 136 | } 137 | 138 | 139 | #pragma mark - UIPickerViewDelegate methods 140 | 141 | - (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component{ 142 | return [NSString stringWithFormat:@"%zd", row+4]; 143 | } 144 | 145 | - (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component{ 146 | self.detailItem.age = row+4; 147 | } 148 | 149 | 150 | #pragma mark - Split view 151 | 152 | - (void)splitViewController:(UISplitViewController *)splitController willHideViewController:(UIViewController *)viewController withBarButtonItem:(UIBarButtonItem *)barButtonItem forPopoverController:(UIPopoverController *)popoverController 153 | { 154 | barButtonItem.title = NSLocalizedString(@"Master", @"Master"); 155 | [self.navigationItem setLeftBarButtonItem:barButtonItem animated:YES]; 156 | self.masterPopoverController = popoverController; 157 | } 158 | 159 | - (void)splitViewController:(UISplitViewController *)splitController willShowViewController:(UIViewController *)viewController invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem 160 | { 161 | // Called when the view is shown again in the split view, invalidating the button and popover controller. 162 | [self.navigationItem setLeftBarButtonItem:nil animated:YES]; 163 | self.masterPopoverController = nil; 164 | } 165 | 166 | 167 | #pragma mark - segue 168 | 169 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender 170 | { 171 | if ([[segue identifier] isEqualToString:@"showPreview"]) { 172 | [[segue destinationViewController] setDetailItem:self.detailItem]; 173 | } 174 | } 175 | 176 | @end 177 | -------------------------------------------------------------------------------- /HelloTesting/HelloTesting-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIMainStoryboardFile 28 | Main_iPhone 29 | UIMainStoryboardFile~ipad 30 | Main_iPad 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UIStatusBarTintParameters 36 | 37 | UINavigationBar 38 | 39 | Style 40 | UIBarStyleDefault 41 | Translucent 42 | 43 | 44 | 45 | UISupportedInterfaceOrientations 46 | 47 | UIInterfaceOrientationPortrait 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | UISupportedInterfaceOrientations~ipad 52 | 53 | UIInterfaceOrientationPortrait 54 | UIInterfaceOrientationPortraitUpsideDown 55 | UIInterfaceOrientationLandscapeLeft 56 | UIInterfaceOrientationLandscapeRight 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /HelloTesting/HelloTesting-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #endif 17 | 18 | 19 | #define GRAVATAR_SERVER @"https://www.gravatar.com" 20 | -------------------------------------------------------------------------------- /HelloTesting/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "60x60", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "ipad", 20 | "size" : "29x29", 21 | "scale" : "1x" 22 | }, 23 | { 24 | "idiom" : "ipad", 25 | "size" : "29x29", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "ipad", 30 | "size" : "40x40", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "40x40", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "76x76", 41 | "scale" : "1x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "76x76", 46 | "scale" : "2x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /HelloTesting/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "orientation" : "portrait", 20 | "idiom" : "ipad", 21 | "extent" : "full-screen", 22 | "minimum-system-version" : "7.0", 23 | "scale" : "1x" 24 | }, 25 | { 26 | "orientation" : "landscape", 27 | "idiom" : "ipad", 28 | "extent" : "full-screen", 29 | "minimum-system-version" : "7.0", 30 | "scale" : "1x" 31 | }, 32 | { 33 | "orientation" : "portrait", 34 | "idiom" : "ipad", 35 | "extent" : "full-screen", 36 | "minimum-system-version" : "7.0", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "orientation" : "landscape", 41 | "idiom" : "ipad", 42 | "extent" : "full-screen", 43 | "minimum-system-version" : "7.0", 44 | "scale" : "2x" 45 | } 46 | ], 47 | "info" : { 48 | "version" : 1, 49 | "author" : "xcode" 50 | } 51 | } -------------------------------------------------------------------------------- /HelloTesting/MasterViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | 9 | @class DetailViewController; 10 | 11 | @interface MasterViewController : UITableViewController 12 | 13 | @property (strong, nonatomic) DetailViewController *detailViewController; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /HelloTesting/MasterViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import "MasterViewController.h" 8 | #import "DetailViewController.h" 9 | #import "Customer.h" 10 | 11 | @interface MasterViewController () { 12 | NSMutableArray *_objects; 13 | } 14 | @end 15 | 16 | @implementation MasterViewController 17 | 18 | - (void)awakeFromNib 19 | { 20 | if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { 21 | self.clearsSelectionOnViewWillAppear = NO; 22 | self.preferredContentSize = CGSizeMake(320.0, 600.0); 23 | } 24 | [super awakeFromNib]; 25 | } 26 | 27 | - (void)viewDidLoad 28 | { 29 | [super viewDidLoad]; 30 | // Do any additional setup after loading the view, typically from a nib. 31 | self.navigationItem.leftBarButtonItem = self.editButtonItem; 32 | 33 | UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(insertNewObject:)]; 34 | self.navigationItem.rightBarButtonItem = addButton; 35 | self.detailViewController = (DetailViewController *)[[self.splitViewController.viewControllers lastObject] topViewController]; 36 | } 37 | 38 | - (void)viewDidAppear:(BOOL)animated{ 39 | [self.tableView reloadData]; 40 | } 41 | 42 | - (void)didReceiveMemoryWarning 43 | { 44 | [super didReceiveMemoryWarning]; 45 | // Dispose of any resources that can be recreated. 46 | } 47 | 48 | - (void)insertNewObject:(id)sender 49 | { 50 | if (!_objects) { 51 | _objects = [[NSMutableArray alloc] init]; 52 | } 53 | [_objects insertObject:[[Customer alloc] init] atIndex:0]; 54 | NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0]; 55 | [self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; 56 | // [self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionNone]; 57 | 58 | //編集画面を直接表示 59 | Customer *object = _objects[indexPath.row]; 60 | self.detailViewController.detailItem = object; 61 | [self performSegueWithIdentifier:@"showDetail" sender:self]; 62 | } 63 | 64 | #pragma mark - Table View 65 | 66 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 67 | { 68 | return 1; 69 | } 70 | 71 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 72 | { 73 | return _objects.count; 74 | } 75 | 76 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 77 | { 78 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath]; 79 | 80 | Customer *object = _objects[indexPath.row]; 81 | cell.textLabel.text = object.name; 82 | cell.detailTextLabel.text = object.divisionString; 83 | 84 | return cell; 85 | } 86 | 87 | - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath 88 | { 89 | // Return NO if you do not want the specified item to be editable. 90 | return YES; 91 | } 92 | 93 | - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath 94 | { 95 | if (editingStyle == UITableViewCellEditingStyleDelete) { 96 | [_objects removeObjectAtIndex:indexPath.row]; 97 | [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade]; 98 | } else if (editingStyle == UITableViewCellEditingStyleInsert) { 99 | // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view. 100 | } 101 | } 102 | 103 | /* 104 | // Override to support rearranging the table view. 105 | - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath 106 | { 107 | } 108 | */ 109 | 110 | /* 111 | // Override to support conditional rearranging of the table view. 112 | - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath 113 | { 114 | // Return NO if you do not want the item to be re-orderable. 115 | return YES; 116 | } 117 | */ 118 | 119 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 120 | { 121 | if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { 122 | Customer *object = _objects[indexPath.row]; 123 | self.detailViewController.detailItem = object; 124 | } 125 | } 126 | 127 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender 128 | { 129 | if ([[segue identifier] isEqualToString:@"showDetail"]) { 130 | NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow]; 131 | Customer *object = _objects[indexPath.row]; 132 | [[segue destinationViewController] setDetailItem:object]; 133 | } 134 | } 135 | 136 | @end 137 | -------------------------------------------------------------------------------- /HelloTesting/PreviewViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | #import "Customer.h" 9 | 10 | @interface PreviewViewController : UIViewController 11 | 12 | @property (strong, nonatomic) Customer* detailItem; 13 | 14 | @property (weak, nonatomic) IBOutlet UIWebView *webView; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /HelloTesting/PreviewViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import "PreviewViewController.h" 8 | 9 | @interface PreviewViewController () 10 | @property (strong, nonatomic) UIPopoverController *masterPopoverController; 11 | - (void)configureView; 12 | @end 13 | 14 | @implementation PreviewViewController 15 | 16 | #pragma mark - Managing the detail item 17 | 18 | - (void)setDetailItem:(Customer*)newDetailItem 19 | { 20 | if (_detailItem != newDetailItem) { 21 | _detailItem = newDetailItem; 22 | 23 | // Update the view. 24 | [self configureView]; 25 | } 26 | 27 | if (self.masterPopoverController != nil) { 28 | [self.masterPopoverController dismissPopoverAnimated:YES]; 29 | } 30 | } 31 | 32 | - (void)configureView 33 | { 34 | // Update the user interface for the detail item. 35 | NSError *error = nil; 36 | 37 | NSString *filepath = [[NSBundle mainBundle] pathForResource:@"CustomerPreview" ofType:@"html"]; 38 | NSString *template = [NSString stringWithContentsOfFile:filepath encoding:NSUTF8StringEncoding error:&error]; 39 | if(!error){ 40 | NSString *name = self.detailItem.name; 41 | NSString *mail = self.detailItem.mail; 42 | NSString *gender = self.detailItem.genderString; 43 | NSString *age = [NSString stringWithFormat:@"%zd", self.detailItem.age]; 44 | NSString *division = self.detailItem.divisionString; 45 | NSString *body = [NSString stringWithFormat:template, name, mail, gender, age, division]; 46 | [self.webView loadHTMLString:body baseURL:nil]; 47 | 48 | }else{ 49 | NSLog(@"error domain:%@, code:%zd", error.domain, error.code); 50 | } 51 | 52 | if (self.detailItem) { 53 | //self.detailDescriptionLabel.text = [self.detailItem description]; 54 | } 55 | } 56 | 57 | - (void)viewDidLoad 58 | { 59 | [super viewDidLoad]; 60 | // Do any additional setup after loading the view, typically from a nib. 61 | [self configureView]; 62 | } 63 | 64 | - (void)didReceiveMemoryWarning 65 | { 66 | [super didReceiveMemoryWarning]; 67 | // Dispose of any resources that can be recreated. 68 | } 69 | 70 | #pragma mark - Split view 71 | 72 | - (void)splitViewController:(UISplitViewController *)splitController willHideViewController:(UIViewController *)viewController withBarButtonItem:(UIBarButtonItem *)barButtonItem forPopoverController:(UIPopoverController *)popoverController 73 | { 74 | barButtonItem.title = NSLocalizedString(@"Master", @"Master"); 75 | [self.navigationItem setLeftBarButtonItem:barButtonItem animated:YES]; 76 | self.masterPopoverController = popoverController; 77 | } 78 | 79 | - (void)splitViewController:(UISplitViewController *)splitController willShowViewController:(UIViewController *)viewController invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem 80 | { 81 | // Called when the view is shown again in the split view, invalidating the button and popover controller. 82 | [self.navigationItem setLeftBarButtonItem:nil animated:YES]; 83 | self.masterPopoverController = nil; 84 | } 85 | 86 | @end 87 | -------------------------------------------------------------------------------- /HelloTesting/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /HelloTesting/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | 9 | #import "AppDelegate.h" 10 | 11 | int main(int argc, char * argv[]) 12 | { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /HelloTesting/models/Customer.h: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | 9 | /** 10 | * 性別の型定義 11 | */ 12 | typedef NS_ENUM(NSUInteger, Gender){ 13 | GenderMale, //男性 14 | GenderFemale //女性 15 | }; 16 | 17 | /** 18 | * マーケティング区分の型定義 19 | */ 20 | typedef NS_ENUM(NSUInteger, Division){ 21 | DivisionM1, //M1層(男性20-34歳) 22 | DivisionM2, //M2層(男性35-49歳) 23 | DivisionM3, //M3層(男性50歳以上) 24 | DivisionF1, //F1層(女性20-34歳) 25 | DivisionF2, //F2層(女性35-49歳) 26 | DivisionF3, //F3層(女性50歳以上) 27 | DivisionC, //C層(男女4-12歳) 28 | DivisionT, //T層(男女13-19歳) 29 | DivisionNone //分類外 30 | }; 31 | 32 | /** 33 | * サンプルの顧客情報管理クラス 34 | */ 35 | @interface Customer : NSObject 36 | 37 | /** 顧客名 */ 38 | @property(nonatomic, copy) NSString *name; 39 | 40 | /** メールアドレス */ 41 | @property(nonatomic, copy) NSString *mail; 42 | 43 | /** 性別 */ 44 | @property(nonatomic, assign) Gender gender; 45 | 46 | /** 年齢 */ 47 | @property(nonatomic, assign) NSUInteger age; 48 | 49 | /** 50 | * マーケティング区分を返す 51 | * @retuen 該当するマーケティング区分 52 | */ 53 | - (Division)division; 54 | 55 | /** 56 | * 引数のマーケティング区分に一致する顧客か否かをBOOL型で返す 57 | * @param division マーケティング区分 58 | * @return YES:この顧客が引数のマーケティング区分に一致する 59 | */ 60 | - (BOOL)isInDivision:(Division)division; 61 | 62 | /** 63 | * 性別を文字列で返す 64 | */ 65 | - (NSString*)genderString; 66 | 67 | /** 68 | * マーケティング区分を文字列で返す 69 | */ 70 | - (NSString*)divisionString; 71 | 72 | @end 73 | -------------------------------------------------------------------------------- /HelloTesting/models/Customer.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import "Customer.h" 8 | 9 | @implementation Customer 10 | 11 | /** 12 | * マーケティング区分を返す 13 | * @retuen 該当するマーケティング区分 14 | */ 15 | - (Division)division 16 | { 17 | if(self.age<=3){ 18 | return DivisionNone; 19 | 20 | }else if(self.age<=12){ 21 | return DivisionC; 22 | 23 | }else if(self.age<=19){ 24 | return DivisionT; 25 | 26 | }else if(self.age<=34){ 27 | if(self.gender==GenderFemale){ 28 | return DivisionF1; 29 | }else{ 30 | return DivisionM1; 31 | } 32 | 33 | }else if(self.age<=49){ 34 | if(self.gender==GenderFemale){ 35 | return DivisionF2; 36 | }else{ 37 | return DivisionM2; 38 | } 39 | 40 | }else{ 41 | if(self.gender==GenderFemale){ 42 | return DivisionF3; 43 | }else{ 44 | return DivisionM3; 45 | } 46 | } 47 | } 48 | 49 | /** 50 | * 引数のマーケティング区分に一致する顧客か否かをBOOL型で返す 51 | * @param division マーケティング区分 52 | * @return YES:この顧客が引数のマーケティング区分に一致する 53 | */ 54 | - (BOOL)isInDivision:(Division)division 55 | { 56 | return division==[self division]; 57 | } 58 | 59 | /** 60 | * 性別を文字列で返す 61 | */ 62 | - (NSString*)genderString{ 63 | switch (self.gender) { 64 | case GenderMale: 65 | return @"男性"; 66 | case GenderFemale: 67 | return @"女性"; 68 | } 69 | } 70 | 71 | /** 72 | * マーケティング区分を文字列で返す 73 | */ 74 | - (NSString*)divisionString{ 75 | switch ([self division]) { 76 | case DivisionM1: 77 | return @"M1層"; 78 | case DivisionM2: 79 | return @"M2層"; 80 | case DivisionM3: 81 | return @"M3層"; 82 | case DivisionF1: 83 | return @"F1層"; 84 | case DivisionF2: 85 | return @"F2層"; 86 | case DivisionF3: 87 | return @"F3層"; 88 | case DivisionC: 89 | return @"C層"; 90 | case DivisionT: 91 | return @"T層"; 92 | case DivisionNone: 93 | return @"分類外"; 94 | } 95 | } 96 | 97 | @end 98 | -------------------------------------------------------------------------------- /HelloTesting/models/Customer2.h: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import "Customer.h" 8 | 9 | /** 10 | * サンプルの顧客情報管理クラス 11 | * 年齢を直接プロパティに持たず、生年月日プロパティから都度計算するサブクラス 12 | */ 13 | @interface Customer2 : Customer 14 | 15 | /** 生年月日 */ 16 | @property(nonatomic, assign) NSDate *birth; 17 | 18 | /** 生年月日を文字列で設定するラッパー */ 19 | - (void)setBirthWithString:(NSString*)dateString; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /HelloTesting/models/Customer2.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import "Customer2.h" 8 | 9 | /** Customer2の非公開フィールド/メソッド定義カテゴリ */ 10 | @interface Customer2() 11 | 12 | /** インスタンス生成日時(age算出に使用する) */ 13 | @property(nonatomic, copy) NSDate *currentDate; 14 | 15 | @end 16 | 17 | 18 | @implementation Customer2 19 | 20 | //ageのアクセサをオーバーライドする 21 | @dynamic age; 22 | 23 | /** initで生成日時をインスタンスフィールドに保持する */ 24 | - (id)init{ 25 | self = [super init]; 26 | if(self){ 27 | self.currentDate = [NSDate date]; 28 | } 29 | return self; 30 | } 31 | 32 | /** 年齢は生年月日から算出する */ 33 | - (NSUInteger)age{ 34 | NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 35 | dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"ja_JP"]; 36 | dateFormatter.timeZone = [NSTimeZone timeZoneWithName:@"JST"]; 37 | dateFormatter.dateFormat = @"yyyyMMdd"; 38 | 39 | NSUInteger to = [[dateFormatter stringFromDate:self.currentDate] intValue]; 40 | NSUInteger from = [[dateFormatter stringFromDate:self.birth] intValue]; 41 | if(to>from){ 42 | return (to-from)/10000; 43 | }else{ 44 | return 0; 45 | } 46 | } 47 | 48 | - (void)setAge:(NSUInteger)age{ 49 | super.age = age; 50 | } 51 | 52 | /** 生年月日を文字列で設定するラッパー */ 53 | - (void)setBirthWithString:(NSString*)dateString{ 54 | NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 55 | dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"ja_JP"]; 56 | dateFormatter.timeZone = [NSTimeZone timeZoneWithName:@"JST"]; 57 | dateFormatter.dateFormat = @"yyyy/M/d"; 58 | 59 | self.birth = [dateFormatter dateFromString:dateString]; 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /HelloTesting/models/GravatarAccessor.h: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | 9 | /** 10 | * アバター画像の取得通知を受けるデリゲート 11 | */ 12 | @protocol GravatarAvatarDelegate 13 | 14 | /** 15 | * gravatar.comからのアバター画像取得完了通知 16 | * @param avatar 取得したアバター画像 17 | */ 18 | - (void)responseAvatar:(UIImage*)avatar; 19 | 20 | /** 21 | * gravatar.comからのアバター画像取得失敗通知 22 | * @param message エラー内容(レスポンスのステータスコードなど) 23 | */ 24 | - (void)responseError:(NSString*)message; 25 | 26 | @end 27 | 28 | 29 | /** 30 | * gravatar.comにアクセスしてemailに対応するアバター画像を取得・管理するクラス 31 | */ 32 | @interface GravatarAccessor : NSObject 33 | 34 | /** 35 | * アバター取得対象メールアドレスと通知先デリゲートを指定して初期化 36 | */ 37 | - (id)initWithMail:(NSString*)mail delegate:(id)delegate; 38 | 39 | /** 40 | * アバター取得をリクエストする(非同期で取得し、デリゲートに通知する) 41 | */ 42 | - (void)requestAvatar; 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /HelloTesting/models/GravatarAccessor.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import "GravatarAccessor.h" 8 | #import 9 | 10 | /** GravatarAccessorの非公開フィールド/メソッド定義カテゴリ */ 11 | @interface GravatarAccessor() 12 | 13 | /** gravatar.comとの通信用コネクション */ 14 | @property(nonatomic, strong) NSURLConnection *connection; 15 | 16 | /** 通知先デリゲート */ 17 | @property(nonatomic, weak) id delegate; 18 | 19 | /** ステータスコード */ 20 | @property(nonatomic, assign) NSInteger statusCode; 21 | 22 | /** MIMEType */ 23 | @property(nonatomic, copy) NSString *MIMEType; 24 | 25 | /** レスポンス本体を蓄積する */ 26 | @property(nonatomic, strong) NSMutableData *responseAccumulator; 27 | 28 | /** 文字列をMD5ハッシュ化して返す非公開メソッド */ 29 | - (NSString*)md5from:(NSString*)string; 30 | 31 | @end 32 | 33 | 34 | @implementation GravatarAccessor 35 | 36 | /** 37 | * アバター取得対象メールアドレスと通知先デリゲートを指定して初期化 38 | */ 39 | - (id)initWithMail:(NSString*)mail delegate:(id)delegate{ 40 | self = [super init]; 41 | if(self){ 42 | NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@/avatar/%@", GRAVATAR_SERVER, [self md5from:mail]]]; 43 | NSURLRequest *req = [NSURLRequest requestWithURL:url]; 44 | self.connection = [NSURLConnection connectionWithRequest:req delegate:self]; 45 | self.delegate = delegate; 46 | self.responseAccumulator = [[NSMutableData alloc] init]; 47 | } 48 | return self; 49 | } 50 | 51 | /** 52 | * アバター取得をリクエストする(非同期で取得し、デリゲートに通知する) 53 | */ 54 | - (void)requestAvatar{ 55 | [self.connection start]; 56 | } 57 | 58 | 59 | #pragma mark - NSURLConnectionDataDelegate methods 60 | 61 | - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{ 62 | self.statusCode = ((NSHTTPURLResponse*)response).statusCode; 63 | self.MIMEType = response.MIMEType; 64 | } 65 | 66 | - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{ 67 | [self.responseAccumulator appendData:data]; 68 | } 69 | 70 | - (void)connectionDidFinishLoading:(NSURLConnection *)connection{ 71 | if(self.statusCode!=200){ 72 | NSString *message = [NSString stringWithFormat:@"Bad statusCode:%zd", self.statusCode]; 73 | [self.delegate responseError:message]; 74 | 75 | }else if(!([self.MIMEType isEqual:@"image/png"] 76 | || [self.MIMEType isEqual:@"image/jpeg"])){ 77 | NSString *message = [NSString stringWithFormat:@"Bad content-type:%@", self.MIMEType]; 78 | [self.delegate responseError:message]; 79 | 80 | }else{ 81 | UIImage *avatar = [UIImage imageWithData:self.responseAccumulator]; 82 | [self.delegate responseAvatar:avatar]; 83 | } 84 | } 85 | 86 | - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{ 87 | NSString *message = [NSString stringWithFormat:@"Error! domain:%@, code:%zd", error.domain, error.code]; 88 | [self.delegate responseError:message]; 89 | } 90 | 91 | 92 | #pragma mark - private methods 93 | 94 | /** 文字列をMD5ハッシュ化して返す非公開メソッド */ 95 | - (NSString*)md5from:(NSString*)string{ 96 | const char *ptr = [string UTF8String]; 97 | unsigned char md5Buffer[CC_MD5_DIGEST_LENGTH]; 98 | CC_MD5(ptr, (CC_LONG)strlen(ptr), md5Buffer); 99 | 100 | NSMutableString *md5 = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH*2]; 101 | for(int i=0; i 8 | #import "Customer2.h" 9 | 10 | /** 非公開プロパティにアクセスするためのカテゴリ */ 11 | @interface Customer2() 12 | 13 | /** 当クラスインスタンス生成日時(age算出に使用する) */ 14 | @property(nonatomic, copy) NSDate *currentDate; 15 | 16 | @end 17 | 18 | 19 | /** Customer2テストクラス */ 20 | @interface Customer2Test : XCTestCase 21 | 22 | @end 23 | 24 | @implementation Customer2Test{ 25 | /** テスト対象オブジェクト */ 26 | Customer2 *_sut; 27 | } 28 | 29 | - (void)setUp{ 30 | [super setUp]; 31 | 32 | NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 33 | dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"ja_JP"]; 34 | dateFormatter.timeZone = [NSTimeZone timeZoneWithName:@"JST"]; 35 | dateFormatter.dateFormat = @"yyyy/M/d"; 36 | 37 | _sut = [[Customer2 alloc] init]; 38 | _sut.currentDate = [dateFormatter dateFromString:@"2014/3/1"]; 39 | } 40 | 41 | - (void)testAge_0{ 42 | [_sut setBirthWithString:@"2013/3/2"]; 43 | XCTAssertEqual(0U, _sut.age); //ageはunsignedなので、expectedにもUを付加 44 | 45 | [_sut setBirthWithString:@"2014/3/1"]; 46 | XCTAssertEqual(0U, _sut.age); 47 | 48 | [_sut setBirthWithString:@"2014/3/2"]; 49 | XCTAssertEqual(0U, _sut.age); 50 | } 51 | 52 | - (void)testAge_1{ 53 | [_sut setBirthWithString:@"2013/3/1"]; 54 | XCTAssertEqual(1U, _sut.age); 55 | 56 | [_sut setBirthWithString:@"2012/3/2"]; 57 | XCTAssertEqual(1U, _sut.age); 58 | 59 | [_sut setBirthWithString:@"2012/3/1"]; 60 | XCTAssertEqual(2U, _sut.age); 61 | } 62 | 63 | - (void)testAge_17{ 64 | [_sut setBirthWithString:@"1997/3/2"]; 65 | XCTAssertEqual(16U, _sut.age); 66 | 67 | [_sut setBirthWithString:@"1997/3/1"]; 68 | XCTAssertEqual(17U, _sut.age); 69 | 70 | [_sut setBirthWithString:@"1996/3/2"]; 71 | XCTAssertEqual(17U, _sut.age); 72 | 73 | [_sut setBirthWithString:@"1996/3/1"]; 74 | XCTAssertEqual(18U, _sut.age); 75 | } 76 | 77 | - (void)testAge_100{ 78 | [_sut setBirthWithString:@"1914/3/2"]; 79 | XCTAssertEqual(99U, _sut.age); 80 | 81 | [_sut setBirthWithString:@"1914/3/1"]; 82 | XCTAssertEqual(100U, _sut.age); 83 | 84 | [_sut setBirthWithString:@"1913/3/2"]; 85 | XCTAssertEqual(100U, _sut.age); 86 | 87 | [_sut setBirthWithString:@"1913/3/1"]; 88 | XCTAssertEqual(101U, _sut.age); 89 | } 90 | 91 | @end 92 | -------------------------------------------------------------------------------- /HelloTestingTests/CustomerTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | #import "Customer.h" 9 | 10 | /** Customerテストクラス */ 11 | @interface CustomerTest : XCTestCase 12 | 13 | @end 14 | 15 | @implementation CustomerTest 16 | { 17 | /** テスト対象オブジェクト */ 18 | Customer *_sut; 19 | } 20 | 21 | - (void)setUp 22 | { 23 | [super setUp]; 24 | _sut = [[Customer alloc] init]; 25 | } 26 | 27 | - (void)tearDown 28 | { 29 | [super tearDown]; 30 | } 31 | 32 | /** C層となるケース */ 33 | - (void)testDivision_C 34 | { 35 | _sut.gender = GenderMale; 36 | _sut.age = 4; 37 | XCTAssertEqual(DivisionC, [_sut division], @"C層であること"); 38 | 39 | _sut.age = 12; 40 | XCTAssertEqual(DivisionC, [_sut division], @"C層であること"); 41 | 42 | _sut.gender = GenderFemale; 43 | _sut.age = 4; 44 | XCTAssertEqual(DivisionC, [_sut division], @"C層であること"); 45 | 46 | _sut.age = 12; 47 | XCTAssertEqual(DivisionC, [_sut division], @"C層であること"); 48 | } 49 | 50 | /** T層となるケース */ 51 | - (void)testDivision_T 52 | { 53 | _sut.gender = GenderMale; 54 | _sut.age = 13; 55 | XCTAssertEqual(DivisionT, [_sut division], @"T層であること"); 56 | 57 | _sut.age = 19; 58 | XCTAssertEqual(DivisionT, [_sut division], @"T層であること"); 59 | 60 | _sut.gender = GenderFemale; 61 | _sut.age = 13; 62 | XCTAssertEqual(DivisionT, [_sut division], @"T層であること"); 63 | 64 | _sut.age = 19; 65 | XCTAssertEqual(DivisionT, [_sut division], @"T層であること"); 66 | } 67 | 68 | /** M1層となるケース */ 69 | - (void)testDivision_M1 70 | { 71 | _sut.gender = GenderMale; 72 | _sut.age = 20; 73 | XCTAssertEqual(DivisionM1, [_sut division], @"M1層であること"); 74 | 75 | _sut.age = 34; 76 | XCTAssertEqual(DivisionM1, [_sut division], @"M1層であること"); 77 | } 78 | 79 | /** M2層となるケース */ 80 | - (void)testDivision_M2 81 | { 82 | _sut.gender = GenderMale; 83 | _sut.age = 35; 84 | XCTAssertEqual(DivisionM2, [_sut division], @"M2層であること"); 85 | 86 | _sut.age = 49; 87 | XCTAssertEqual(DivisionM2, [_sut division], @"M2層であること"); 88 | } 89 | 90 | /** M3層となるケース */ 91 | - (void)testDivision_M3 92 | { 93 | _sut.gender = GenderMale; 94 | _sut.age = 50; 95 | XCTAssertEqual(DivisionM3, [_sut division], @"M3層であること"); 96 | 97 | _sut.age = 51; 98 | XCTAssertEqual(DivisionM3, [_sut division], @"M3層であること"); 99 | } 100 | 101 | /** F1層となるケース */ 102 | - (void)testDivision_F1 103 | { 104 | _sut.gender = GenderFemale; 105 | _sut.age = 20; 106 | XCTAssertEqual(DivisionF1, [_sut division], @"F1層であること"); 107 | 108 | _sut.age = 34; 109 | XCTAssertEqual(DivisionF1, [_sut division], @"F1層であること"); 110 | } 111 | 112 | /** F2層となるケース */ 113 | - (void)testDivision_F2 114 | { 115 | _sut.gender = GenderFemale; 116 | _sut.age = 35; 117 | XCTAssertEqual(DivisionF2, [_sut division], @"F2層であること"); 118 | 119 | _sut.age = 49; 120 | XCTAssertEqual(DivisionF2, [_sut division], @"F2層であること"); 121 | } 122 | 123 | /** F3層となるケース */ 124 | - (void)testDivision_F3 125 | { 126 | _sut.gender = GenderFemale; 127 | _sut.age = 50; 128 | XCTAssertEqual(DivisionF3, [_sut division], @"F3層であること"); 129 | 130 | _sut.age = 51; 131 | XCTAssertEqual(DivisionF3, [_sut division], @"F3層であること"); 132 | } 133 | 134 | /** 分類外となるケース */ 135 | - (void)testDivision_None 136 | { 137 | _sut.gender = GenderMale; 138 | _sut.age = 3; 139 | XCTAssertEqual(DivisionNone, [_sut division], @"分類外であること"); 140 | 141 | _sut.gender = GenderFemale; 142 | _sut.age = 3; 143 | XCTAssertEqual(DivisionNone, [_sut division], @"分類外であること"); 144 | } 145 | 146 | @end 147 | -------------------------------------------------------------------------------- /HelloTestingTests/CustomerTestExtracted.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | #import "Customer.h" 9 | 10 | /** 11 | * Customerテストクラスの例2(共通部分を抽出してリファクタリングしたパターン) 12 | */ 13 | @interface CustomerTestExtracted : XCTestCase 14 | 15 | /** GenderおよびAgeを設定し、divisionを求める処理を抽出したメソッド */ 16 | //- (void)divisionWithGender:(Gender)gender age:(NSUInteger)age; 17 | 18 | @end 19 | 20 | @implementation CustomerTestExtracted{ 21 | /** テスト対象オブジェクト */ 22 | Customer *_sut; 23 | } 24 | 25 | - (void)setUp{ 26 | [super setUp]; 27 | _sut = [[Customer alloc] init]; 28 | } 29 | 30 | - (void)tearDown{ 31 | [super tearDown]; 32 | } 33 | 34 | /** C層となるケース */ 35 | - (void)testDivision_C{ 36 | XCTAssertEqual(DivisionC, [self divisionWithGender:GenderMale age:4]); 37 | XCTAssertEqual(DivisionC, [self divisionWithGender:GenderMale age:12]); 38 | XCTAssertEqual(DivisionC, [self divisionWithGender:GenderFemale age:4]); 39 | XCTAssertEqual(DivisionC, [self divisionWithGender:GenderFemale age:12]); 40 | } 41 | 42 | /** T層となるケース */ 43 | - (void)testDivision_T{ 44 | XCTAssertEqual(DivisionT, [self divisionWithGender:GenderMale age:13]); 45 | XCTAssertEqual(DivisionT, [self divisionWithGender:GenderMale age:19]); 46 | XCTAssertEqual(DivisionT, [self divisionWithGender:GenderFemale age:13]); 47 | XCTAssertEqual(DivisionT, [self divisionWithGender:GenderFemale age:19]); 48 | } 49 | 50 | /** M1層となるケース */ 51 | - (void)testDivision_M1{ 52 | XCTAssertEqual(DivisionM1, [self divisionWithGender:GenderMale age:20]); 53 | XCTAssertEqual(DivisionM1, [self divisionWithGender:GenderMale age:34]); 54 | } 55 | 56 | /** M2層となるケース */ 57 | - (void)testDivision_M2{ 58 | XCTAssertEqual(DivisionM2, [self divisionWithGender:GenderMale age:35]); 59 | XCTAssertEqual(DivisionM2, [self divisionWithGender:GenderMale age:49]); 60 | } 61 | 62 | /** M3層となるケース */ 63 | - (void)testDivision_M3{ 64 | XCTAssertEqual(DivisionM3, [self divisionWithGender:GenderMale age:50]); 65 | XCTAssertEqual(DivisionM3, [self divisionWithGender:GenderMale age:51]); 66 | } 67 | 68 | /** F1層となるケース */ 69 | - (void)testDivision_F1{ 70 | XCTAssertEqual(DivisionF1, [self divisionWithGender:GenderFemale age:20]); 71 | XCTAssertEqual(DivisionF1, [self divisionWithGender:GenderFemale age:34]); 72 | } 73 | 74 | /** F2層となるケース */ 75 | - (void)testDivision_F2{ 76 | XCTAssertEqual(DivisionF2, [self divisionWithGender:GenderFemale age:35]); 77 | XCTAssertEqual(DivisionF2, [self divisionWithGender:GenderFemale age:49]); 78 | } 79 | 80 | /** F3層となるケース */ 81 | - (void)testDivision_F3{ 82 | XCTAssertEqual(DivisionF3, [self divisionWithGender:GenderFemale age:50]); 83 | XCTAssertEqual(DivisionF3, [self divisionWithGender:GenderFemale age:51]); 84 | } 85 | 86 | /** 分類外となるケース */ 87 | - (void)testDivision_None{ 88 | XCTAssertEqual(DivisionNone, [self divisionWithGender:GenderMale age:3]); 89 | XCTAssertEqual(DivisionNone, [self divisionWithGender:GenderFemale age:3]); 90 | } 91 | 92 | 93 | 94 | #pragma mark - 95 | 96 | /** GenderおよびAgeを設定し、divisionを求める処理を抽出したメソッド */ 97 | - (Division)divisionWithGender:(Gender)gender age:(NSUInteger)age{ 98 | _sut.gender = gender; 99 | _sut.age = age; 100 | return [_sut division]; 101 | } 102 | 103 | @end 104 | -------------------------------------------------------------------------------- /HelloTestingTests/GravatarAccessorTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | #import "GravatarAccessor.h" 9 | 10 | /** 非公開メソッドをテストするためのカテゴリ */ 11 | @interface GravatarAccessor() 12 | 13 | /** 文字列をMD5ハッシュ化して返す非公開メソッド */ 14 | - (NSString*)md5from:(NSString*)string; 15 | 16 | @end 17 | 18 | 19 | /** GravatarAccessorテストクラス */ 20 | @interface GravatarAccessorTest : XCTestCase 21 | 22 | @end 23 | 24 | @implementation GravatarAccessorTest{ 25 | /** テスト対象オブジェクト */ 26 | GravatarAccessor *_sut; 27 | } 28 | 29 | - (void)setUp{ 30 | [super setUp]; 31 | _sut = [[GravatarAccessor alloc] init]; 32 | } 33 | 34 | - (void)testMd5from{ 35 | NSString *expected = @"0bc83cb571cd1c50ba6f3e8a78ef1346"; 36 | NSString *actual = [_sut md5from:@"myemailaddress@example.com"]; 37 | XCTAssertEqualObjects(expected, actual); 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /HelloTestingTests/HelloTestingTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /HelloTestingTests/XCTAssertionTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | 9 | /** 検証に使用する例外クラス定義 */ 10 | @interface OriginalException : NSException 11 | @end 12 | @implementation OriginalException 13 | @end 14 | 15 | 16 | /** XCTAssertの動作サンプル */ 17 | @interface XCTAssertionTest : XCTestCase 18 | 19 | @end 20 | 21 | @implementation XCTAssertionTest 22 | 23 | - (void)testAssertNil 24 | { 25 | NSObject *sutNil = nil; 26 | NSObject *sutNotNil = [[NSObject alloc] init]; 27 | 28 | XCTAssertNil(sutNil); 29 | // XCTAssertNil(sutNotNil, @"このテストは失敗します"); 30 | 31 | XCTAssertNotNil(sutNotNil); 32 | // XCTAssertNotNil(sutNil, @"このテストは失敗します"); 33 | } 34 | 35 | - (void)testAssertTrue 36 | { 37 | XCTAssert(true); 38 | // XCTAssert(false, @"このテストは失敗します"); 39 | 40 | XCTAssertTrue(true); 41 | // XCTAssertTrue(false, @"このテストは失敗します"); 42 | 43 | XCTAssertFalse(false); 44 | // XCTAssertFalse(true, @"このテストは失敗します"); 45 | } 46 | 47 | - (void)testAssertEqualObjects 48 | { 49 | NSString *a1 = @"abcdef"; 50 | NSString *a2same = @"abcdef"; 51 | NSString *a2notSame = @"012345"; 52 | 53 | XCTAssertEqualObjects(a1, a2same); 54 | // XCTAssertEqualObjects(a1, a2notSame, @"このテストは失敗します"); 55 | 56 | XCTAssertNotEqualObjects(a1, a2notSame); 57 | // XCTAssertNotEqualObjects(a1, a2same, @"このテストは失敗します"); 58 | } 59 | 60 | - (void)testAssertEqual 61 | { 62 | NSInteger a1 = 1000; 63 | NSInteger a2same = 1000; 64 | NSInteger a2notSame = 9999; 65 | 66 | XCTAssertEqual(a1, a2same); 67 | // XCTAssertEqual(a1, a2notSame, @"このテストは失敗します"); 68 | 69 | XCTAssertNotEqual(a1, a2notSame); 70 | // XCTAssertNotEqual(a1, a2same, @"このテストは失敗します"); 71 | } 72 | 73 | - (void)testAssertEqualWithAccuracy 74 | { 75 | double a1 = 0.021; 76 | double a2 = 0.071 - 0.05; 77 | 78 | XCTAssertEqualWithAccuracy(a1, a2, 0.0001, @"丸め誤差が出ても成功します"); 79 | // XCTAssertEqual(a1, a2, @"このテストは失敗します"); 80 | } 81 | 82 | - (void)testAssertThrows 83 | { 84 | XCTAssertThrows([@"abcdef" substringWithRange:NSMakeRange(5,9)]); 85 | // XCTAssertThrows([@"abcdef" substringWithRange:NSMakeRange(5,1)], @"このテストは失敗します"); 86 | } 87 | 88 | - (void)testAssertThrowsSpecific 89 | { 90 | XCTAssertThrowsSpecific([@"abcdef" substringWithRange:NSMakeRange(5,9)], NSException); 91 | // XCTAssertThrowsSpecific([@"abcdef" substringWithRange:NSMakeRange(5,9)], OriginalException, @"このテストは失敗します"); 92 | // XCTAssertThrowsSpecific([@"abcdef" substringWithRange:NSMakeRange(5,1)], NSException, @"このテストは失敗します"); 93 | } 94 | 95 | - (void)testAssertThrowsSpecificNamed 96 | { 97 | XCTAssertThrowsSpecificNamed([@"abcdef" substringWithRange:NSMakeRange(5,9)], NSException, NSRangeException); 98 | // XCTAssertThrowsSpecificNamed([@"abcdef" substringWithRange:NSMakeRange(5,9)], NSException, NSInvalidArgumentException, @"このテストは失敗します"); 99 | // XCTAssertThrowsSpecificNamed([@"abcdef" substringWithRange:NSMakeRange(5,1)], NSException, NSRangeException, @"このテストは失敗します"); 100 | } 101 | 102 | - (void)testAssertNoThrow 103 | { 104 | // XCTAssertNoThrow([@"abcdef" substringWithRange:NSMakeRange(5,9)], @"このテストは失敗します"); 105 | XCTAssertNoThrow([@"abcdef" substringWithRange:NSMakeRange(5,1)]); 106 | } 107 | 108 | - (void)testAssertNoThrowsSpecific 109 | { 110 | // XCTAssertNoThrowSpecific([@"abcdef" substringWithRange:NSMakeRange(5,9)], NSException, @"このテストは失敗します"); 111 | XCTAssertNoThrowSpecific([@"abcdef" substringWithRange:NSMakeRange(5,9)], OriginalException); 112 | XCTAssertNoThrowSpecific([@"abcdef" substringWithRange:NSMakeRange(5,1)], NSException); 113 | } 114 | 115 | - (void)testAssertNoThrowsSpecificNamed 116 | { 117 | // XCTAssertNoThrowSpecificNamed([@"abcdef" substringWithRange:NSMakeRange(5,9)], NSException, NSRangeException, @"このテストは失敗します"); 118 | XCTAssertNoThrowSpecificNamed([@"abcdef" substringWithRange:NSMakeRange(5,9)], NSException, NSInvalidArgumentException); 119 | XCTAssertNoThrowSpecificNamed([@"abcdef" substringWithRange:NSMakeRange(5,1)], NSException, NSRangeException); 120 | } 121 | 122 | @end 123 | -------------------------------------------------------------------------------- /HelloTestingTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /KIFTests/AddFirstCustomer.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | //#import "KIFUITestActor+EXAdditions.h" 9 | 10 | @interface AddFirstCustomer : KIFTestCase 11 | 12 | @end 13 | 14 | @implementation AddFirstCustomer 15 | 16 | - (void)beforeEach 17 | { 18 | //"Add"ボタンをタップすると顧客を1件追加し、編集画面に遷移する 19 | [tester tapViewWithAccessibilityLabel:@"Add"]; 20 | } 21 | 22 | - (void)afterEach 23 | { 24 | // 25 | } 26 | 27 | - (void)testAddFirstCustomer 28 | { 29 | //名前を設定 30 | [tester enterText:@"Newton Geizler" intoViewWithAccessibilityLabel:@"name"]; 31 | 32 | //男性・35歳に設定 33 | [tester tapViewWithAccessibilityLabel:@"男性"]; 34 | [tester selectPickerViewRowWithTitle:@"35"]; 35 | 36 | //一覧に戻る 37 | [tester tapViewWithAccessibilityLabel:@"Master"]; 38 | 39 | //マーケティング区分はM2層となること 40 | [tester waitForTappableViewWithAccessibilityLabel:@"Newton Geizler, M2層"]; 41 | } 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /KIFTests/KIFTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /KIFTests/KIFTests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #import 10 | #endif 11 | -------------------------------------------------------------------------------- /KIFTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /KiwiTests/CustomerDetailViewSpec.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | #import "Kiwi.h" 9 | #import "CustomerDetailView.h" 10 | 11 | /** CustomerDetailViewテストクラス(Kiwiによるテストスタブのサンプル) */ 12 | SPEC_BEGIN(CustomerDetailViewSpec) 13 | 14 | describe(@"CustomerDetailView test using stub", ^{ 15 | __block CustomerDetailView *sut; 16 | 17 | beforeEach(^{ 18 | UINib* nib = [UINib nibWithNibName:@"CustomerDetailView" bundle:nil]; 19 | NSArray* array = [nib instantiateWithOwner:nil options:nil]; 20 | sut = [array objectAtIndex:0]; 21 | }); 22 | 23 | it(@"use normally stub", ^{ 24 | //Test Stubを準備する 25 | Customer *customer = [[Customer alloc] init]; 26 | customer.name = @"岩田 貫一"; 27 | [customer stub:@selector(mail) andReturn:@"myemailaddress@example.com"]; 28 | [customer stub:@selector(age) andReturn:theValue(33U)]; 29 | [customer stub:@selector(genderString) andReturn:@"男性"]; 30 | [customer stub:@selector(divisionString) andReturn:@"M1層"]; 31 | [customer stub:@selector(isInDivision:) andReturn:theValue(YES) withArguments:theValue(DivisionM1), nil]; 32 | 33 | [sut setCustomer:customer]; 34 | 35 | //SUTのUILabelにセットされていることを確認(表示内容の確認ではない) 36 | [[sut.nameLabel.text should] equal:@"岩田 貫一"]; 37 | [[sut.mailLabel.text should] equal:@"myemailaddress@example.com"]; 38 | [[sut.genderLabel.text should] equal:@"男性"]; 39 | [[sut.ageLabel.text should] equal:@"33"]; 40 | [[sut.marketDivisionLabel.text should] equal:@"M1層"]; 41 | 42 | //引数付きメソッドのスタブを確認(SUTでは使っていないがサンプルとして) 43 | [[theValue([customer isInDivision:DivisionM1]) should] equal:theValue(YES)]; 44 | }); 45 | }); 46 | 47 | /** Kiwiによる非同期テストのサンプル */ 48 | describe(@"CustomerDetailView asynchronous testing", ^{ 49 | __block CustomerDetailView *sut; 50 | 51 | beforeEach(^{ 52 | UINib* nib = [UINib nibWithNibName:@"CustomerDetailView" bundle:nil]; 53 | NSArray* array = [nib instantiateWithOwner:nil options:nil]; 54 | sut = [array objectAtIndex:0]; 55 | //アバター画像取得確認のため、デフォルト画像を一旦nilに差し替える 56 | [sut.avatarImage setImage:nil]; 57 | }); 58 | 59 | it(@"connect to gravatar.com(asynchronous)", ^{ 60 | __block Customer *customer = [[Customer alloc] init]; 61 | customer.mail = @"myemailaddress@example.com"; 62 | 63 | //非同期処理を実行 64 | [sut setCustomer:customer]; 65 | 66 | //1秒後にアバター画像が取得できていること(nilでないこと)をチェック 67 | [[expectFutureValue(sut.avatarImage.image) shouldEventually] beNonNil]; 68 | }); 69 | }); 70 | 71 | SPEC_END 72 | -------------------------------------------------------------------------------- /KiwiTests/CustomerSpec.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | #import "Kiwi.h" 9 | #import "Customer.h" 10 | 11 | /** KiwiによるCustomerクラスのSpecサンプル */ 12 | SPEC_BEGIN(CustomerSpec) 13 | 14 | describe(@"Customer test(顧客クラスのテストケース)", ^{ 15 | __block Customer *sut; 16 | 17 | beforeEach(^{ 18 | sut = [[Customer alloc] init]; 19 | }); 20 | 21 | context(@"Female cases(性別=女性のブロック)", ^{ 22 | beforeEach(^{ 23 | sut.gender = GenderFemale; 24 | }); 25 | 26 | context(@"age=3のブロック", ^{ 27 | beforeEach(^{ 28 | sut.age = 3; 29 | }); 30 | it(@"divisionからはNone(分類外)が返るべき", ^{ 31 | [[theValue([sut division]) should] equal:theValue(DivisionNone)]; 32 | }); 33 | it(@"divisionStringからは分類外が返るべき", ^{ 34 | [[[sut divisionString] should] equal:@"分類外"]; 35 | }); 36 | describe(@"isInDivision", ^{ 37 | it(@"NoneにはYESが返るべき", ^{ 38 | [[theValue([sut isInDivision:DivisionNone]) should] equal:theValue(YES)]; 39 | }); 40 | it(@"F1層にはNOが返るべき", ^{ 41 | [[theValue([sut isInDivision:DivisionF1]) should] equal:theValue(NO)]; 42 | }); 43 | it(@"F2層にはNOが返るべき", ^{ 44 | [[theValue([sut isInDivision:DivisionF2]) should] equal:theValue(NO)]; 45 | }); 46 | it(@"F3層にはNOが返るべき", ^{ 47 | [[theValue([sut isInDivision:DivisionF3]) should] equal:theValue(NO)]; 48 | }); 49 | }); 50 | }); 51 | 52 | context(@"age=4のブロック", ^{ 53 | beforeEach(^{ 54 | sut.age = 4; 55 | }); 56 | it(@"divisionからはC層が返るべき", ^{ 57 | [[theValue([sut division]) should] equal:theValue(DivisionC)]; 58 | }); 59 | it(@"divisionStringからはC層が返るべき", ^{ 60 | [[[sut divisionString] should] equal:@"C層"]; 61 | }); 62 | describe(@"isInDivision", ^{ 63 | it(@"C層にはYESが返るべき", ^{ 64 | [[theValue([sut isInDivision:DivisionC]) should] equal:theValue(YES)]; 65 | }); 66 | it(@"F1層にはNOが返るべき", ^{ 67 | [[theValue([sut isInDivision:DivisionF1]) should] equal:theValue(NO)]; 68 | }); 69 | it(@"F2層にはNOが返るべき", ^{ 70 | [[theValue([sut isInDivision:DivisionF2]) should] equal:theValue(NO)]; 71 | }); 72 | it(@"F3層にはNOが返るべき", ^{ 73 | [[theValue([sut isInDivision:DivisionF3]) should] equal:theValue(NO)]; 74 | }); 75 | }); 76 | }); 77 | 78 | context(@"age=12のブロック", ^{ 79 | beforeEach(^{ 80 | sut.age = 12; 81 | }); 82 | it(@"divisionからはC層が返るべき", ^{ 83 | [[theValue([sut division]) should] equal:theValue(DivisionC)]; 84 | }); 85 | it(@"divisionStringからはC層が返るべき", ^{ 86 | [[[sut divisionString] should] equal:@"C層"]; 87 | }); 88 | describe(@"isInDivision", ^{ 89 | it(@"C層にはYESが返るべき", ^{ 90 | [[theValue([sut isInDivision:DivisionC]) should] equal:theValue(YES)]; 91 | }); 92 | it(@"F1層にはNOが返るべき", ^{ 93 | [[theValue([sut isInDivision:DivisionF1]) should] equal:theValue(NO)]; 94 | }); 95 | it(@"F2層にはNOが返るべき", ^{ 96 | [[theValue([sut isInDivision:DivisionF2]) should] equal:theValue(NO)]; 97 | }); 98 | it(@"F3層にはNOが返るべき", ^{ 99 | [[theValue([sut isInDivision:DivisionF3]) should] equal:theValue(NO)]; 100 | }); 101 | }); 102 | }); 103 | 104 | context(@"age=13のブロック", ^{ 105 | beforeEach(^{ 106 | sut.age = 13; 107 | }); 108 | it(@"divisionからはT層が返るべき", ^{ 109 | [[theValue([sut division]) should] equal:theValue(DivisionT)]; 110 | }); 111 | it(@"divisionStringからはT層が返るべき", ^{ 112 | [[[sut divisionString] should] equal:@"T層"]; 113 | }); 114 | describe(@"isInDivision", ^{ 115 | it(@"T層にはYESが返るべき", ^{ 116 | [[theValue([sut isInDivision:DivisionT]) should] equal:theValue(YES)]; 117 | }); 118 | it(@"F1層にはNOが返るべき", ^{ 119 | [[theValue([sut isInDivision:DivisionF1]) should] equal:theValue(NO)]; 120 | }); 121 | it(@"F2層にはNOが返るべき", ^{ 122 | [[theValue([sut isInDivision:DivisionF2]) should] equal:theValue(NO)]; 123 | }); 124 | it(@"F3層にはNOが返るべき", ^{ 125 | [[theValue([sut isInDivision:DivisionF3]) should] equal:theValue(NO)]; 126 | }); 127 | }); 128 | }); 129 | 130 | context(@"age=19のブロック", ^{ 131 | beforeEach(^{ 132 | sut.age = 19; 133 | }); 134 | it(@"divisionからはT層が返るべき", ^{ 135 | [[theValue([sut division]) should] equal:theValue(DivisionT)]; 136 | }); 137 | it(@"divisionStringからはT層が返るべき", ^{ 138 | [[[sut divisionString] should] equal:@"T層"]; 139 | }); 140 | describe(@"isInDivision", ^{ 141 | it(@"T層にはYESが返るべき", ^{ 142 | [[theValue([sut isInDivision:DivisionT]) should] equal:theValue(YES)]; 143 | }); 144 | it(@"F1層にはNOが返るべき", ^{ 145 | [[theValue([sut isInDivision:DivisionF1]) should] equal:theValue(NO)]; 146 | }); 147 | it(@"F2層にはNOが返るべき", ^{ 148 | [[theValue([sut isInDivision:DivisionF2]) should] equal:theValue(NO)]; 149 | }); 150 | it(@"F3層にはNOが返るべき", ^{ 151 | [[theValue([sut isInDivision:DivisionF3]) should] equal:theValue(NO)]; 152 | }); 153 | }); 154 | }); 155 | 156 | context(@"age=20のブロック", ^{ 157 | beforeEach(^{ 158 | sut.age = 20; 159 | }); 160 | it(@"divisionからはF1層が返るべき", ^{ 161 | KWValue *actual = theValue([sut division]); 162 | KWValue *expected = theValue(DivisionF1); 163 | [[actual should] equal:expected]; 164 | }); 165 | it(@"divisionStringからはF1層が返るべき", ^{ 166 | [[[sut divisionString] should] equal:@"F1層"]; 167 | }); 168 | describe(@"isInDivision", ^{ 169 | it(@"F1層にはYESが返るべき", ^{ 170 | KWValue *actual = theValue([sut isInDivision:DivisionF1]); 171 | KWValue *expected = theValue(YES); 172 | [[actual should] equal:expected]; 173 | }); 174 | it(@"F2層にはNOが返るべき", ^{ 175 | KWValue *actual = theValue([sut isInDivision:DivisionF2]); 176 | KWValue *expected = theValue(NO); 177 | [[actual should] equal:expected]; 178 | }); 179 | it(@"F3層にはNOが返るべき", ^{ 180 | KWValue *actual = theValue([sut isInDivision:DivisionF3]); 181 | KWValue *expected = theValue(NO); 182 | [[actual should] equal:expected]; 183 | }); 184 | }); 185 | }); 186 | 187 | context(@"age=34のブロック", ^{ 188 | beforeEach(^{ 189 | sut.age = 34; 190 | }); 191 | it(@"divisionからはF1層が返るべき", ^{ 192 | [[theValue([sut division]) should] equal:theValue(DivisionF1)]; 193 | }); 194 | it(@"divisionStringからはF1層が返るべき", ^{ 195 | [[[sut divisionString] should] equal:@"F1層"]; 196 | }); 197 | describe(@"isInDivision", ^{ 198 | it(@"F1層にはYESが返るべき", ^{ 199 | [[theValue([sut isInDivision:DivisionF1]) should] equal:theValue(YES)]; 200 | }); 201 | it(@"F2層にはNOが返るべき", ^{ 202 | [[theValue([sut isInDivision:DivisionF2]) should] equal:theValue(NO)]; 203 | }); 204 | it(@"F3層にはNOが返るべき", ^{ 205 | [[theValue([sut isInDivision:DivisionF3]) should] equal:theValue(NO)]; 206 | }); 207 | }); 208 | }); 209 | 210 | context(@"age=35のブロック", ^{ 211 | beforeEach(^{ 212 | sut.age = 35; 213 | }); 214 | it(@"divisionからはF2層が返るべき", ^{ 215 | [[theValue([sut division]) should] equal:theValue(DivisionF2)]; 216 | }); 217 | it(@"divisionStringからはF2層が返るべき", ^{ 218 | [[[sut divisionString] should] equal:@"F2層"]; 219 | }); 220 | describe(@"isInDivision", ^{ 221 | it(@"F1層にはNOが返るべき", ^{ 222 | [[theValue([sut isInDivision:DivisionF1]) should] equal:theValue(NO)]; 223 | }); 224 | it(@"F2層にはYESが返るべき", ^{ 225 | [[theValue([sut isInDivision:DivisionF2]) should] equal:theValue(YES)]; 226 | }); 227 | it(@"F3層にはNOが返るべき", ^{ 228 | [[theValue([sut isInDivision:DivisionF3]) should] equal:theValue(NO)]; 229 | }); 230 | }); 231 | }); 232 | 233 | context(@"age=49のブロック", ^{ 234 | beforeEach(^{ 235 | sut.age = 49; 236 | }); 237 | it(@"divisionからはF2層が返るべき", ^{ 238 | [[theValue([sut division]) should] equal:theValue(DivisionF2)]; 239 | }); 240 | it(@"divisionStringからはF2層が返るべき", ^{ 241 | [[[sut divisionString] should] equal:@"F2層"]; 242 | }); 243 | describe(@"isInDivision", ^{ 244 | it(@"F1層にはNOが返るべき", ^{ 245 | [[theValue([sut isInDivision:DivisionF1]) should] equal:theValue(NO)]; 246 | }); 247 | it(@"F2層にはYESが返るべき", ^{ 248 | [[theValue([sut isInDivision:DivisionF2]) should] equal:theValue(YES)]; 249 | }); 250 | it(@"F3層にはNOが返るべき", ^{ 251 | [[theValue([sut isInDivision:DivisionF3]) should] equal:theValue(NO)]; 252 | }); 253 | }); 254 | }); 255 | 256 | context(@"age=50のブロック", ^{ 257 | beforeEach(^{ 258 | sut.age = 50; 259 | }); 260 | it(@"divisionからはF3層が返るべき", ^{ 261 | [[theValue([sut division]) should] equal:theValue(DivisionF3)]; 262 | }); 263 | it(@"divisionStringからはF3層が返るべき", ^{ 264 | [[[sut divisionString] should] equal:@"F3層"]; 265 | }); 266 | describe(@"isInDivision", ^{ 267 | it(@"F1層にはNOが返るべき", ^{ 268 | [[theValue([sut isInDivision:DivisionF1]) should] equal:theValue(NO)]; 269 | }); 270 | it(@"F2層にはNOが返るべき", ^{ 271 | [[theValue([sut isInDivision:DivisionF2]) should] equal:theValue(NO)]; 272 | }); 273 | it(@"F3層にはYESが返るべき", ^{ 274 | [[theValue([sut isInDivision:DivisionF3]) should] equal:theValue(YES)]; 275 | }); 276 | }); 277 | }); 278 | 279 | context(@"age=51のブロック", ^{ 280 | beforeEach(^{ 281 | sut.age = 51; 282 | }); 283 | it(@"divisionからはF3層が返るべき", ^{ 284 | [[theValue([sut division]) should] equal:theValue(DivisionF3)]; 285 | }); 286 | it(@"divisionStringからはF3層が返るべき", ^{ 287 | [[[sut divisionString] should] equal:@"F3層"]; 288 | }); 289 | describe(@"isInDivision", ^{ 290 | it(@"F1層にはNOが返るべき", ^{ 291 | [[theValue([sut isInDivision:DivisionF1]) should] equal:theValue(NO)]; 292 | }); 293 | it(@"F2層にはNOが返るべき", ^{ 294 | [[theValue([sut isInDivision:DivisionF2]) should] equal:theValue(NO)]; 295 | }); 296 | it(@"F3層にはYESが返るべき", ^{ 297 | [[theValue([sut isInDivision:DivisionF3]) should] equal:theValue(YES)]; 298 | }); 299 | }); 300 | }); 301 | }); 302 | 303 | context(@"Male cases(性別=男性のブロック)", ^{ 304 | beforeEach(^{ 305 | sut.gender = GenderMale; 306 | }); 307 | 308 | context(@"age=3のブロック", ^{ 309 | beforeEach(^{ 310 | sut.age = 3; 311 | }); 312 | it(@"divisionからはNone(分類外)が返るべき", ^{ 313 | [[theValue([sut division]) should] equal:theValue(DivisionNone)]; 314 | }); 315 | it(@"divisionStringからは分類外が返るべき", ^{ 316 | [[[sut divisionString] should] equal:@"分類外"]; 317 | }); 318 | describe(@"isInDivision", ^{ 319 | it(@"NoneにはYESが返るべき", ^{ 320 | [[theValue([sut isInDivision:DivisionNone]) should] equal:theValue(YES)]; 321 | }); 322 | it(@"M1層にはNOが返るべき", ^{ 323 | [[theValue([sut isInDivision:DivisionM1]) should] equal:theValue(NO)]; 324 | }); 325 | it(@"M2層にはNOが返るべき", ^{ 326 | [[theValue([sut isInDivision:DivisionM2]) should] equal:theValue(NO)]; 327 | }); 328 | it(@"M3層にはNOが返るべき", ^{ 329 | [[theValue([sut isInDivision:DivisionM3]) should] equal:theValue(NO)]; 330 | }); 331 | }); 332 | }); 333 | 334 | context(@"age=4のブロック", ^{ 335 | beforeEach(^{ 336 | sut.age = 4; 337 | }); 338 | it(@"divisionからはC層が返るべき", ^{ 339 | [[theValue([sut division]) should] equal:theValue(DivisionC)]; 340 | }); 341 | it(@"divisionStringからはC層が返るべき", ^{ 342 | [[[sut divisionString] should] equal:@"C層"]; 343 | }); 344 | describe(@"isInDivision", ^{ 345 | it(@"C層にはYESが返るべき", ^{ 346 | [[theValue([sut isInDivision:DivisionC]) should] equal:theValue(YES)]; 347 | }); 348 | it(@"M1層にはNOが返るべき", ^{ 349 | [[theValue([sut isInDivision:DivisionM1]) should] equal:theValue(NO)]; 350 | }); 351 | it(@"M2層にはNOが返るべき", ^{ 352 | [[theValue([sut isInDivision:DivisionM2]) should] equal:theValue(NO)]; 353 | }); 354 | it(@"M3層にはNOが返るべき", ^{ 355 | [[theValue([sut isInDivision:DivisionM3]) should] equal:theValue(NO)]; 356 | }); 357 | }); 358 | }); 359 | 360 | context(@"age=12のブロック", ^{ 361 | beforeEach(^{ 362 | sut.age = 12; 363 | }); 364 | it(@"divisionからはC層が返るべき", ^{ 365 | [[theValue([sut division]) should] equal:theValue(DivisionC)]; 366 | }); 367 | it(@"divisionStringからはC層が返るべき", ^{ 368 | [[[sut divisionString] should] equal:@"C層"]; 369 | }); 370 | describe(@"isInDivision", ^{ 371 | it(@"C層にはYESが返るべき", ^{ 372 | [[theValue([sut isInDivision:DivisionC]) should] equal:theValue(YES)]; 373 | }); 374 | it(@"M1層にはNOが返るべき", ^{ 375 | [[theValue([sut isInDivision:DivisionM1]) should] equal:theValue(NO)]; 376 | }); 377 | it(@"M2層にはNOが返るべき", ^{ 378 | [[theValue([sut isInDivision:DivisionM2]) should] equal:theValue(NO)]; 379 | }); 380 | it(@"M3層にはNOが返るべき", ^{ 381 | [[theValue([sut isInDivision:DivisionM3]) should] equal:theValue(NO)]; 382 | }); 383 | }); 384 | }); 385 | 386 | context(@"age=13のブロック", ^{ 387 | beforeEach(^{ 388 | sut.age = 13; 389 | }); 390 | it(@"divisionからはT層が返るべき", ^{ 391 | [[theValue([sut division]) should] equal:theValue(DivisionT)]; 392 | }); 393 | it(@"divisionStringからはT層が返るべき", ^{ 394 | [[[sut divisionString] should] equal:@"T層"]; 395 | }); 396 | describe(@"isInDivision", ^{ 397 | it(@"T層にはYESが返るべき", ^{ 398 | [[theValue([sut isInDivision:DivisionT]) should] equal:theValue(YES)]; 399 | }); 400 | it(@"M1層にはNOが返るべき", ^{ 401 | [[theValue([sut isInDivision:DivisionM1]) should] equal:theValue(NO)]; 402 | }); 403 | it(@"M2層にはNOが返るべき", ^{ 404 | [[theValue([sut isInDivision:DivisionM2]) should] equal:theValue(NO)]; 405 | }); 406 | it(@"M3層にはNOが返るべき", ^{ 407 | [[theValue([sut isInDivision:DivisionM3]) should] equal:theValue(NO)]; 408 | }); 409 | }); 410 | }); 411 | 412 | context(@"age=19のブロック", ^{ 413 | beforeEach(^{ 414 | sut.age = 19; 415 | }); 416 | it(@"divisionからはT層が返るべき", ^{ 417 | [[theValue([sut division]) should] equal:theValue(DivisionT)]; 418 | }); 419 | it(@"divisionStringからはT層が返るべき", ^{ 420 | [[[sut divisionString] should] equal:@"T層"]; 421 | }); 422 | describe(@"isInDivision", ^{ 423 | it(@"T層にはYESが返るべき", ^{ 424 | [[theValue([sut isInDivision:DivisionT]) should] equal:theValue(YES)]; 425 | }); 426 | it(@"M1層にはNOが返るべき", ^{ 427 | [[theValue([sut isInDivision:DivisionM1]) should] equal:theValue(NO)]; 428 | }); 429 | it(@"M2層にはNOが返るべき", ^{ 430 | [[theValue([sut isInDivision:DivisionM2]) should] equal:theValue(NO)]; 431 | }); 432 | it(@"M3層にはNOが返るべき", ^{ 433 | [[theValue([sut isInDivision:DivisionM3]) should] equal:theValue(NO)]; 434 | }); 435 | }); 436 | }); 437 | 438 | context(@"age=20のブロック", ^{ 439 | beforeEach(^{ 440 | sut.age = 20; 441 | }); 442 | it(@"divisionからはM1層が返るべき", ^{ 443 | KWValue *actual = theValue([sut division]); 444 | KWValue *expected = theValue(DivisionM1); 445 | [[actual should] equal:expected]; 446 | }); 447 | it(@"divisionStringからはM1層が返るべき", ^{ 448 | [[[sut divisionString] should] equal:@"M1層"]; 449 | }); 450 | describe(@"isInDivision", ^{ 451 | it(@"M1層にはYESが返るべき", ^{ 452 | KWValue *actual = theValue([sut isInDivision:DivisionM1]); 453 | KWValue *expected = theValue(YES); 454 | [[actual should] equal:expected]; 455 | }); 456 | it(@"M2層にはNOが返るべき", ^{ 457 | KWValue *actual = theValue([sut isInDivision:DivisionM2]); 458 | KWValue *expected = theValue(NO); 459 | [[actual should] equal:expected]; 460 | }); 461 | it(@"M3層にはNOが返るべき", ^{ 462 | KWValue *actual = theValue([sut isInDivision:DivisionM3]); 463 | KWValue *expected = theValue(NO); 464 | [[actual should] equal:expected]; 465 | }); 466 | }); 467 | }); 468 | 469 | context(@"age=34のブロック", ^{ 470 | beforeEach(^{ 471 | sut.age = 34; 472 | }); 473 | it(@"divisionからはM1層が返るべき", ^{ 474 | [[theValue([sut division]) should] equal:theValue(DivisionM1)]; 475 | }); 476 | it(@"divisionStringからはM1層が返るべき", ^{ 477 | [[[sut divisionString] should] equal:@"M1層"]; 478 | }); 479 | describe(@"isInDivision", ^{ 480 | it(@"M1層にはYESが返るべき", ^{ 481 | [[theValue([sut isInDivision:DivisionM1]) should] equal:theValue(YES)]; 482 | }); 483 | it(@"M2層にはNOが返るべき", ^{ 484 | [[theValue([sut isInDivision:DivisionM2]) should] equal:theValue(NO)]; 485 | }); 486 | it(@"M3層にはNOが返るべき", ^{ 487 | [[theValue([sut isInDivision:DivisionM3]) should] equal:theValue(NO)]; 488 | }); 489 | }); 490 | }); 491 | 492 | context(@"age=35のブロック", ^{ 493 | beforeEach(^{ 494 | sut.age = 35; 495 | }); 496 | it(@"divisionからはM2層が返るべき", ^{ 497 | [[theValue([sut division]) should] equal:theValue(DivisionM2)]; 498 | }); 499 | it(@"divisionStringからはM2層が返るべき", ^{ 500 | [[[sut divisionString] should] equal:@"M2層"]; 501 | }); 502 | describe(@"isInDivision", ^{ 503 | it(@"M1層にはNOが返るべき", ^{ 504 | [[theValue([sut isInDivision:DivisionM1]) should] equal:theValue(NO)]; 505 | }); 506 | it(@"M2層にはYESが返るべき", ^{ 507 | [[theValue([sut isInDivision:DivisionM2]) should] equal:theValue(YES)]; 508 | }); 509 | it(@"M3層にはNOが返るべき", ^{ 510 | [[theValue([sut isInDivision:DivisionM3]) should] equal:theValue(NO)]; 511 | }); 512 | }); 513 | }); 514 | 515 | context(@"age=49のブロック", ^{ 516 | beforeEach(^{ 517 | sut.age = 49; 518 | }); 519 | it(@"divisionからはM2層が返るべき", ^{ 520 | [[theValue([sut division]) should] equal:theValue(DivisionM2)]; 521 | }); 522 | it(@"divisionStringからはM2層が返るべき", ^{ 523 | [[[sut divisionString] should] equal:@"M2層"]; 524 | }); 525 | describe(@"isInDivision", ^{ 526 | it(@"M1層にはNOが返るべき", ^{ 527 | [[theValue([sut isInDivision:DivisionM1]) should] equal:theValue(NO)]; 528 | }); 529 | it(@"M2層にはYESが返るべき", ^{ 530 | [[theValue([sut isInDivision:DivisionM2]) should] equal:theValue(YES)]; 531 | }); 532 | it(@"M3層にはNOが返るべき", ^{ 533 | [[theValue([sut isInDivision:DivisionM3]) should] equal:theValue(NO)]; 534 | }); 535 | }); 536 | }); 537 | 538 | context(@"age=50のブロック", ^{ 539 | beforeEach(^{ 540 | sut.age = 50; 541 | }); 542 | it(@"divisionからはM3層が返るべき", ^{ 543 | [[theValue([sut division]) should] equal:theValue(DivisionM3)]; 544 | }); 545 | it(@"divisionStringからはM3層が返るべき", ^{ 546 | [[[sut divisionString] should] equal:@"M3層"]; 547 | }); 548 | describe(@"isInDivision", ^{ 549 | it(@"M1層にはNOが返るべき", ^{ 550 | [[theValue([sut isInDivision:DivisionM1]) should] equal:theValue(NO)]; 551 | }); 552 | it(@"M2層にはNOが返るべき", ^{ 553 | [[theValue([sut isInDivision:DivisionM2]) should] equal:theValue(NO)]; 554 | }); 555 | it(@"M3層にはYESが返るべき", ^{ 556 | [[theValue([sut isInDivision:DivisionM3]) should] equal:theValue(YES)]; 557 | }); 558 | }); 559 | }); 560 | 561 | context(@"age=51のブロック", ^{ 562 | beforeEach(^{ 563 | sut.age = 51; 564 | }); 565 | it(@"divisionからはM3層が返るべき", ^{ 566 | [[theValue([sut division]) should] equal:theValue(DivisionM3)]; 567 | }); 568 | it(@"divisionStringからはM3層が返るべき", ^{ 569 | [[[sut divisionString] should] equal:@"M3層"]; 570 | }); 571 | describe(@"isInDivision", ^{ 572 | it(@"M1層にはNOが返るべき", ^{ 573 | [[theValue([sut isInDivision:DivisionM1]) should] equal:theValue(NO)]; 574 | }); 575 | it(@"M2層にはNOが返るべき", ^{ 576 | [[theValue([sut isInDivision:DivisionM2]) should] equal:theValue(NO)]; 577 | }); 578 | it(@"M3層にはYESが返るべき", ^{ 579 | [[theValue([sut isInDivision:DivisionM3]) should] equal:theValue(YES)]; 580 | }); 581 | }); 582 | }); 583 | }); 584 | 585 | }); 586 | 587 | SPEC_END 588 | -------------------------------------------------------------------------------- /KiwiTests/ExpectationsSpec.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | #import "Kiwi.h" 9 | #import "Customer.h" 10 | 11 | /** KiwiのExpectationsサンプル */ 12 | SPEC_BEGIN(ExpectationsSpec) 13 | 14 | describe(@"Expectations examples by Customer class", ^{ 15 | __block Customer *sut; 16 | 17 | beforeEach(^{ 18 | sut = [[Customer alloc] init]; 19 | sut.gender = GenderFemale; 20 | sut.age = 20; 21 | }); 22 | 23 | it(@"Object nil expectations", ^{ 24 | [[sut should] beNonNil]; 25 | [[sut shouldNot] beNil]; 26 | [sut shouldNotBeNil]; 27 | }); 28 | 29 | it(@"Object extends and implementation expectations", ^{ 30 | [[sut should] beKindOfClass:[Customer class]]; 31 | [[sut shouldNot] conformToProtocol:@protocol(NSURLConnectionDataDelegate)]; 32 | }); 33 | 34 | it(@"Numeric expectations", ^{ 35 | [[theValue(sut.age) should] beLessThan:theValue(21)]; 36 | [[theValue(sut.age) should] beLessThanOrEqualTo:theValue(20)]; 37 | [[theValue(sut.age) should] beGreaterThan:theValue(19)]; 38 | [[theValue(sut.age) should] beGreaterThanOrEqualTo:theValue(20)]; 39 | }); 40 | 41 | it(@"Boolean expectations", ^{ 42 | [[theValue([sut isInDivision:DivisionF1]) should] beYes]; 43 | }); 44 | 45 | it(@"String expectations", ^{ 46 | [[[sut divisionString] should] startWithString:@"F1"]; 47 | [[[sut divisionString] should] matchPattern:@"^.1.+$"]; 48 | }); 49 | 50 | it(@"Collection expectations", ^{ 51 | NSArray *actualArray = @[@"1", @"2", @"3"]; 52 | [[actualArray should] haveCountOf:3]; 53 | }); 54 | }); 55 | 56 | //it()でなくpending_()を使用すると、このテストは実行時に評価されない(常に成功する) 57 | pending_(@"pending failure case", ^{ 58 | fail(@"fail"); 59 | }); 60 | 61 | SPEC_END 62 | -------------------------------------------------------------------------------- /KiwiTests/GravatarAccessorAsynchronousSpec.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | #import "Kiwi.h" 9 | #import "GravatarAccessor.h" 10 | 11 | 12 | /** 非同期のテスト用GravatarAvatarDelegate実装 */ 13 | @interface GravatarAvatarDelegateStub : NSObject 14 | 15 | /** 取得成功時にアバター画像を格納する */ 16 | @property(nonatomic, strong) UIImage *avatar; 17 | 18 | @end 19 | 20 | @implementation GravatarAvatarDelegateStub 21 | 22 | /** 取得成功時のスタブ実装 */ 23 | - (void)responseAvatar:(UIImage*)avatar{ 24 | self.avatar = avatar; 25 | } 26 | 27 | /** 取得失敗時のスタブ実装 */ 28 | - (void)responseError:(NSString*)message{ 29 | self.avatar = nil; 30 | } 31 | 32 | @end 33 | 34 | 35 | /** GravatarAccessorクラスのテスト(Kiwiによる非同期テストのサンプル) */ 36 | SPEC_BEGIN(GravatarAccessorAsynchronousSpec) 37 | 38 | describe(@"asynchronous testing", ^{ 39 | it(@"connect to gravatar.com(asynchronous)", ^{ 40 | //delegateのStubを準備する 41 | __block GravatarAvatarDelegateStub *stub = [[GravatarAvatarDelegateStub alloc] init]; 42 | 43 | NSOperationQueue *queue = [NSOperationQueue mainQueue]; 44 | [queue addOperationWithBlock:^{ 45 | //非同期処理を実行 46 | GravatarAccessor *sut = [[GravatarAccessor alloc] initWithMail:@"myemailaddress@example.com" delegate:stub]; 47 | [sut requestAvatar]; 48 | }]; 49 | 50 | //1秒後にアバター画像が取得できていること(nilでないこと)をチェック 51 | [[expectFutureValue(stub.avatar) shouldEventually] beNonNil]; 52 | }); 53 | }); 54 | 55 | SPEC_END 56 | -------------------------------------------------------------------------------- /KiwiTests/GravatarAccessorSpec.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | #import "Kiwi.h" 9 | #import "GravatarAccessor.h" 10 | 11 | /** 非公開フィールドにアクセスするためのカテゴリ */ 12 | @interface GravatarAccessor() 13 | 14 | /** 通知先デリゲート */ 15 | @property(nonatomic, weak) id delegate; 16 | 17 | /** ステータスコード */ 18 | @property(nonatomic, assign) NSInteger statusCode; 19 | 20 | /** MIMEType */ 21 | @property(nonatomic, copy) NSString *MIMEType; 22 | 23 | /** レスポンス本体 */ 24 | @property(nonatomic, strong) NSMutableData *responseAccumulator; 25 | 26 | @end 27 | 28 | 29 | /** GravatarAccessorクラスのテスト(Kiwiによるモックオブジェクトのサンプル) */ 30 | SPEC_BEGIN(GravatarAccessorSpec) 31 | 32 | describe(@"GravatarAccessor test using mock", ^{ 33 | __block GravatarAccessor *sut; 34 | __block NSURLConnection *conn; //NSURLConnectionDataDelegateのメソッドでnilが警告されるので、空のオブジェクトを用意する 35 | 36 | beforeEach(^{ 37 | sut = [[GravatarAccessor alloc] 38 | initWithMail:@"myemailaddress@example.com" delegate:nil]; 39 | 40 | conn = [[NSURLConnection alloc] init]; 41 | }); 42 | 43 | describe(@"didReceiveResponse test using stub", ^{ 44 | it(@"", ^{ 45 | //Test Stubを準備する 46 | //このテストはNSHTTPURLResponseインスタンスでも可能ですが、スタブを使うほうがシンプルに書けます 47 | NSHTTPURLResponse *response = [NSHTTPURLResponse mock]; 48 | [response stub:@selector(statusCode) andReturn:theValue(200)]; 49 | [response stub:@selector(MIMEType) andReturn:@"image/png"]; 50 | 51 | [sut connection:conn didReceiveResponse:response]; 52 | 53 | //レスポンスからインスタンスフィールドにコピーされていることを確認 54 | [[theValue(sut.statusCode) should] equal:theValue(200)]; 55 | [[sut.MIMEType should] equal:@"image/png"]; 56 | }); 57 | }); 58 | 59 | describe(@"didReceiveData test using mock", ^{ 60 | it(@"use normally mock", ^{ 61 | //検証用NSData 62 | NSBundle *testBundle = [NSBundle bundleForClass:[self class]]; 63 | NSString *path = [testBundle pathForResource:@"testavatar" ofType:@"png"]; 64 | NSData *dummyData = [NSData dataWithContentsOfFile:path]; 65 | [[dummyData shouldNot] beNil]; 66 | 67 | //Mock Objectを準備する 68 | NSMutableData *mockMutableData = [NSMutableData mock]; 69 | [[mockMutableData should] receive:@selector(appendData:) withArguments:dummyData]; 70 | sut.responseAccumulator = mockMutableData; 71 | 72 | [sut connection:conn didReceiveData:dummyData]; 73 | }); 74 | }); 75 | 76 | describe(@"connectionDidFinishLoading test using protocol mock", ^{ 77 | it(@"success case", ^{ 78 | //delegateのMock Objectを準備する 79 | id mockDelegate = [KWMock mockForProtocol:@protocol(GravatarAvatarDelegate)]; 80 | [[mockDelegate should] receive:@selector(responseAvatar:) withArguments:any()]; 81 | sut.delegate = mockDelegate; 82 | 83 | //SUTに通信成功状態をセット 84 | NSHTTPURLResponse *response = [NSHTTPURLResponse mock]; 85 | [response stub:@selector(statusCode) andReturn:theValue(200)]; 86 | [response stub:@selector(MIMEType) andReturn:@"image/png"]; 87 | [sut connection:conn didReceiveResponse:response]; 88 | 89 | [sut connectionDidFinishLoading:conn]; 90 | }); 91 | 92 | it(@"failure case", ^{ 93 | //delegateのMock Objectを準備する 94 | id mockDelegate = [KWMock mockForProtocol:@protocol(GravatarAvatarDelegate)]; 95 | [[mockDelegate should] receive:@selector(responseError:) withArguments:@"Bad statusCode:500"]; 96 | sut.delegate = mockDelegate; 97 | 98 | //SUTに通信失敗状態をセット 99 | NSHTTPURLResponse *response = [NSHTTPURLResponse mock]; 100 | [response stub:@selector(statusCode) andReturn:theValue(500)]; 101 | [response stub:@selector(MIMEType) andReturn:@"image/png"]; 102 | [sut connection:conn didReceiveResponse:response]; 103 | 104 | [sut connectionDidFinishLoading:conn]; 105 | }); 106 | }); 107 | 108 | describe(@"didFailWithError test using mock", ^{ 109 | it(@"call didFailWithError case", ^{ 110 | //delegateのMock Objectを準備する 111 | id mockDelegate = [KWMock mockForProtocol:@protocol(GravatarAvatarDelegate)]; 112 | [[mockDelegate should] receive:@selector(responseError:) withArguments:@"Error! domain:d, code:-9"]; 113 | sut.delegate = mockDelegate; 114 | 115 | NSError *testError = [NSError errorWithDomain:@"d" code:-9 userInfo:nil]; 116 | [sut connection:conn didFailWithError:testError]; 117 | }); 118 | }); 119 | }); 120 | 121 | SPEC_END 122 | -------------------------------------------------------------------------------- /KiwiTests/KiwiTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /KiwiTests/KiwiTests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #import 10 | #endif 11 | -------------------------------------------------------------------------------- /KiwiTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /KiwiTests/testavatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nowsprinting/iosAppsTestAutomationSamples/c51437f6012e07ed08ba26f011ecf4c3b1a7c9dc/KiwiTests/testavatar.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Koji Hasegawa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export PATH := ${PATH}:/usr/local/bin:~/oclint/bin 2 | REPORTER=junit:test-reports/xctest-report.xml 3 | DST_OS=9.0 4 | DST_NAME=iPhone 4s 5 | 6 | test: 7 | xctool -workspace HelloTesting.xcworkspace -scheme HelloTesting \ 8 | -sdk iphonesimulator \ 9 | -destination "platform=iOS Simulator,OS=${DST_OS},name=${DST_NAME}" \ 10 | -reporter ${REPORTER} \ 11 | GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES \ 12 | GCC_GENERATE_TEST_COVERAGE_FILES=YES \ 13 | OTHER_CFLAGS="-DUSE_GCOV_FLUSH" \ 14 | OBJROOT=build \ 15 | clean test 16 | 17 | coverage-and-gcovr: test 18 | mkdir -p coverage-reports 19 | gcovr --object-directory build \ 20 | --exclude Pods --exclude ".*Tests" --exclude ".*\.h" \ 21 | --xml > coverage-reports/coverage.xml 22 | 23 | send-to-coveralls: 24 | coveralls \ 25 | --exclude Pods --exclude-pattern ".*Tests" --exclude-pattern ".*\.h" \ 26 | --repo-token 9ig1boI707fjkhwNBgVtTwOVFREPGVHeT 27 | 28 | oclint: 29 | xctool -workspace HelloTesting.xcworkspace -scheme HelloTesting \ 30 | -reporter json-compilation-database:compile_commands.json \ 31 | clean build 32 | mkdir -p pmd-reports 33 | oclint-json-compilation-database -- \ 34 | -max-priority-1 10 -max-priority-2 100 -max-priority-3 200 \ 35 | -report-type pmd -o pmd-reports/oclint.xml 36 | 37 | frank-test: 38 | frank setup --target="HelloTesting" --conf="Debug" 39 | frank build --workspace HelloTesting.xcworkspace --scheme HelloTesting 40 | export USE_SIM_LAUNCHER_SERVER=TRUE 41 | cucumber --format junit --out cucumber-reports Frank/features 42 | 43 | -------------------------------------------------------------------------------- /MonkeyTalk/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | HelloTesting for iOS 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.wst.jsdt.core.javascriptValidator 10 | 11 | 12 | 13 | 14 | com.gorillalogic.monkeyconsole.builder.monkeyTalkBuilder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.wst.jsdt.core.jsNature 21 | com.gorillalogic.monkeyconsole.builder.monkeyTalkNature 22 | 23 | 24 | -------------------------------------------------------------------------------- /MonkeyTalk/.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding/=UTF-8 3 | -------------------------------------------------------------------------------- /MonkeyTalk/add_customer.mt: -------------------------------------------------------------------------------- 1 | #顧客登録と検証をパラメタライズ 2 | Vars * Define name mail gender age division 3 | 4 | #顧客を追加 5 | Button Add Tap 6 | 7 | #顧客情報を入力 8 | Input name EnterText ${name} enter 9 | Input mail EnterText ${mail} enter 10 | ButtonSelector "gender segmentedcontrol" Select ${gender} 11 | ItemSelector "age pickerview" Select ${age} 12 | 13 | #Previewで表示内容を確認 14 | Button "Preview on WebView" Tap 15 | View name Verify ${name} 16 | View gender Verify ${gender} 17 | View age Verify ${age} 18 | View division Verify ${division} 19 | 20 | #Detailに戻る 21 | Button Detail Tap 22 | 23 | #Masterに戻る(画面遷移直後はボタンが反応しないためVerify後にTapする) 24 | Button Master Verify "Master" 25 | Button Master Tap 26 | 27 | -------------------------------------------------------------------------------- /MonkeyTalk/add_first_customer.mt: -------------------------------------------------------------------------------- 1 | #顧客を追加 2 | Button Add Tap 3 | 4 | #顧客情報を入力 5 | Input name EnterText "Newton Geizler" enter 6 | ButtonSelector "gender segmentedcontrol" Select 男性 7 | ItemSelector "age pickerview" Select 35 8 | 9 | #Previewで表示内容を確認 10 | Button "Preview on WebView" Tap 11 | View name Verify "Newton Geizler" 12 | View gender Verify 男性 13 | View age Verify 35 14 | View division Verify "M2層" 15 | 16 | #Detailに戻る 17 | Button Detail Tap 18 | 19 | #Masterに戻る(画面遷移直後はボタンが反応しないためVerify後にTapする) 20 | Button Master Verify "Master" 21 | Button Master Tap 22 | 23 | -------------------------------------------------------------------------------- /MonkeyTalk/add_many_customers.mt: -------------------------------------------------------------------------------- 1 | Script add_customer.mt Run "Newton Geizler" * "男性" "35" "M2層" 2 | Script add_customer.mt Run "Hermann Gottlieb" * "男性" "34" "M1層" 3 | Script add_customer.mt Run "Mako Mori" * "女性" "22" "F1層" 4 | 5 | -------------------------------------------------------------------------------- /NLTHTTPStubServerTests/GravatarAccessorTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | #import "NLTHTTPStubServer.h" 9 | #import "GravatarAccessor.h" 10 | 11 | /** NLTHTTPStubServerを利用したGravatarAccessorのテスト */ 12 | @interface GravatarAccessorTest : GHAsyncTestCase 13 | 14 | @end 15 | 16 | @implementation GravatarAccessorTest{ 17 | NLTHTTPStubServer *_server; 18 | } 19 | 20 | - (void)setUpClass{ 21 | [super setUpClass]; 22 | _server = [NLTHTTPStubServer sharedServer]; 23 | } 24 | 25 | - (void)setUp{ 26 | [super setUp]; 27 | [_server clear]; 28 | } 29 | 30 | - (void)testRequestAvatar_success{ 31 | //スタブサーバの設定 32 | [[[_server expect] forPath:@"/avatar/0bc83cb571cd1c50ba6f3e8a78ef1346" HTTPMethod:@"GET"] andResponseResource:@"testavatar" ofType:@"png"]; 33 | 34 | //非同期処理実行前にはこの[prepare]メソッドを必ず呼びます 35 | [self prepare]; 36 | 37 | //非同期処理を実行 38 | GravatarAccessor *sut = [[GravatarAccessor alloc] initWithMail:@"myemailaddress@example.com" delegate:self]; 39 | [sut requestAvatar]; 40 | 41 | //非同期処理が完了(成功)するのを待機します。timeout秒が経過するとテストは失敗します 42 | [self waitForStatus:kGHUnitWaitStatusSuccess timeout:10.0]; 43 | 44 | //スタブサーバにexpectしたURLにアクセスされたことを検証 45 | [_server verify]; 46 | } 47 | 48 | - (void)testRequestAvatar_failure{ 49 | //スタブサーバの設定(ステータスコード500を返します) 50 | [[[_server expect] forPath:@"/avatar/0bc83cb571cd1c50ba6f3e8a78ef1346" HTTPMethod:@"GET"] andStatusCode:500]; 51 | 52 | //非同期処理実行前にはこの[prepare]メソッドを必ず呼びます 53 | [self prepare]; 54 | 55 | //非同期処理を実行 56 | GravatarAccessor *sut = [[GravatarAccessor alloc] initWithMail:@"myemailaddress@example.com" delegate:self]; 57 | [sut requestAvatar]; 58 | 59 | //非同期処理が完了(成功)するのを待機します。timeout秒が経過するとテストは失敗します 60 | [self waitForStatus:kGHUnitWaitStatusFailure timeout:10.0]; 61 | 62 | //スタブサーバにexpectしたURLにアクセスされたことを検証 63 | [_server verify]; 64 | } 65 | 66 | 67 | #pragma mark - GravatarAvatarDelegate methods 68 | 69 | //取得成功(GravatarAvatarDelegateのメソッド) 70 | - (void)responseAvatar:(UIImage*)avatar{ 71 | //テスト成功を通知 72 | [self notify:kGHUnitWaitStatusSuccess forSelector:@selector(testRequestAvatar_success)]; 73 | } 74 | 75 | //取得失敗(GravatarAvatarDelegateのメソッド) 76 | - (void)responseError:(NSString*)message{ 77 | GHTestLog(message); 78 | //テスト失敗を通知 79 | [self notify:kGHUnitWaitStatusFailure forSelector:@selector(testRequestAvatar_failure)]; 80 | } 81 | 82 | @end 83 | -------------------------------------------------------------------------------- /NLTHTTPStubServerTests/NLTHTTPStubServerTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIApplicationExitsOnSuspend 28 | 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /NLTHTTPStubServerTests/NLTHTTPStubServerTests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_3_0 10 | #warning "This project uses features only available in iOS SDK 3.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #endif 17 | 18 | 19 | #define GRAVATAR_SERVER @"http://localhost:12345" 20 | -------------------------------------------------------------------------------- /NLTHTTPStubServerTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /NLTHTTPStubServerTests/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | 9 | #import "AppDelegate.h" 10 | 11 | int main(int argc, char * argv[]) 12 | { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, @"GHUnitIOSAppDelegate"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /NLTHTTPStubServerTests/testavatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nowsprinting/iosAppsTestAutomationSamples/c51437f6012e07ed08ba26f011ecf4c3b1a7c9dc/NLTHTTPStubServerTests/testavatar.png -------------------------------------------------------------------------------- /OCHamcrestTests/CustomerTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | #define HC_SHORTHAND 9 | #import 10 | #import "Customer.h" 11 | 12 | /** Customerテストクラス(OCHamcrest使用サンプル) */ 13 | @interface CustomerTest : XCTestCase 14 | 15 | @end 16 | 17 | @implementation CustomerTest 18 | { 19 | /** テスト対象オブジェクト */ 20 | Customer *_sut; 21 | } 22 | 23 | - (void)setUp 24 | { 25 | [super setUp]; 26 | _sut = [[Customer alloc] init]; 27 | } 28 | 29 | - (void)tearDown 30 | { 31 | [super tearDown]; 32 | } 33 | 34 | /** C層となるケース */ 35 | - (void)testDivision_C 36 | { 37 | _sut.gender = GenderMale; 38 | _sut.age = 4; 39 | assertThatUnsignedInteger([_sut division], equalToUnsignedInteger(DivisionC)); 40 | 41 | _sut.age = 12; 42 | assertThatUnsignedInteger([_sut division], equalToUnsignedInteger(DivisionC)); 43 | 44 | _sut.gender = GenderFemale; 45 | _sut.age = 4; 46 | assertThatUnsignedInteger([_sut division], equalToUnsignedInteger(DivisionC)); 47 | 48 | _sut.age = 12; 49 | assertThatUnsignedInteger([_sut division], equalToUnsignedInteger(DivisionC)); 50 | } 51 | 52 | /** T層となるケース */ 53 | - (void)testDivision_T 54 | { 55 | _sut.gender = GenderMale; 56 | _sut.age = 13; 57 | assertThatUnsignedInteger([_sut division], equalToUnsignedInteger(DivisionT)); 58 | 59 | _sut.age = 19; 60 | assertThatUnsignedInteger([_sut division], equalToUnsignedInteger(DivisionT)); 61 | 62 | _sut.gender = GenderFemale; 63 | _sut.age = 13; 64 | assertThatUnsignedInteger([_sut division], equalToUnsignedInteger(DivisionT)); 65 | 66 | _sut.age = 19; 67 | assertThatUnsignedInteger([_sut division], equalToUnsignedInteger(DivisionT)); 68 | } 69 | 70 | /** M1層となるケース */ 71 | - (void)testDivision_M1 72 | { 73 | _sut.gender = GenderMale; 74 | _sut.age = 20; 75 | assertThatUnsignedInteger([_sut division], equalToUnsignedInteger(DivisionM1)); 76 | 77 | _sut.age = 34; 78 | assertThatUnsignedInteger([_sut division], equalToUnsignedInteger(DivisionM1)); 79 | } 80 | 81 | /** M2層となるケース */ 82 | - (void)testDivision_M2 83 | { 84 | _sut.gender = GenderMale; 85 | _sut.age = 35; 86 | assertThatUnsignedInteger([_sut division], equalToUnsignedInteger(DivisionM2)); 87 | 88 | _sut.age = 49; 89 | assertThatUnsignedInteger([_sut division], equalToUnsignedInteger(DivisionM2)); 90 | } 91 | 92 | /** M3層となるケース */ 93 | - (void)testDivision_M3 94 | { 95 | _sut.gender = GenderMale; 96 | _sut.age = 50; 97 | assertThatUnsignedInteger([_sut division], equalToUnsignedInteger(DivisionM3)); 98 | 99 | _sut.age = 51; 100 | assertThatUnsignedInteger([_sut division], equalToUnsignedInteger(DivisionM3)); 101 | } 102 | 103 | /** F1層となるケース */ 104 | - (void)testDivision_F1 105 | { 106 | _sut.gender = GenderFemale; 107 | _sut.age = 20; 108 | assertThatUnsignedInteger([_sut division], equalToUnsignedInteger(DivisionF1)); 109 | assertThat([_sut divisionString], equalTo(@"F1層")); 110 | 111 | _sut.age = 34; 112 | assertThatUnsignedInteger([_sut division], equalToUnsignedInteger(DivisionF1)); 113 | assertThat([_sut divisionString], startsWith(@"F1")); 114 | } 115 | 116 | /** F2層となるケース */ 117 | - (void)testDivision_F2 118 | { 119 | _sut.gender = GenderFemale; 120 | _sut.age = 35; 121 | assertThatUnsignedInteger([_sut division], equalToUnsignedInteger(DivisionF2)); 122 | 123 | _sut.age = 49; 124 | assertThatUnsignedInteger([_sut division], equalToUnsignedInteger(DivisionF2)); 125 | } 126 | 127 | /** F3層となるケース */ 128 | - (void)testDivision_F3 129 | { 130 | _sut.gender = GenderFemale; 131 | _sut.age = 50; 132 | assertThatUnsignedInteger([_sut division], equalToUnsignedInteger(DivisionF3)); 133 | 134 | _sut.age = 51; 135 | assertThatUnsignedInteger([_sut division], equalToUnsignedInteger(DivisionF3)); 136 | } 137 | 138 | /** 分類外となるケース */ 139 | - (void)testDivision_None 140 | { 141 | _sut.gender = GenderMale; 142 | _sut.age = 3; 143 | assertThatUnsignedInteger([_sut division], equalToUnsignedInteger(DivisionNone)); 144 | 145 | _sut.gender = GenderFemale; 146 | _sut.age = 3; 147 | assertThatUnsignedInteger([_sut division], equalToUnsignedInteger(DivisionNone)); 148 | } 149 | 150 | @end 151 | -------------------------------------------------------------------------------- /OCHamcrestTests/OCHamcrestTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /OCHamcrestTests/OCHamcrestTests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #import 10 | #endif 11 | -------------------------------------------------------------------------------- /OCHamcrestTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /OCMockTests/Customer2Test.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | #import 9 | #import "Customer2.h" 10 | 11 | /** Customer2テストクラス(OCMockによるクラスメソッド書き換えのサンプル) */ 12 | @interface Customer2Test : XCTestCase 13 | 14 | @end 15 | 16 | @implementation Customer2Test{ 17 | /** テスト対象オブジェクト */ 18 | Customer2 *_sut; 19 | } 20 | 21 | - (void)setUp{ 22 | [super setUp]; 23 | 24 | //[NSDate date]が返す偽のNSDateオブジェクトを生成 25 | NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 26 | dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"ja_JP"]; 27 | dateFormatter.timeZone = [NSTimeZone timeZoneWithName:@"JST"]; 28 | dateFormatter.dateFormat = @"yyyy/M/d"; 29 | NSDate *mockCurrentDate = [dateFormatter dateFromString:@"2014/3/1"]; 30 | 31 | //NSDateのモックオブジェクトを生成、クラスメソッド[date]を置き換える 32 | id mockDate = [OCMockObject mockForClass:[NSDate class]]; 33 | [[[[mockDate stub] classMethod] andReturn:mockCurrentDate] date]; 34 | 35 | //SUT生成(内部で[NSDate date]が呼び出される) 36 | _sut = [[Customer2 alloc] init]; 37 | 38 | //NSDateのクラスメソッド[date]の置き換えを終了 39 | [mockDate stopMocking]; 40 | } 41 | 42 | - (void)testAge_0{ 43 | [_sut setBirthWithString:@"2013/3/2"]; 44 | XCTAssertEqual(0U, _sut.age); //ageはunsignedなので、expectedにもUを付加 45 | 46 | [_sut setBirthWithString:@"2014/3/1"]; 47 | XCTAssertEqual(0U, _sut.age); 48 | 49 | [_sut setBirthWithString:@"2014/3/2"]; 50 | XCTAssertEqual(0U, _sut.age); 51 | } 52 | 53 | - (void)testAge_1{ 54 | [_sut setBirthWithString:@"2013/3/1"]; 55 | XCTAssertEqual(1U, _sut.age); 56 | 57 | [_sut setBirthWithString:@"2012/3/2"]; 58 | XCTAssertEqual(1U, _sut.age); 59 | 60 | [_sut setBirthWithString:@"2012/3/1"]; 61 | XCTAssertEqual(2U, _sut.age); 62 | } 63 | 64 | - (void)testAge_17{ 65 | [_sut setBirthWithString:@"1997/3/2"]; 66 | XCTAssertEqual(16U, _sut.age); 67 | 68 | [_sut setBirthWithString:@"1997/3/1"]; 69 | XCTAssertEqual(17U, _sut.age); 70 | 71 | [_sut setBirthWithString:@"1996/3/2"]; 72 | XCTAssertEqual(17U, _sut.age); 73 | 74 | [_sut setBirthWithString:@"1996/3/1"]; 75 | XCTAssertEqual(18U, _sut.age); 76 | } 77 | 78 | - (void)testAge_100{ 79 | [_sut setBirthWithString:@"1914/3/2"]; 80 | XCTAssertEqual(99U, _sut.age); 81 | 82 | [_sut setBirthWithString:@"1914/3/1"]; 83 | XCTAssertEqual(100U, _sut.age); 84 | 85 | [_sut setBirthWithString:@"1913/3/2"]; 86 | XCTAssertEqual(100U, _sut.age); 87 | 88 | [_sut setBirthWithString:@"1913/3/1"]; 89 | XCTAssertEqual(101U, _sut.age); 90 | } 91 | 92 | @end 93 | -------------------------------------------------------------------------------- /OCMockTests/CustomerDetailViewTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | #import 9 | #import "CustomerDetailView.h" 10 | 11 | /** CustomerDetailViewテストクラス(OCMockによるテストスタブのサンプル) */ 12 | @interface CustomerDetailViewTest : XCTestCase 13 | 14 | @end 15 | 16 | @implementation CustomerDetailViewTest 17 | 18 | - (void)testSetCustomer{ 19 | //Test Stubを準備する 20 | id customer = [OCMockObject mockForClass:[Customer class]]; 21 | [[[customer stub] andReturn:@"岩田 貫一"] name]; 22 | [[[customer stub] andReturn:@"myemailaddress@example.com"] mail]; 23 | [[[customer stub] andReturnValue:OCMOCK_VALUE((NSUInteger){33})] age]; 24 | [[[customer stub] andReturn:@"男性"] genderString]; 25 | [[[customer stub] andReturn:@"M1層"] divisionString]; 26 | [[[customer stub] andReturnValue:OCMOCK_VALUE((BOOL){YES})] isInDivision:DivisionM1]; 27 | 28 | UINib* nib = [UINib nibWithNibName:@"CustomerDetailView" bundle:nil]; 29 | NSArray* array = [nib instantiateWithOwner:nil options:nil]; 30 | CustomerDetailView *sut = [array objectAtIndex:0]; 31 | 32 | [sut setCustomer:customer]; 33 | 34 | //SUTのUILabelにセットされていることを確認(表示内容の確認ではない) 35 | XCTAssertEqualObjects(@"岩田 貫一", sut.nameLabel.text); 36 | XCTAssertEqualObjects(@"myemailaddress@example.com", sut.mailLabel.text); 37 | XCTAssertEqualObjects(@"男性", sut.genderLabel.text); 38 | XCTAssertEqualObjects(@"33", sut.ageLabel.text); 39 | XCTAssertEqualObjects(@"M1層", sut.marketDivisionLabel.text); 40 | 41 | //引数付きメソッドのスタブを確認(SUTでは使っていないがサンプルとして) 42 | XCTAssertTrue([customer isInDivision:DivisionM1]); 43 | } 44 | 45 | - (void)testException{ 46 | //例外オブジェクトの準備 47 | NSException *excep = [NSException exceptionWithName:NSRangeException reason:nil userInfo:nil]; 48 | 49 | //Test Stubを準備する 50 | id customer = [OCMockObject mockForClass:[Customer class]]; 51 | [[[customer stub] andThrow:excep] name]; 52 | 53 | UINib* nib = [UINib nibWithNibName:@"CustomerDetailView" bundle:nil]; 54 | NSArray* array = [nib instantiateWithOwner:nil options:nil]; 55 | CustomerDetailView *sut = [array objectAtIndex:0]; 56 | 57 | //nameメソッドで例外が発生することを確認(例外発生すれば成功) 58 | XCTAssertThrows([sut setCustomer:customer]); 59 | } 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /OCMockTests/GravatarAccessorTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | #import 9 | #import "GravatarAccessor.h" 10 | 11 | /** 非公開フィールドにアクセスするためのカテゴリ */ 12 | @interface GravatarAccessor() 13 | 14 | /** 通知先デリゲート */ 15 | @property(nonatomic, weak) id delegate; 16 | 17 | /** ステータスコード */ 18 | @property(nonatomic, assign) NSInteger statusCode; 19 | 20 | /** MIMEType */ 21 | @property(nonatomic, copy) NSString *MIMEType; 22 | 23 | /** レスポンス本体 */ 24 | @property(nonatomic, strong) NSMutableData *responseAccumulator; 25 | 26 | @end 27 | 28 | 29 | /** GravatarAccessorテストクラス(OCMockによるモックオブジェクトのサンプル) */ 30 | @interface GravatarAccessorTest : XCTestCase 31 | 32 | @end 33 | 34 | @implementation GravatarAccessorTest{ 35 | /** テスト対象オブジェクト */ 36 | GravatarAccessor *_sut; 37 | 38 | /** NSURLConnectionDataDelegateのメソッドでnilが警告されるので、空のオブジェクトを用意する */ 39 | NSURLConnection *_conn; 40 | } 41 | 42 | - (void)setUp{ 43 | [super setUp]; 44 | _sut = [[GravatarAccessor alloc] 45 | initWithMail:@"myemailaddress@example.com" delegate:nil]; 46 | 47 | _conn = [[NSURLConnection alloc] init]; 48 | } 49 | 50 | - (void)testDidReceiveResponse{ 51 | //Test Stubを準備する 52 | //このテストはNSHTTPURLResponseインスタンスでも可能ですが、スタブを使うほうがシンプルに書けます 53 | id response = [OCMockObject mockForClass:[NSHTTPURLResponse class]]; 54 | [[[response stub] andReturnValue:OCMOCK_VALUE((NSInteger){200})] statusCode]; 55 | [[[response stub] andReturn:@"image/png"] MIMEType]; 56 | 57 | [_sut connection:_conn didReceiveResponse:response]; 58 | 59 | //レスポンスからインスタンスフィールドにコピーされていることを確認 60 | XCTAssertEqual(200, _sut.statusCode); 61 | XCTAssertEqualObjects(@"image/png", _sut.MIMEType); 62 | } 63 | 64 | - (void)testDidReceiveData{ 65 | //検証用NSData 66 | NSBundle *testBundle = [NSBundle bundleForClass:[self class]]; 67 | NSString *path = [testBundle pathForResource:@"testavatar" ofType:@"png"]; 68 | NSData *dummyData = [NSData dataWithContentsOfFile:path]; 69 | XCTAssertNotNil(dummyData, @"テスト画像がロードできていること"); 70 | 71 | //Mock Objectを準備する 72 | id mockMutableData = [OCMockObject mockForClass:[NSMutableData class]]; 73 | [[mockMutableData expect] appendData:[OCMArg any]]; 74 | _sut.responseAccumulator = mockMutableData; 75 | 76 | [_sut connection:_conn didReceiveData:dummyData]; 77 | 78 | //内部で[appendData:]が呼ばれたことを検証 79 | [mockMutableData verify]; 80 | } 81 | 82 | - (void)testConnectionDidFinishLoading_success{ 83 | //delegateのMock Objectを準備する 84 | id mockDelegate = [OCMockObject mockForProtocol:@protocol(GravatarAvatarDelegate)]; 85 | [[mockDelegate expect] responseAvatar:[OCMArg any]]; 86 | _sut.delegate = mockDelegate; 87 | 88 | //SUTに通信成功状態をセット 89 | id response = [OCMockObject mockForClass:[NSHTTPURLResponse class]]; 90 | [[[response stub] andReturnValue:OCMOCK_VALUE((NSInteger){200})] statusCode]; 91 | [[[response stub] andReturn:@"image/png"] MIMEType]; 92 | [_sut connection:_conn didReceiveResponse:response]; 93 | 94 | [_sut connectionDidFinishLoading:_conn]; 95 | 96 | //内部で[responseAvatar:]が呼ばれたことを検証 97 | [mockDelegate verify]; 98 | } 99 | 100 | - (void)testConnectionDidFinishLoading_failure{ 101 | //delegateのMock Objectを準備する 102 | id mockDelegate = [OCMockObject mockForProtocol:@protocol(GravatarAvatarDelegate)]; 103 | [[mockDelegate expect] responseError:@"Bad statusCode:500"]; 104 | _sut.delegate = mockDelegate; 105 | 106 | //SUTに通信失敗状態をセット 107 | id response = [OCMockObject mockForClass:[NSHTTPURLResponse class]]; 108 | [[[response stub] andReturnValue:OCMOCK_VALUE((NSInteger){500})] statusCode]; 109 | [[[response stub] andReturn:@"image/png"] MIMEType]; 110 | [_sut connection:_conn didReceiveResponse:response]; 111 | 112 | [_sut connectionDidFinishLoading:_conn]; 113 | 114 | //内部で[responseError:]が呼ばれたことを検証 115 | [mockDelegate verify]; 116 | } 117 | 118 | - (void)testDidFailWithError{ 119 | //delegateのMock Objectを準備する 120 | id mockDelegate = [OCMockObject mockForProtocol:@protocol(GravatarAvatarDelegate)]; 121 | [[mockDelegate expect] responseError:@"Error! domain:d, code:-9"]; 122 | _sut.delegate = mockDelegate; 123 | 124 | NSError *testError = [NSError errorWithDomain:@"d" code:-9 userInfo:nil]; 125 | [_sut connection:_conn didFailWithError:testError]; 126 | 127 | //内部で[responseError:]が呼ばれたことを検証 128 | [mockDelegate verify]; 129 | } 130 | 131 | @end 132 | -------------------------------------------------------------------------------- /OCMockTests/OCMockTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /OCMockTests/OCMockTests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #import 10 | #endif 11 | -------------------------------------------------------------------------------- /OCMockTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /OCMockTests/testavatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nowsprinting/iosAppsTestAutomationSamples/c51437f6012e07ed08ba26f011ecf4c3b1a7c9dc/OCMockTests/testavatar.png -------------------------------------------------------------------------------- /OCMockitoTests/CustomerDetailViewTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | #define HC_SHORTHAND 9 | #import 10 | #define MOCKITO_SHORTHAND 11 | #import 12 | #import "CustomerDetailView.h" 13 | 14 | /** CustomerDetailViewテストクラス(OCMockitoによるテストスタブのサンプル) */ 15 | @interface CustomerDetailViewTest : XCTestCase 16 | 17 | @end 18 | 19 | @implementation CustomerDetailViewTest 20 | 21 | - (void)testSetCustomer{ 22 | //Test Stubを準備する 23 | Customer *customer = mock([Customer class]); 24 | [given([customer name]) willReturn:@"岩田 貫一"]; 25 | [given([customer mail]) willReturn:@"myemailaddress@example.com"]; 26 | [given([customer age]) willReturnUnsignedInteger:33U]; 27 | [given([customer genderString]) willReturn:@"男性"]; 28 | [given([customer divisionString]) willReturn:@"M1層"]; 29 | [given([customer isInDivision:DivisionM1]) willReturnBool:YES]; 30 | 31 | UINib* nib = [UINib nibWithNibName:@"CustomerDetailView" bundle:nil]; 32 | NSArray* array = [nib instantiateWithOwner:nil options:nil]; 33 | CustomerDetailView *sut = [array objectAtIndex:0]; 34 | 35 | [sut setCustomer:customer]; 36 | 37 | //SUTのUILabelにセットされていることを確認(表示内容の確認ではない) 38 | XCTAssertEqualObjects(@"岩田 貫一", sut.nameLabel.text); 39 | XCTAssertEqualObjects(@"myemailaddress@example.com", sut.mailLabel.text); 40 | XCTAssertEqualObjects(@"男性", sut.genderLabel.text); 41 | XCTAssertEqualObjects(@"33", sut.ageLabel.text); 42 | XCTAssertEqualObjects(@"M1層", sut.marketDivisionLabel.text); 43 | 44 | //引数付きメソッドのスタブを確認(SUTでは使っていないがサンプルとして) 45 | XCTAssertTrue([customer isInDivision:DivisionM1]); 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /OCMockitoTests/GravatarAccessorTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | #import 8 | #define HC_SHORTHAND 9 | #import 10 | #define MOCKITO_SHORTHAND 11 | #import 12 | #import "GravatarAccessor.h" 13 | 14 | /** 非公開フィールドにアクセスするためのカテゴリ */ 15 | @interface GravatarAccessor() 16 | 17 | /** 通知先デリゲート */ 18 | @property(nonatomic, weak) id delegate; 19 | 20 | /** ステータスコード */ 21 | @property(nonatomic, assign) NSInteger statusCode; 22 | 23 | /** MIMEType */ 24 | @property(nonatomic, copy) NSString *MIMEType; 25 | 26 | /** レスポンス本体 */ 27 | @property(nonatomic, strong) NSMutableData *responseAccumulator; 28 | 29 | @end 30 | 31 | 32 | /** GravatarAccessorテストクラス(OCMockioによるモックオブジェクトのサンプル) */ 33 | @interface GravatarAccessorTest : XCTestCase 34 | 35 | @end 36 | 37 | @implementation GravatarAccessorTest{ 38 | /** テスト対象オブジェクト */ 39 | GravatarAccessor *_sut; 40 | 41 | /** NSURLConnectionDataDelegateのメソッドでnilが警告されるので、空のオブジェクトを用意する */ 42 | NSURLConnection *_conn; 43 | } 44 | 45 | - (void)setUp{ 46 | [super setUp]; 47 | _sut = [[GravatarAccessor alloc] 48 | initWithMail:@"myemailaddress@example.com" delegate:nil]; 49 | 50 | _conn = [[NSURLConnection alloc] init]; 51 | } 52 | 53 | - (void)testDidReceiveResponse{ 54 | //Test Stubを準備する 55 | //このテストはNSHTTPURLResponseインスタンスでも可能ですが、スタブを使うほうがシンプルに書けます 56 | NSHTTPURLResponse *response = mock([NSHTTPURLResponse class]); 57 | [given([response statusCode]) willReturnInteger:200]; 58 | [given([response MIMEType]) willReturn:@"image/png"]; 59 | 60 | [_sut connection:_conn didReceiveResponse:response]; 61 | 62 | //レスポンスからインスタンスフィールドにコピーされていることを確認 63 | XCTAssertEqual(200, _sut.statusCode); 64 | XCTAssertEqualObjects(@"image/png", _sut.MIMEType); 65 | } 66 | 67 | - (void)testDidReceiveData{ 68 | //検証用NSData 69 | NSBundle *testBundle = [NSBundle bundleForClass:[self class]]; 70 | NSString *path = [testBundle pathForResource:@"testavatar" ofType:@"png"]; 71 | NSData *dummyData = [NSData dataWithContentsOfFile:path]; 72 | XCTAssertNotNil(dummyData, @"テスト画像がロードできていること"); 73 | 74 | //Mock Objectを準備する(OCMockitoはこの時点でexpectしません) 75 | NSMutableData *mockMutableData = mock([NSMutableData class]); 76 | _sut.responseAccumulator = mockMutableData; 77 | 78 | [_sut connection:_conn didReceiveData:dummyData]; 79 | 80 | //内部で[appendData:]が呼ばれたことを検証 81 | [verify(mockMutableData) appendData:dummyData]; 82 | } 83 | 84 | - (void)testConnectionDidFinishLoading_success{ 85 | //delegateのMock Objectを準備する(OCMockitoはこの時点でexpectしません) 86 | id mockDelegate = mockProtocol(@protocol(GravatarAvatarDelegate)); 87 | _sut.delegate = mockDelegate; 88 | 89 | //SUTに通信成功状態をセット 90 | NSHTTPURLResponse *response = mock([NSHTTPURLResponse class]); 91 | [given([response statusCode]) willReturnInteger:200]; 92 | [given([response MIMEType]) willReturn:@"image/png"]; 93 | [_sut connection:_conn didReceiveResponse:response]; 94 | 95 | [_sut connectionDidFinishLoading:_conn]; 96 | 97 | //内部で[responseAvatar:]が呼ばれたことを検証 98 | [verify(mockDelegate) responseAvatar:nil]; 99 | } 100 | 101 | - (void)testConnectionDidFinishLoading_failure{ 102 | //delegateのMock Objectを準備する(OCMockitoはこの時点でexpectしません) 103 | id mockDelegate = mockProtocol(@protocol(GravatarAvatarDelegate)); 104 | _sut.delegate = mockDelegate; 105 | 106 | //SUTに通信失敗状態をセット 107 | NSHTTPURLResponse *response = mock([NSHTTPURLResponse class]); 108 | [given([response statusCode]) willReturnInteger:500]; 109 | [given([response MIMEType]) willReturn:@"image/png"]; 110 | [_sut connection:_conn didReceiveResponse:response]; 111 | 112 | [_sut connectionDidFinishLoading:_conn]; 113 | 114 | //内部で[responseError:]が呼ばれたことを検証 115 | [verify(mockDelegate) responseError:@"Bad statusCode:500"]; 116 | } 117 | 118 | - (void)testDidFailWithError{ 119 | //delegateのMock Objectを準備する(OCMockitoはこの時点でexpectしません) 120 | id mockDelegate = mockProtocol(@protocol(GravatarAvatarDelegate)); 121 | _sut.delegate = mockDelegate; 122 | 123 | NSError *testError = [NSError errorWithDomain:@"d" code:-9 userInfo:nil]; 124 | [_sut connection:_conn didFailWithError:testError]; 125 | 126 | //内部で[responseError:]が呼ばれたことを検証 127 | [verify(mockDelegate) responseError:@"Error! domain:d, code:-9"]; 128 | } 129 | 130 | @end 131 | -------------------------------------------------------------------------------- /OCMockitoTests/OCMockitoTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /OCMockitoTests/OCMockitoTests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #import 10 | #endif 11 | -------------------------------------------------------------------------------- /OCMockitoTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /OCMockitoTests/testavatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nowsprinting/iosAppsTestAutomationSamples/c51437f6012e07ed08ba26f011ecf4c3b1a7c9dc/OCMockitoTests/testavatar.png -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | platform :ios, '6.0' 3 | 4 | pod 'tuneup_js' 5 | 6 | target :GHUnitTests, :exclusive => true do 7 | pod 'GHUnit' 8 | end 9 | target :KiwiTests, :exclusive => true do 10 | pod 'Kiwi' 11 | end 12 | target :OCHamcrestTests, :exclusive => true do 13 | pod 'OCHamcrest', '~> 4.0' 14 | end 15 | target :OCMockTests, :exclusive => true do 16 | pod 'OCMock', '~> 2.2' 17 | end 18 | target :OCMockitoTests, :exclusive => true do 19 | pod 'OCMockito', '~> 1.0' 20 | end 21 | target :NLTHTTPStubServerTests, :exclusive => true do 22 | pod 'GHUnit' 23 | pod 'NLTHTTPStubServer' 24 | end 25 | target :KIFTests, :exclusive => true do 26 | pod 'KIF', '~> 3.0' 27 | end 28 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - CocoaAsyncSocket (7.4.2) 3 | - CocoaHTTPServer (2.3): 4 | - CocoaAsyncSocket 5 | - CocoaLumberjack 6 | - CocoaLumberjack (2.0.1): 7 | - CocoaLumberjack/Default (= 2.0.1) 8 | - CocoaLumberjack/Extensions (= 2.0.1) 9 | - CocoaLumberjack/Core (2.0.1) 10 | - CocoaLumberjack/Default (2.0.1): 11 | - CocoaLumberjack/Core 12 | - CocoaLumberjack/Extensions (2.0.1): 13 | - CocoaLumberjack/Default 14 | - GHUnit (0.5.9) 15 | - KIF (3.3.0): 16 | - KIF/Core (= 3.3.0) 17 | - KIF/Core (3.3.0) 18 | - Kiwi (2.4.0) 19 | - NLTHTTPStubServer (0.4.0): 20 | - CocoaHTTPServer (>= 2.3) 21 | - OCHamcrest (4.2.0) 22 | - OCMock (2.2.4) 23 | - OCMockito (1.4.0): 24 | - OCHamcrest (~> 4.0) 25 | - tuneup_js (1.2.3) 26 | 27 | DEPENDENCIES: 28 | - GHUnit 29 | - KIF (~> 3.0) 30 | - Kiwi 31 | - NLTHTTPStubServer 32 | - OCHamcrest (~> 4.0) 33 | - OCMock (~> 2.2) 34 | - OCMockito (~> 1.0) 35 | - tuneup_js 36 | 37 | SPEC CHECKSUMS: 38 | CocoaAsyncSocket: f5783bdedd232d91b89769bc4b5a1580aed518ad 39 | CocoaHTTPServer: 5624681fc3473d43b18202f635f9b3abb013b530 40 | CocoaLumberjack: 019d1361244274a6138c788c6cb80baabc13fb8f 41 | GHUnit: c54e0019313bd49a575aecc502e30597f4b6664e 42 | KIF: 0a82046d06f3648799cac522d2d0f7934214caac 43 | Kiwi: f49c9d54b28917df5928fe44968a39ed198cb8a8 44 | NLTHTTPStubServer: a075a2a76f18f0f1af25ef8a95f4ba6b02f28e06 45 | OCHamcrest: c14c0b3f4c4e99363e5b87d97bae38c4438b6d5d 46 | OCMock: a6a7dc0e3997fb9f35d99f72528698ebf60d64f2 47 | OCMockito: 4981140c9a9ec06c31af40f636e3c0f25f27e6b2 48 | tuneup_js: bb9bd3060b8a115501bdf2b668f6497a806d5df3 49 | 50 | COCOAPODS: 0.38.2 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #iOSアプリ テスト自動化入門 サンプル 2 | [![Build Status](https://travis-ci.org/nowsprinting/iosAppsTestAutomationSamples.svg?branch=master)](https://travis-ci.org/nowsprinting/iosAppsTestAutomationSamples) 3 | [![Coverage Status](https://coveralls.io/repos/nowsprinting/iosAppsTestAutomationSamples/badge.png?branch=master)](https://coveralls.io/r/nowsprinting/iosAppsTestAutomationSamples?branch=master) 4 | [![Gitter](https://badges.gitter.im/nowsprinting/iosAppsTestAutomationSamples.png)](https://gitter.im/nowsprinting/iosAppsTestAutomationSamples) 5 | 6 | 7 | 書籍『 8 | iOSアプリ テスト自動化入門 9 | 』(秀和システム)のサンプルコードです。 10 | 11 | 12 | 13 | 14 | 書籍について、また、サンプルコード以外の訂正・変更箇所については、以下のブログエントリを参照してください。 15 | 16 | - [iOSアプリのテスト自動化本を執筆しました - やらなイカ?](http://nowsprinting.hatenablog.com/entry/2014/02/12/104959) 17 | - [iOSアプリ テスト自動化入門 のサポート - やらなイカ?](http://nowsprinting.hatenablog.com/entry/2014/03/18/115911) 18 | 19 | 20 | 21 | ##セットアップ手順 22 | 23 | 下記コマンドでセットアップを行ないます。 24 | 25 | $ git clone git@github.com:nowsprinting/iosAppsTestAutomationSamples.git 26 | $ cd iosAppsTestAutomationSamples/ 27 | $ pod install 28 | $ open HelloTesting.xcworkspace/ 29 | 30 | もしくは、このページ右にある"Download ZIP"ボタンでダウンロードしてください。CocoaPodsのインストール手順や使いかたは書籍のAppendixを参照してください。 31 | 32 | 33 | 34 | ##Chapterごとの特記事項 35 | 36 | ###2.2.1 不具合修正の前後をテストする 37 | 38 | 本文中の変更を入れた状態のブランチ[2.2.1](https://github.com/nowsprinting/iosAppsTestAutomationSamples/tree/2.2.1)がありますので、参考にしてください。 39 | 40 | ブランチの切り替えは、上記セットアップを済ませた状態から下記コマンドで行ないます。 41 | 42 | $ git checkout 2.2.1 43 | 44 | 45 | ###3.2.5 UIViewの表示テスト 46 | 47 | 本文中の変更を入れた状態のブランチ[3.2.5](https://github.com/nowsprinting/iosAppsTestAutomationSamples/tree/3.2.5)がありますので、参考にしてください。 48 | 49 | ブランチの切り替えは、上記セットアップを済ませた状態から下記コマンドで行ないます。 50 | 51 | $ git checkout 3.2.5 52 | 53 | 54 | ###5.3 Frank 55 | 56 | Frankのインストール、`frank setup`、`frank build`を実行してから、`cucumber`コマンドで実行してください。プロジェクトのビルド設定などの詳細は書籍を参照してください。 57 | 58 | なお、Xcode 5.1以降でFrankが動作しない(iOSシミュレータの起動に失敗する)問題があがっています。現在、Frankの公式リポジトリが下記に変更(移動)されてメンテナンスは引き継がれているようです。 59 | 60 | - [TestingWithFrank/Frank](https://github.com/TestingWithFrank/Frank) 61 | 62 | 63 | ###5.4 MonkeyTalk 64 | 65 | **5.4.4 MonkeyTalkエージェントのアプリへの組み込み** の手順で追加したビルドターゲット `HelloTestingWithMonkeyTalkAgent`を追加していますが、MonkeyTalkのエージェントのスタティックライブラリは同梱してありません。 66 | ビルドターゲット設定の"Build Phases"->"Link Binary With Libraries"で、MonkeyTalkエージェントのファイルパスを設定してご利用ください。 67 | 68 | また、本リポジトリのサンプルアプリを操作するテストスクリプトをMonkeyTalk/ディレクトリ下に追加しました。こちらについて詳しくは、@IT連載「[スマホ向け無料システムテスト自動化ツール](http://www.atmarkit.co.jp/ait/kw/smapho_testtool.html)」第1回「[システムテスト自動化の基礎知識とMonkeyTalkの使い方](http://www.atmarkit.co.jp/ait/articles/1407/16/news037.html) 」を参照してください。 69 | 70 | 71 | ###7.1 OS X Server/Bots 72 | 73 | このサンプルプロジェクトをOS X Server/Botsでビルドする場合、`.gitignore`ファイルの以下の行を削除し、Pods/ディレクトリ下のファイルをリポジトリに追加してください。 74 | 75 | #CocoaPods 76 | Pods 77 | -------------------------------------------------------------------------------- /UIAutomation/script/add_first_customer.js: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | var target = UIATarget.localTarget(); 8 | var testcase = "Add first customer"; 9 | 10 | //テストの開始(トレースログにテストケース名が記録されます) 11 | UIALogger.logStart(testcase); 12 | 13 | //"+"をタップ(顧客が追加され、編集画面に遷移します) 14 | target.frontMostApp().navigationBar().rightButton().tap(); 15 | 16 | //氏名欄に入力(0番目のUITextFieldをタップし、キーボード入力) 17 | target.frontMostApp().mainWindow().textFields()[0].tap(); 18 | target.frontMostApp().keyboard().typeString("Newton Geizler\n"); 19 | 20 | //性別を設定(0番目のUISegmentedControlの"男性"ボタンをタップ) 21 | target.frontMostApp().mainWindow().segmentedControls()[0].buttons()["男性"].tap(); 22 | 23 | //年齢を設定(0番目のUIPickerViewの"35"を選択) 24 | target.frontMostApp().mainWindow().pickers()[0].wheels()[0].selectValue("35"); 25 | 26 | //一覧画面に戻る 27 | target.frontMostApp().navigationBar().leftButton().tap(); 28 | 29 | //入力結果を検証する 30 | var cell = target.frontMostApp().mainWindow().tableViews()["Empty list"].cells()["Newton Geizler, M2層"]; 31 | if(cell){ 32 | UIALogger.logPass(testcase); //セルが存在すれば成功 33 | }else{ 34 | UIALogger.logFail(testcase); 35 | } 36 | -------------------------------------------------------------------------------- /UIAutomation/script/other.js: -------------------------------------------------------------------------------- 1 | // 2 | // 『iOSアプリ テスト自動化入門』サンプルコード 3 | // 4 | // Copyright (c) 2014 Koji Hasegawa. All rights reserved. 5 | // 6 | 7 | var target = UIATarget.localTarget(); 8 | var testcase = "Other actions"; 9 | 10 | //テストの開始(トレースログにテストケース名が記録されます) 11 | UIALogger.logStart(testcase); 12 | 13 | //端末を横向きに 14 | target.setDeviceOrientation(UIA_DEVICE_ORIENTATION_LANDSCAPELEFT); 15 | 16 | //スクリーンショット撮影 17 | UIATarget.localTarget().captureScreenWithName(testcase+"_1"); 18 | 19 | //端末を縦向きに 20 | target.setDeviceOrientation(UIA_DEVICE_ORIENTATION_PORTRAIT); 21 | 22 | //スクリーンショット撮影 23 | UIATarget.localTarget().captureScreenWithName(testcase+"_2"); 24 | 25 | UIALogger.logPass(testcase); 26 | -------------------------------------------------------------------------------- /distribution/HelloTesting.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | items 6 | 7 | 8 | assets 9 | 10 | 11 | kind 12 | software-package 13 | url 14 | http:www.example.com/ota/HelloTesting.ipa 15 | 16 | 17 | metadata 18 | 19 | bundle-identifier 20 | com.nowsprinting.iostestautomationbook.HelloTesting 21 | bundle-version 22 | 1.0 23 | kind 24 | software 25 | title 26 | HelloTesting 27 | 28 | 29 | 30 | 31 | 32 | --------------------------------------------------------------------------------