├── .gitignore ├── .tm_properties ├── CONTRIBUTING.md ├── README.md ├── SUMMARY.md ├── assets ├── Cover.png ├── pragmatic-testing.sketch └── pragmatic_style.css ├── book.json ├── chapters └── en-UK │ ├── app_testing │ ├── ipad_and_iphone.md │ ├── scroll_views.md │ ├── techniques_for_testing_different_aspects_of_the_app.md │ ├── testing_delegates.md │ ├── user_interactions.md │ └── views__snapshots.md │ ├── async │ ├── animations.md │ ├── dispatch_asyncs__ar_dispatch_etc.md │ ├── networking_in_view_controllers__network_models.md │ ├── techniques_for_getting_around_async_networking.md │ ├── techniques_for_getting_around_async_testing.md │ └── will_and_xctest_6.md │ ├── core_data │ ├── core_data.md │ └── core_data_migrations.md │ ├── foundations │ ├── dependency_injection.md │ └── stubs_mocks_and_fakes.md │ ├── ops │ ├── creation_of_app-centric_it_blocks.md │ ├── developer_operations_aka_automation.md │ ├── fixtures_and_factories.md │ └── techniques_for_keeping_testing_code_sane.md │ ├── oss_libs │ ├── expanding_on_bdd_frameworks.md │ ├── mocking_and_stubbing__ocmock_and_ocmockito_.md │ └── network_stubbing__ohttp_and_vcrurlconnection.md │ ├── prag_prog │ ├── improving_xcode.md │ ├── making_libraries_to_get_annoying_tests_out_of_your_app.md │ └── using_xcode_pragmatically.md │ ├── setup │ ├── getting_setup.md │ ├── how_i_got_started.md │ ├── introducing_tests_into_an_existing_application.md │ └── starting_a_new_application_and_using_tests.md │ ├── what_is │ ├── how_can_i_be_pragmatic_with_my_testing.md │ └── what_and_why_of_the_book.md │ ├── wrap_up │ ├── books.md │ ├── recommended_websites.md │ └── twitter_follows.md │ └── xctest │ ├── behavior_testing.md │ ├── integration_testing.md │ ├── test_driven_development.md │ ├── three_types_of_unit_tests.md │ ├── types_of_testing.md │ ├── unit_testing.md │ └── what_is_xctest_how_does_it_work.md ├── generate.rb ├── generators ├── generate_epub.rb ├── generate_gitbook.rb ├── generate_toc.rb └── structure_of_pragmatic_programming.rb ├── gitbook_summary.md └── pragmatic_testing.epub /.gitignore: -------------------------------------------------------------------------------- 1 | .Ulysses-Group.plist 2 | *.marked 3 | .DS_Store 4 | _book 5 | -------------------------------------------------------------------------------- /.tm_properties: -------------------------------------------------------------------------------- 1 | [text] 2 | spellChecking = 1 3 | spellLanguage = en_GB 4 | 5 | ["*.md"] 6 | spellChecking = 1 7 | spellLanguage = en_GB 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How can I contribute to this book? 2 | 3 | There's a bunch of things I've not covered, and there's still a lot of work to do based on my current Table Of Contents (README), which currently lets you see how each chapters is doing with respect to word count. Ideally most topics should come to around ~300 - 500 words. 4 | 5 | I'm happy to have other people send pull requests to the book, adding or amending chapters. Here's my general rules: 6 | 7 | * **Strive for Real-World Examples** 8 | No `foo = bar`- even better if you can take it directly from an OSS code-base and reference it. 9 | 10 | Talk about personal experiences with ideas, and their trade-offs. It's great that you can test everything, but if it comes at the trade-off of a time intensive test-suite. Let people know. 11 | 12 | * **Down to Earth** 13 | Functional programming, protocol oriented programming, Reactive Programming, MVVM, etc. etc. - are all nice, but this book isn't for architecture astronauts. We want to be sticking with the well-known, well documented classics. 14 | 15 | Write like you talk, this isn't going to be published by a fancy publisher, so it should be in a casual tone. 16 | 17 | * **Simplicity Ships** 18 | This project is like, a million months overdue. Don't faff with changing formats or adding extra engineering work to "make things easier to write." 19 | 20 | Also, seriously, don't try making or improving a text editor just for this. Just use TextMate, with markdown, and "Deal with it." 21 | 22 | This makes life easier: 23 | 24 | ``` shell 25 | mkdir -p ~/Library/Application\ Support/Avian/Bundles 26 | cd ~/Library/Application\ Support/Avian/Bundles 27 | git clone https://github.com/mikemcquaid/GitHub-Markdown.tmbundle 28 | ``` 29 | 30 | * **Be Opinionated** 31 | Presenting options for anything is fine, but this book is about being pragmatic, not providing a tonne of sites to read through before being able to make a decision. 32 | 33 | * **Ship Before Promises** 34 | I've had a lot of offers on other projects around interesting things like translations, of course I'm interested, but without proof that it's both being done, and going to get completed - it's not worth breaking "Simplicity Ships." I've got a bunch of semi-translated projects that were abandoned. 35 | 36 | Don't worry about the style of the ebook, or whether you're using the right amount of `####`s until the book is ready, it's much easier to figure out the style once there is something to work with. It's got a pretty cover image, that's enough. 37 | 38 | * **Issues before new chapters** 39 | Each chapter takes a good long time to write, and if you've spent a long time on something which I don't think is a good pragmatic testing pattern, then that's gonna be a shame to waste your time. 40 | 41 | If you're not sure, make an issue and ask. It is my name on the book, and I have to deal with repercussions / maintaining your chapters. 42 | 43 | * **Things I Obsess Over** 44 | 45 | * Time it takes to run the test-suite 46 | * Simplicity to test, without strongly affecting maintainability 47 | 48 | * **Things I Don't Obsess Over** 49 | 50 | * Testing every last detail 51 | * Being right all the time 52 | 53 | ### Other than that? 54 | 55 | All the book generation is done via code, in ruby via the `generate.rb` file and the `generators` folder. It's ruby, because it pre-dates Swift being a useful language. 56 | 57 | Running `ruby generate.rb` will create an ePub ( if you have `pandoc` installed (`brew install pandoc`)) generate all the files for the [GitBook](https://www.gitbook.com/book/orta/pragmatic-ios-testing/details) and update the `README.md`. 58 | 59 | The file `generators/structure_of_pragmatic_programming.rb` contains all the path `->` chapter mapping, running `ruby generate.rb` will complain about missing paths/chapters. 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A Pragmatic Approach to iOS Testing 2 | =============== 3 | 4 | An ebook about pragmatic testing strategies. Click below to get the latest ePub. 5 | 6 | [ ![Image](assets/Cover.png "Pragmatic iOS Testing") ](https://github.com/orta/pragmatic-testing/blob/master/pragmatic_testing.epub?raw=true "Download ePub") 7 | 8 | Or check [it on GitBook](https://www.gitbook.com/book/orta/pragmatic-ios-testing/details). 9 | 10 | Wanna contribute? [read this](CONTRIBUTING.md). 11 | 12 | ##### Existing Pages 13 | 14 | | Topic | Last Updated | State | Length | 15 | | -------|------|---|-----| 16 | |[What Is/What And Why Of The Book](chapters/en-UK/what_is/what_and_why_of_the_book.md)|2016-03-31|💌|Words: 517| 17 | |[What Is/How Can I Be Pragmatic With My Testing](chapters/en-UK/what_is/how_can_i_be_pragmatic_with_my_testing.md)|2017-07-26|💌|Words: 368| 18 | |[XCTest/What Is XCTest How Does It Work](chapters/en-UK/xctest/what_is_xctest_how_does_it_work.md)|2018-02-18|💌|Words: 571| 19 | |[XCTest/Types Of Testing](chapters/en-UK/xctest/types_of_testing.md)|2016-09-09|💌|Words: 511| 20 | |[XCTest/Unit Testing](chapters/en-UK/xctest/unit_testing.md)|2016-03-31|💌|Words: 336| 21 | |[XCTest/Three Types Of Unit Tests](chapters/en-UK/xctest/three_types_of_unit_tests.md)|2016-09-09|💌|Words: 350| 22 | |[XCTest/Behavior Testing](chapters/en-UK/xctest/behavior_testing.md)|2016-03-31|💌|Words: 1227| 23 | |[XCTest/Test Driven Development](chapters/en-UK/xctest/test_driven_development.md)|2016-09-09|💌|Words: 444| 24 | |[XCTest/Integration Testing](chapters/en-UK/xctest/integration_testing.md)|2016-09-09|💌|Words: 535| 25 | |[Foundations/Dependency Injection](chapters/en-UK/foundations/dependency_injection.md)|2016-08-30|💌|Words: 806| 26 | |[Foundations/Stubs Mocks And Fakes](chapters/en-UK/foundations/stubs_mocks_and_fakes.md)|2016-09-09|💌|Words: 474| 27 | |[OSS Libs/Expanding On Bdd Frameworks](chapters/en-UK/oss_libs/expanding_on_bdd_frameworks.md)|2016-03-31|✍🏾|Words: 3| 28 | |[OSS Libs/Mocking And Stubbing Ocmock And Ocmockito ](chapters/en-UK/oss_libs/mocking_and_stubbing__ocmock_and_ocmockito_.md)|2016-03-31|✍🏾|Words: 0| 29 | |[OSS Libs/Network Stubbing Ohttp And Vcrurlconnection](chapters/en-UK/oss_libs/network_stubbing__ohttp_and_vcrurlconnection.md)|2016-03-31|✍🏾|Words: 0| 30 | |[Setup/How I Got Started](chapters/en-UK/setup/how_i_got_started.md)|2016-03-31|💌|Words: 333| 31 | |[Setup/Getting Setup](chapters/en-UK/setup/getting_setup.md)|2016-03-31|💌|Words: 315| 32 | |[Setup/Introducing Tests Into An Existing Application](chapters/en-UK/setup/introducing_tests_into_an_existing_application.md)|2016-03-31|💌|Words: 337| 33 | |[Setup/Starting A New Application And Using Tests](chapters/en-UK/setup/starting_a_new_application_and_using_tests.md)|2016-03-31|💌|Words: 498| 34 | |[Ops/Developer Operations Aka Automation](chapters/en-UK/ops/developer_operations_aka_automation.md)|2016-03-31|💌|Words: 1269| 35 | |[Ops/Techniques For Keeping Testing Code Sane](chapters/en-UK/ops/techniques_for_keeping_testing_code_sane.md)|2016-03-31|✍🏾|Words: 0| 36 | |[Ops/Creation Of App-Centric It Blocks](chapters/en-UK/ops/creation_of_app-centric_it_blocks.md)|2016-03-31|✍🏾|Words: 0| 37 | |[Ops/Fixtures And Factories](chapters/en-UK/ops/fixtures_and_factories.md)|2016-03-31|✍🏾|Words: 0| 38 | |[Async/Dispatch Asyncs Ar Dispatch Etc](chapters/en-UK/async/dispatch_asyncs__ar_dispatch_etc.md)|2016-03-31|💌|Words: 667| 39 | |[Async/Techniques For Getting Around Async Testing](chapters/en-UK/async/techniques_for_getting_around_async_testing.md)|2016-03-31|💌|Words: 1211| 40 | |[Async/Techniques For Getting Around Async Networking](chapters/en-UK/async/techniques_for_getting_around_async_networking.md)|2016-03-31|✍🏾|Words: 0| 41 | |[Async/Networking In View Controllers Network Models](chapters/en-UK/async/networking_in_view_controllers__network_models.md)|2016-07-07|💌|Words: 425| 42 | |[Async/Animations](chapters/en-UK/async/animations.md)|2016-03-31|📎|Words: 148| 43 | |[Async/Will And XCTest 6](chapters/en-UK/async/will_and_xctest_6.md)|2016-03-31|✍🏾|Words: 0| 44 | |[App Testing/Techniques For Testing Different Aspects Of The App](chapters/en-UK/app_testing/techniques_for_testing_different_aspects_of_the_app.md)|2016-03-31|✍🏾|Words: 0| 45 | |[App Testing/Views Snapshots](chapters/en-UK/app_testing/views__snapshots.md)|2016-06-23|💌|Words: 536| 46 | |[App Testing/Scroll Views](chapters/en-UK/app_testing/scroll_views.md)|2016-03-31|✍🏾|Words: 0| 47 | |[App Testing/User Interactions](chapters/en-UK/app_testing/user_interactions.md)|2016-03-31|✍🏾|Words: 7| 48 | |[App Testing/Ipad And Iphone](chapters/en-UK/app_testing/ipad_and_iphone.md)|2016-03-31|💌|Words: 414| 49 | |[App Testing/Testing Delegates](chapters/en-UK/app_testing/testing_delegates.md)|2016-09-09|💌|Words: 490| 50 | |[Core Data/Core Data](chapters/en-UK/core_data/core_data.md)|2016-03-31|💌|Words: 778| 51 | |[Core Data/Core Data Migrations](chapters/en-UK/core_data/core_data_migrations.md)|2016-03-31|💌|Words: 490| 52 | |[Prag Prog/Making Libraries To Get Annoying Tests Out Of Your App](chapters/en-UK/prag_prog/making_libraries_to_get_annoying_tests_out_of_your_app.md)|2016-03-31|✍🏾|Words: 0| 53 | |[Prag Prog/Using Xcode Pragmatically](chapters/en-UK/prag_prog/using_xcode_pragmatically.md)|2016-03-31|✍🏾|Words: 0| 54 | |[Prag Prog/Improving Xcode](chapters/en-UK/prag_prog/improving_xcode.md)|2016-03-31|✍🏾|Words: 0| 55 | |[Wrap Up/Books](chapters/en-UK/wrap_up/books.md)|2016-03-31|📎|Words: 168| 56 | |[Wrap Up/Twitter Follows](chapters/en-UK/wrap_up/twitter_follows.md)|2016-03-31|✍🏾|Words: 0| 57 | |[Wrap Up/Recommended Websites](chapters/en-UK/wrap_up/recommended_websites.md)|2018-03-12|✍🏾|Words: 50| 58 | 59 | 60 | Over 200 words: 57.1% 61 | Over 300 words: 57.1% 62 | TODOs: 13 63 | Words: 14278 64 | 65 | 66 | ##### Generating the ebook 67 | 68 | The latest ePub copy can be generated by running `ruby generate.rb` 69 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | ### Summary 2 | 3 | * [Chapters/En-Uk/What Is/What And Why Of The Book](chapters/en-UK/what_is/what_and_why_of_the_book.md) 4 | * [Chapters/En-Uk/What Is/How Can I Be Pragmatic With My Testing](chapters/en-UK/what_is/how_can_i_be_pragmatic_with_my_testing.md) 5 | * [Chapters/En-Uk/Xctest/What Is Xctest How Does It Work](chapters/en-UK/xctest/what_is_xctest_how_does_it_work.md) 6 | * [Chapters/En-Uk/Xctest/Types Of Testing](chapters/en-UK/xctest/types_of_testing.md) 7 | * [Chapters/En-Uk/Xctest/Unit Testing](chapters/en-UK/xctest/unit_testing.md) 8 | * [Chapters/En-Uk/Xctest/Three Types Of Unit Tests](chapters/en-UK/xctest/three_types_of_unit_tests.md) 9 | * [Chapters/En-Uk/Xctest/Behavior Testing](chapters/en-UK/xctest/behavior_testing.md) 10 | * [Chapters/En-Uk/Xctest/Test Driven Development](chapters/en-UK/xctest/test_driven_development.md) 11 | * [Chapters/En-Uk/Xctest/Integration Testing](chapters/en-UK/xctest/integration_testing.md) 12 | * [Chapters/En-Uk/Foundations/Dependency Injection](chapters/en-UK/foundations/dependency_injection.md) 13 | * [Chapters/En-Uk/Foundations/Stubs Mocks And Fakes](chapters/en-UK/foundations/stubs_mocks_and_fakes.md) 14 | * [Chapters/En-Uk/Oss Libs/Expanding On Bdd Frameworks](chapters/en-UK/oss_libs/expanding_on_bdd_frameworks.md) 15 | * [Chapters/En-Uk/Oss Libs/Mocking And Stubbing Ocmock And Ocmockito ](chapters/en-UK/oss_libs/mocking_and_stubbing__ocmock_and_ocmockito_.md) 16 | * [Chapters/En-Uk/Oss Libs/Network Stubbing Ohttp And Vcrurlconnection](chapters/en-UK/oss_libs/network_stubbing__ohttp_and_vcrurlconnection.md) 17 | * [Chapters/En-Uk/Setup/How I Got Started](chapters/en-UK/setup/how_i_got_started.md) 18 | * [Chapters/En-Uk/Setup/Getting Setup](chapters/en-UK/setup/getting_setup.md) 19 | * [Chapters/En-Uk/Setup/Introducing Tests Into An Existing Application](chapters/en-UK/setup/introducing_tests_into_an_existing_application.md) 20 | * [Chapters/En-Uk/Setup/Starting A New Application And Using Tests](chapters/en-UK/setup/starting_a_new_application_and_using_tests.md) 21 | * [Chapters/En-Uk/Ops/Developer Operations Aka Automation](chapters/en-UK/ops/developer_operations_aka_automation.md) 22 | * [Chapters/En-Uk/Ops/Techniques For Keeping Testing Code Sane](chapters/en-UK/ops/techniques_for_keeping_testing_code_sane.md) 23 | * [Chapters/En-Uk/Ops/Creation Of App-Centric It Blocks](chapters/en-UK/ops/creation_of_app-centric_it_blocks.md) 24 | * [Chapters/En-Uk/Ops/Fixtures And Factories](chapters/en-UK/ops/fixtures_and_factories.md) 25 | * [Chapters/En-Uk/Async/Dispatch Asyncs Ar Dispatch Etc](chapters/en-UK/async/dispatch_asyncs__ar_dispatch_etc.md) 26 | * [Chapters/En-Uk/Async/Techniques For Getting Around Async Testing](chapters/en-UK/async/techniques_for_getting_around_async_testing.md) 27 | * [Chapters/En-Uk/Async/Techniques For Getting Around Async Networking](chapters/en-UK/async/techniques_for_getting_around_async_networking.md) 28 | * [Chapters/En-Uk/Async/Networking In View Controllers Network Models](chapters/en-UK/async/networking_in_view_controllers__network_models.md) 29 | * [Chapters/En-Uk/Async/Animations](chapters/en-UK/async/animations.md) 30 | * [Chapters/En-Uk/Async/Will And Xctest 6](chapters/en-UK/async/will_and_xctest_6.md) 31 | * [Chapters/En-Uk/App Testing/Techniques For Testing Different Aspects Of The App](chapters/en-UK/app_testing/techniques_for_testing_different_aspects_of_the_app.md) 32 | * [Chapters/En-Uk/App Testing/Views Snapshots](chapters/en-UK/app_testing/views__snapshots.md) 33 | * [Chapters/En-Uk/App Testing/Scroll Views](chapters/en-UK/app_testing/scroll_views.md) 34 | * [Chapters/En-Uk/App Testing/User Interactions](chapters/en-UK/app_testing/user_interactions.md) 35 | * [Chapters/En-Uk/App Testing/Ipad And Iphone](chapters/en-UK/app_testing/ipad_and_iphone.md) 36 | * [Chapters/En-Uk/App Testing/Testing Delegates](chapters/en-UK/app_testing/testing_delegates.md) 37 | * [Chapters/En-Uk/Core Data/Core Data](chapters/en-UK/core_data/core_data.md) 38 | * [Chapters/En-Uk/Core Data/Core Data Migrations](chapters/en-UK/core_data/core_data_migrations.md) 39 | * [Chapters/En-Uk/Prag Prog/Making Libraries To Get Annoying Tests Out Of Your App](chapters/en-UK/prag_prog/making_libraries_to_get_annoying_tests_out_of_your_app.md) 40 | * [Chapters/En-Uk/Prag Prog/Using Xcode Pragmatically](chapters/en-UK/prag_prog/using_xcode_pragmatically.md) 41 | * [Chapters/En-Uk/Prag Prog/Improving Xcode](chapters/en-UK/prag_prog/improving_xcode.md) 42 | * [Chapters/En-Uk/Wrap Up/Books](chapters/en-UK/wrap_up/books.md) 43 | * [Chapters/En-Uk/Wrap Up/Twitter Follows](chapters/en-UK/wrap_up/twitter_follows.md) 44 | * [Chapters/En-Uk/Wrap Up/Recommended Websites](chapters/en-UK/wrap_up/recommended_websites.md) 45 | -------------------------------------------------------------------------------- /assets/Cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orta/pragmatic-testing/b2fcc710925034effacebdce4dd857f2db43ccda/assets/Cover.png -------------------------------------------------------------------------------- /assets/pragmatic-testing.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orta/pragmatic-testing/b2fcc710925034effacebdce4dd857f2db43ccda/assets/pragmatic-testing.sketch -------------------------------------------------------------------------------- /assets/pragmatic_style.css: -------------------------------------------------------------------------------- 1 | /* This defines styles and classes used in the book */ 2 | body { } 3 | code { font-family: monospace; } 4 | h1, h2, h3, h4, h5, h6 { text-align: center; margin-top:1.5em;} 5 | h1.title { } 6 | h2.author { } 7 | p { 8 | text-indent:2em; 9 | } 10 | blockquote { 11 | margin-left:3em; 12 | margin-right:3em; 13 | } 14 | .caption { 15 | text-align:center; 16 | font-style:italic; 17 | margin-bottom:1em; 18 | margin-top:.2em; 19 | font-size:.8em; 20 | } 21 | blockquote > p { 22 | text-indent:0; 23 | margin-bottom:1em; 24 | } 25 | 26 | img { 27 | display:block; 28 | margin-left: auto; 29 | margin-right: auto; 30 | text-align:center; 31 | margin-top:1em; 32 | } 33 | -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "structure": { 3 | "readme": "gitbook_summary.md" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /chapters/en-UK/app_testing/ipad_and_iphone.md: -------------------------------------------------------------------------------- 1 | ### Multi-Device Support 2 | 3 | There are mainly two ways to have a test-suite handle multiple device-types, and orientations. The easy way: Run your test-suite multiple times on multiple devices, simulators, and orientations. 4 | 5 | The hard way: Mock and Stub your way to multi-device support in one single test-suite. 6 | 7 | #### Device Fakes 8 | 9 | Like a lot of things, this used to be easier in simpler times. When you could just set a device size, and go from there, you can see this in Eigen's - ARTestContext.m 10 | 11 | TODO - Link ^ 12 | 13 | ``` objc 14 | static OCMockObject *ARPartialScreenMock; 15 | 16 | @interface UIScreen (Prvate) 17 | - (CGRect)_applicationFrameForInterfaceOrientation:(long long)arg1 usingStatusbarHeight:(double)arg2 ignoreStatusBar:(BOOL)ignore; 18 | @end 19 | 20 | + (void)runAsDevice:(enum ARDeviceType)device 21 | { 22 | [... setup] 23 | 24 | ARPartialScreenMock = [OCMockObject partialMockForObject:UIScreen.mainScreen]; 25 | NSValue *phoneSize = [NSValue valueWithCGRect:(CGRect)CGRectMake(0, 0, size.width, size.height)]; 26 | 27 | [[[ARPartialScreenMock stub] andReturnValue:phoneSize] bounds]; 28 | [[[[ARPartialScreenMock stub] andReturnValue:phoneSize] ignoringNonObjectArgs] _applicationFrameForInterfaceOrientation:0 usingStatusbarHeight:0 ignoreStatusBar:NO]; 29 | } 30 | ``` 31 | 32 | This ensures all ViewControllers are created at the expected size. Then you can use your own logic to determine iPhone vs iPad. This works for simple cases, but it isn't optimal in the current landscape of iOS apps. 33 | 34 | #### Trait Fakes 35 | 36 | Trait collections are now the recommended way to distinguish devices, as an iPad could now be showing your app in a screen the size of an iPhone. You can't rely on having an application the same size as the screen. This makes it more complex. 🎉 37 | 38 | This is not a space I've devoted a lot of time to, so consider this section a beta. If anyone wants to dig in, I'd be interested in knowing what the central point of knowledge for train collections is, and stubbing that in the way I did with `_applicationFrameForInterfaceOrientation:usingStatusbarHeight:ignoreStatusBar:`. 39 | 40 | Every View or View Controller (V/VC) has a read-only collection of traits, the V/VCs can listen for trait changes and re-arrange themselves. For example, we have a view that sets itself up on the collection change: 41 | 42 | TODO: Link to AuctionBannerView.swift 43 | 44 | ``` swift 45 | class AuctionBannerView: UIView { 46 | 47 | override func traitCollectionDidChange(previousTraitCollection: UITraitCollection?) { 48 | super.traitCollectionDidChange(previousTraitCollection) 49 | 50 | // Remove all subviews and call setupViews() again to start from scratch. 51 | subviews.forEach { $0.removeFromSuperview() } 52 | setupViews() 53 | } 54 | } 55 | ``` 56 | 57 | When we test this view we stub the `traitCollection` array and trigger `traitCollectionDidChange`, this is done in our [Forgeries](https://github.com/ashfurrow/forgeries) library. It looks pretty much like this, with the environment being the V/VC. 58 | 59 | ``` objc 60 | void stubTraitCollectionInEnvironment(UITraitCollection *traitCollection, id environment) { 61 | id partialMock = [OCMockObject partialMockForObject:environment]; 62 | [[[partialMock stub] andReturn:traitCollection] traitCollection]; 63 | [environment traitCollectionDidChange:nil]; 64 | } 65 | ``` 66 | 67 | Giving us the chance to make our V/VC think it's in any type of environment that we want to write tests for. 68 | -------------------------------------------------------------------------------- /chapters/en-UK/app_testing/scroll_views.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /chapters/en-UK/app_testing/techniques_for_testing_different_aspects_of_the_app.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orta/pragmatic-testing/b2fcc710925034effacebdce4dd857f2db43ccda/chapters/en-UK/app_testing/techniques_for_testing_different_aspects_of_the_app.md -------------------------------------------------------------------------------- /chapters/en-UK/app_testing/testing_delegates.md: -------------------------------------------------------------------------------- 1 | ## Patterns for Testing Using Protocols 2 | 3 | One of the really nice things about using protocols instead of classes is that it defines a collection of methods something should act with, but doesn't force the relationship into specific implementations. 4 | 5 | This works out really well, because it makes it super easy to switch out the object in test, as it just has to conform to said protocol. Let's look at an example of a tricky to test `UITableViewDataSource`. 6 | 7 | This example has a class whose responsibility is to deal with getting data, and providing that to a tableview. 8 | 9 | ``` swift 10 | class ORArtworkDataSource, NSObject, UITableViewDataSource { 11 | // Do some networking, pull in some data, make it possible to generate cells 12 | func getData() { 13 | [...] 14 | } 15 | [...] 16 | } 17 | 18 | class ORArtworkViewController: UITableViewController { 19 | var dataSource: ORArtworkDataSource! 20 | 21 | [...] 22 | override func viewDidLoad() { 23 | dataSource = ORArtworkDataSource() 24 | tableView.dataSource = dataSource 25 | dataSource.getData() 26 | } 27 | } 28 | ``` 29 | 30 | This implementation is great if you don't want to write any tests, but it can get tricky to find ways to have your tests perform easy-to-assert on behavior with this tactic. 31 | 32 | One of the simplest approaches to making this type of code easy to test is to use lazy initialisation, and a protocol to define the expectations but not the implementation. 33 | 34 | So, define a protocol that says what methods the `ORArtworkDataSource` should have then only let the `ORArtworkViewController` know it's talking to something which conforms to this protocol. 35 | 36 | ```swift 37 | 38 | /// This protocol abstracts the implementation details of the networking 39 | protocol ORArtworkDataSourcable { 40 | func getData() 41 | } 42 | 43 | class ORArtworkDataSource: NSObject, ORArtworkDataSourcable, UITableViewDataSource { 44 | // Do some networking, pull in some data, make it possible to generate cells 45 | func getData() { 46 | [...] 47 | } 48 | 49 | [...] 50 | } 51 | 52 | class ORArtworkViewController: UITableViewController { 53 | 54 | // Allows another class to change the dataSource, 55 | // but also will fall back to ORArtworkDataSource() 56 | // when not set 57 | lazy var dataSource: ORArtworkDataSourcable = { 58 | return ORArtworkDataSource() 59 | }() 60 | 61 | [...] 62 | override func viewDidLoad() { 63 | tableView.dataSource = dataSource 64 | dataSource.getData() 65 | } 66 | } 67 | 68 | ``` 69 | 70 | This allows you to create a new object that conforms to `ORArtworkDataSourcable` which can have different behaviour in tests. For example: 71 | 72 | ```swift 73 | it("shows a tableview cell") { 74 | subject = ORArtworkViewController() 75 | subject.dataSource = ORStubbedArtworkDataSource() 76 | // [...] 77 | expect(subject.tableView.cellForRowAtIndexPath(index)).to( beTruthy() ) 78 | } 79 | ``` 80 | 81 | There is a great video from Apple called [Protocol-Oriented Programming in Swift](https://developer.apple.com/videos/play/wwdc2015/408/) that covers this topic, and more. The video has a great example of showing how you can test a graphic interface by comparing log files because of the abstraction covered here. 82 | 83 | The same principles occur in Objective-C too, don't think this is a special Swift thing, the only major new change for Swift is the ability for a protocol to offer methods, allowing for a strange kind of multiple inheritence. 84 | 85 | Examples in practice, mostly Network models: 86 | 87 | * Eigen - [ARArtistNetworkModel](https://github.com/artsy/eigen/blob/da011cb4e0cd45e9148e89b92a4021ea3651753f/Artsy/Networking/Network_Models/ARArtistNetworkModel.h) is a protocol which `ARArtistNetworkModel` and `ARStubbedArtistNetworkModel` conform to. Here are some [tests using the technique.](https://github.com/artsy/eigen/blob/da011cb4e0cd45e9148e89b92a4021ea3651753f/Artsy_Tests/View_Controller_Tests/Artist/ARArtistViewControllerTests.m#L25) 88 | 89 | * Eidolon - [BidderNetworkModel](https://github.com/artsy/eidolon/blob/16867a8de52fdf24db07937be003b6104c0ee5e9/Kiosk/Bid%20Fulfillment/BidderNetworkModel.swift) `BidderNetworkModelType` is a protocol that `BidderNetworkModel` and `StubBidderNetworkModel` conform to. Here are [tests using the technique](https://github.com/artsy/eidolon/blob/16867a8de52fdf24db07937be003b6104c0ee5e9/KioskTests/Bid%20Fulfillment/LoadingViewModelTests.swift) 90 | -------------------------------------------------------------------------------- /chapters/en-UK/app_testing/user_interactions.md: -------------------------------------------------------------------------------- 1 | 2 | ### UIButtons 3 | ### UIGestures 4 | ### Target Action 5 | -------------------------------------------------------------------------------- /chapters/en-UK/app_testing/views__snapshots.md: -------------------------------------------------------------------------------- 1 | ## Snapshot Testing 2 | 3 | The process of taking a snapshot of the view hierarchy of a UIView subclass, then storing that as a reference for what your app should look like. 4 | 5 | ### Why Bother 6 | 7 | TLDR: Fast, easily re-producible tests of visual layouts. If you want a longer introduction, you should read my objc.io article about [Snapshot Testing](https://www.objc.io/issues/15-testing/snapshot-testing/). I'll be assuming some familiarity from here on in. 8 | 9 | ### Techniques 10 | 11 | We aim for snapshot tests to cover two main areas 12 | 13 | 1. Overall state for View Controllers 14 | 2. Individual States per View Component 15 | 16 | As snapshots are the largest testing brush, and as the apps I work on tend to be fancy perspectives on remote data. Snapshot testing provides easy coverage, for larger complex objects like view controllers where we can see how all the pieces come together. They can then provide a finer grained look at individual view components. 17 | 18 | Let's use some real-world examples. 19 | 20 | #### A simple UITableViewController subclass 21 | 22 | I have a `UITableViewController` subclass that shows the bid history of a live auction, `LiveAuctionBidHistoryViewController`. It receives a collection of events and presents each as a different looking cell. 23 | 24 | [LiveAuctionBidHistoryViewController]() - [Real Tests]() 25 | 26 | TODO: Add links ^ 27 | 28 | #### Custom Cells 29 | 30 | Ideally you should have _every_ state covered at View level. This means for every possible major style of the view, we want to have a snapshot covering it 31 | 32 | ```swift 33 | class LiveAuctionBidHistoryViewControllerTests: QuickSpec { 34 | [...] 35 | override func spec() { 36 | describe("cells") { 37 | var subject: LiveAuctionHistoryCell! 38 | 39 | it("looks right for open") { 40 | let event = LiveEvent(JSON: ["type" : "open", "id" : "OK"]) 41 | 42 | subject = self.setupCellWithEvent(event) 43 | expect(subject).to( haveValidSnapshot() ) 44 | } 45 | 46 | it("looks right for closed") { [...] } 47 | it("looks right for bid") { [...] } 48 | it("looks right for final call") { [...] } 49 | it("looks right for fair warning") { [...] } 50 | } 51 | } 52 | } 53 | ``` 54 | 55 | This means we have a good coverage of all the possible states for the data. This makes it easy to do code-review as it shows the entire set of possible styles for your data. 56 | 57 | The View Controller is where it all comes together, in this case, the View Controller isn't really doing anything outside of showing a collection of cells. It is given a collection of items, it does the usual `UITableView` datasource and delegate bits and it shows the history. Simple. 58 | 59 | So for the View Controller, we only need a simple test: 60 | 61 | ``` swift 62 | class LiveAuctionBidHistoryViewControllerTests: QuickSpec { 63 | override func spec() { 64 | describe("view controller") { 65 | it("looks right with example data") { 66 | let subject = LiveAuctionBidHistoryViewController() 67 | 68 | // Triggers viewDidLoad (and the rest of the viewXXX methods) 69 | subject.beginAppearanceTransition(true, animated: false) 70 | subject.endAppearanceTransition() 71 | 72 | subject.lotViewModel = StubbedLotViewModel() 73 | expect(subject).to( haveValidSnapshot() ) 74 | } 75 | } 76 | } 77 | } 78 | ``` 79 | 80 | This may not show all the different types of events that it can show, but those are specifically handled by the View-level tests, not at the View Controller. 81 | 82 | ### A Non-Trivial View Controller 83 | 84 | TODO: Energy's `AREditAlbumArtistViewController`` 85 | 86 | ### Common issues with Testing View Controllers 87 | 88 | Turns out to really grok why some problems happen you have to have quite a solid foundation in: 89 | 90 | * View Controller setup process - e.g.`viewDidLoad`, `viewDid/WillAppear` etc. 91 | * View Controller Containment - e.g. `childViewControllers`, `definesPresentationContext` etc. 92 | 93 | This is not useless, esoteric, knowledge though. Having a firmer understanding of this process means that you will probably write better code. 94 | -------------------------------------------------------------------------------- /chapters/en-UK/async/animations.md: -------------------------------------------------------------------------------- 1 | # Animations 2 | 3 | Animations are notorious for being hard to test. The problem arises from the fact that normally an animation is a fire and forget change that is handled by Apple. 4 | 5 | ### UIView animations 6 | 7 | One way that we deal with animations in tests is by having a strict policy on always including a `animates:` bool on any function that could contain animation. We mix this with a CocoaPod that makes it easy to do animations with a boolean flag, [UIView+BooleanAnimations](https://github.com/ashfurrow/UIView-BooleanAnimations/). This provides the UIView class with an API like: 8 | 9 | ``` objc 10 | [UIView animateIf:animates duration:ARAnimationDuration :^{ 11 | self.relatedTitle.alpha = 1; 12 | }]; 13 | ``` 14 | 15 | Which gives the control whether to animate in a test or not to the `animates` BOOL. If this is being called inside a `viewWillAppear:` method for example, then you already have a bool to work with. 16 | 17 | ### Core Animation 18 | 19 | It can be tough to test a core animation 20 | -------------------------------------------------------------------------------- /chapters/en-UK/async/dispatch_asyncs__ar_dispatch_etc.md: -------------------------------------------------------------------------------- 1 | ## Techniques for avoiding Async Testing 2 | 3 | Ideally an app should be running on multiple threads, with lots of work being done in the background. This means you can avoid having the user waiting for things to happen. A side effect of this is that asynchronous code can be difficult to test, as the scope for all your objects quickly collapses, and you never get callbacks in time. 4 | 5 | For me there are three main ways to deal with this, in order of preference: 6 | 7 | * Make asynchronous code run synchronously 8 | * Make your main test thread wait while the work is done in the background 9 | * Use a testing frameworks ability to have a run loop checking for a test pass 10 | 11 | One of the big downsides of using Asynchronous testing is that it can be _extremely time consuming_ trying to figure out why a test is failing, or flaky. Especially when it may only happen on CI that could have a weaker processor, less memory or be on a different OS version. 12 | 13 | An unreliable test is really, really hard to figure out, especially when you can't get a simple stack trace or log. Doubly so if it's not reproducible on a developer's computer. So I put in as much effort as possible to ensure that a test starts and ends during the `it`'s initial scope. 14 | 15 | If you're the one pitching that it's worth writing tests, you're the one that has to figure out what some obvious-ish code isn't working. For me, after years, Asynchronous tests are the one to blame the most. 16 | 17 | ### Get in line 18 | 19 | A friend in Sweden passed on a technique he was using to cover complex series of background jobs. In testing where he would typically use `dispatch_async` to run some code he would instead use `dispatch_sync` or just run a block directly. I took this technique and turned it into a [simple library][ar_dispatch] that allows you to toggle all uses of these functions to be asychronous or not. 20 | 21 | This is not the only example, we built this idea into a network [abstraction layer][moya] library too. If you are making stubbed requests then they happen synchronously. This reduced complexity in our testing. 22 | 23 | ### It will happen 24 | 25 | Testing frameworks typically have two options for running asynchronous tests, within the matchers or within the testing scaffolding. For example in Specta/Expecta you can use Specta's `waitUntil()` or Expecta's `will`. 26 | 27 | #### Wait Until 28 | 29 | `waitUntil` is a simple function that blocks the main thread that your tests are running on. Then after a certain amount of time, it will allow the block inside to run, and you can do your check. This method of testing will likely result in slow tests, as it will take the full amount of required time unlike Expecta's `will`. 30 | 31 | #### Will 32 | 33 | A `will` looks like this: `expect(x).will.beNil();`. By default it will fail after 0.3 seconds, but what will happen is that it constantly runs the expectation during that timeframe. In the above example it will keep checking if `x` is nil. If it succeeds then the checks stop and it moves on. This means it takes as little time as possible. 34 | 35 | ### Downsides 36 | 37 | Quite frankly though, async is something you should be avoiding. From a pragmatic perspective, I'm happy to write extra code in my app's code to make sure that it's possible to run something synchronously. 38 | 39 | For example, we expose a `Bool` called `ARPerformWorkAsynchronously` in eigen, so that we can add `animated:` flags to things that would be notoriously hard to test. 40 | 41 | For example, here is some code that upon tapping a button it will push a view controller. This can either be tested by stubbing out a the navigationViewController ( or perhaps providing an easy to test subclass (fake)) or you can allow the real work to happen fast and verify the real result. I'd be happy with the fake, or the real one. 42 | 43 | ``` 44 | - (void)tappedArtworkViewInRoom 45 | { 46 | ARViewInRoomViewController *viewInRoomVC = [[ARViewInRoomViewController alloc] initWithArtwork:self.artwork]; 47 | [self.navigationController pushViewController:viewInRoomVC animated:ARPerformWorkAsynchronously]; 48 | } 49 | ``` 50 | 51 | [ar_dispatch]: https://github.com/orta/ar_dispatch 52 | [moya]: https://github.com/ashfurrow/Moya 53 | -------------------------------------------------------------------------------- /chapters/en-UK/async/networking_in_view_controllers__network_models.md: -------------------------------------------------------------------------------- 1 | # Network Models 2 | 3 | In another chapter, I talk about creating HTTP clients that converts async networking code into synchronous APIs. Another tactic for dealing with testing out your networking. 4 | 5 | There are lots of clever ways to test your networking, I want to talk about the simplest. From a View Controller's perspective, all networking should go through a networking model. 6 | 7 | A network model is a protocol that represents getting and transforming data into something the view controller can use. In your application, this will perform networking asynchronously - in tests, it is primed with data and synchronously returns those values. 8 | 9 | Let's look at some code before we add a network model: 10 | 11 | ``` swift 12 | class UserNamesTableVC: UITableViewController { 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | MyNetworkingClient.sharedClient().getUsers { users in 17 | self.names = users.map { $0.names } 18 | } 19 | } 20 | } 21 | ``` 22 | 23 | OK, so it accesses a shared client, which returns a bunch of users - as we only need the names, we map out the names from the users and do something with that. 24 | 25 | Let's start by defining the relationship between `UsersNameTableVC` and it's data: 26 | 27 | ``` swift 28 | protocol UserNamesTableNetworkModelType { 29 | func getUserNames(completion: ([String]) -> ()) 30 | } 31 | ``` 32 | 33 | Then we need to have an object that conforms to this in our app: 34 | 35 | ```swift 36 | class UserNamesTableNetworkModel: UserNamesTableNetworkModelType { 37 | 38 | func getUserNames(completion: ([String]) -> ()) { 39 | MyNetworkingClient.sharedClient().getUsers { users in 40 | completion(users.map {$0.name}) 41 | } 42 | } 43 | } 44 | ``` 45 | 46 | We can then bring this into our ViewController to handle pulling in data: 47 | 48 | ``` swift 49 | class UserNamesTableVC: UITableViewController { 50 | 51 | var network: UserNamesTableNetworkModelType = UserNamesTableNetworkModel() 52 | 53 | override func viewDidLoad() { 54 | super.viewDidLoad() 55 | network.getUserNames { userNames in 56 | self.names = userNames 57 | } 58 | } 59 | } 60 | ``` 61 | 62 | OK, so we've abstracted it out a little, this is very similar to what happened back in the Dependency Injection chapter. To use network models to their fullest, we want to make another object that conforms to the `UserNamesTableNetworkModelType` protocol. 63 | 64 | ``` swift 65 | class StubbedUserNamesTableNetworkModel: UserNamesTableNetworkModelType { 66 | 67 | var userNames = [] 68 | func getUserNames(completion: ([String]) -> ()) { 69 | completion(userNames) 70 | } 71 | } 72 | ``` 73 | 74 | Now in our tests we can use the `StubbedUserNamesTableNetworkModel` instead of the `UserNamesTableNetworkModel` and we've got synchronous networking, and really simple tests. 75 | 76 | ``` swift 77 | it("shows the same amount names in the tableview") { 78 | let stubbedNetwork = StubbedUserNamesTableNetworkModel() 79 | stubbedNetwork.names = ["gemma", "dmitry"] 80 | 81 | let subject = UserNamesTableVC() 82 | subject.network = stubbedNetwork 83 | 84 | // Triggers viewDidLoad (and the rest of the viewXXX methods) 85 | subject.beginAppearanceTransition(true, animated: false) 86 | subject.endAppearanceTransition() 87 | 88 | let rows = subject.tableView.numberOfRowsInSection(0) 89 | expect(rows) == stubbedNetwork.names.count 90 | } 91 | ``` 92 | 93 | This pattern has saved us a lot of trouble over a long time. It's a nice pattern for one-off networking issues, and can slowly be adopted over time. 94 | -------------------------------------------------------------------------------- /chapters/en-UK/async/techniques_for_getting_around_async_networking.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /chapters/en-UK/async/techniques_for_getting_around_async_testing.md: -------------------------------------------------------------------------------- 1 | ## Techniques for avoiding Async Networking 2 | 3 | We've already covered Network Models. So I need to delve a little bit harder in order to really drive this point home. Networking is one of the biggest reasons for needing to do async testing, and we want to do everything possible to stop that happening. 4 | 5 | The way I deal with this, is a little hacky. However, it is the best technique I have come up with so far. [This is the PR](TODO_Eigen_Networking_PR) for when I added this technique to Eigen. 6 | 7 | ### Stubbed Networking Client 8 | 9 | It's normal to create a centralised HTTP client, the HTTP client's responsibilities are generally: 10 | 11 | * Convert a path to a `NSURL` 12 | * Create `NSURLRequests` for` NSURL`s 13 | * Create networking operations for `NSURLRequests` 14 | * Start the networking operations 15 | 16 | We care about taking the last two responsibilities and making them act differently when in testing. 17 | 18 | ### Eigen 19 | 20 | Let's go through the process _simplified_ for how Eigen's stubbed networking HTTP client works. 21 | 22 | We want to have a networking client that can act differently in tests, so create a subclass of your HTTP client, in my case, the client is called `ArtsyAPI`. I want to call the subclass `ArtsyOHHTTPAPI` - as I want to use the library [OHHTTPStubs](https://github.com/AliSoftware/OHHTTPStubs) to make my work easier. 23 | 24 | ``` objc 25 | @interface ArtsyOHHTTPAPI : ArtsyAPI 26 | @end 27 | ``` 28 | 29 | You need to have a way to ensure in your tests that you are using this version in testing. This can be done via Dependency Injection, or as I did, by using different classes in a singleton method when the new class is available. 30 | 31 | ```objc 32 | + (ArtsyAPI *)sharedAPI 33 | { 34 | static ArtsyAPI *_sharedController = nil; 35 | static dispatch_once_t oncePredicate; 36 | dispatch_once(&oncePredicate, ^{ 37 | Class klass = NSClassFromString(@"ArtsyOHHTTPAPI") ?: self; 38 | _sharedController = [[klass alloc] init]; 39 | }); 40 | return _sharedController; 41 | } 42 | ``` 43 | 44 | Next up you need a _point of inflection_ with the generation of networking operations in the HTTP client. For `ArtsyAPI` that is this method: `- (AFHTTPRequestOperation *)requestOperation:(NSURLRequest *)request success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id JSON))success failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON))failureCallback` 45 | 46 | We want to override this function to work synchronously. So let's talk a little about how this will work. 47 | 48 | 1. #### Request Lookup 49 | 50 | We need an API to be able to declare a stubbed route, luckily for me OHHTTPStubs has been working on this problem for years. So I want to be able to build on top of that work, rather than write my own stubbed `NSURLRequest` resolver. 51 | 52 | After some digging into OHHTTPStubs, I discovered that it has a private API that does exactly what I need. 53 | 54 | ``` objc 55 | @interface OHHTTPStubs (PrivateButItWasInABookSoItMustBeFine) 56 | + (instancetype)sharedInstance; 57 | - (OHHTTPStubsDescriptor *)firstStubPassingTestForRequest:(NSURLRequest *)request; 58 | @end 59 | ``` 60 | 61 | This allows us to access all of the `OHHTTPStubsDescriptor` objects, and more importantly, find out which ones are in memory at the moment. We can use this lookup function to work with the `request` parameter in our inflection function. All in one simple line of code. 62 | 63 | ```objc 64 | OHHTTPStubsDescriptor *stub = [[OHHTTPStubs sharedInstance] firstStubPassingTestForRequest:request]; 65 | ``` 66 | 67 | 2. #### Operation Variables 68 | 69 | So we have request look-up working, next up is creating an operation. It's very likely that you will need to create an API compatible fake version of whatever you're working with. In my case, that's [AFNetworking](https://github.com/AFNetworking/AFNetworking) `NSOperation` subclasses. 70 | 71 | However, first, you'll need to pull out some details from the stub: 72 | 73 | ``` objc 74 | // Grab the response by putting in the request 75 | OHHTTPStubsResponse *response = stub.responseBlock(request); 76 | 77 | // Open the input stream for in JSON data 78 | [response.inputStream open]; 79 | 80 | id json = @[]; 81 | NSError *error = nil; 82 | if (response.inputStream.hasBytesAvailable) { 83 | json = [NSJSONSerialization JSONObjectWithStream:response.inputStream options:NSJSONReadingAllowFragments error:&error]; 84 | } 85 | ``` 86 | 87 | This gives us all the details we'll need, the `response` object will also contain things like `statusCode` and `httpHeaders` that we'll need for determining operation behaviour. 88 | 89 | 3. #### Operation Execution 90 | 91 | In my case, I wanted an operation that does barely anything. The best operation that does barely anything is the trusty `NSBlockOperation` - which is an operation which executes a block when something tells it to start. Easy. 92 | 93 | ``` objc 94 | @interface ARFakeAFJSONOperation : NSBlockOperation 95 | @property (nonatomic, strong) NSURLRequest *request; 96 | @property (nonatomic, strong) id responseObject; 97 | @property (nonatomic, strong) NSError *error; 98 | @end 99 | 100 | @implementation ARFakeAFJSONOperation 101 | @end 102 | ``` 103 | 104 | Depending on how you use the `NSOperation`s in your app, you'll need to add more properties, or methods in order to effectively fake the operation. 105 | 106 | For this function to be completed it needs to return an operation, so lets `return` a `ARFakeAFJSONOperation`. 107 | 108 | ```objc 109 | ARFakeAFJSONOperation *fakeOp = [ARFakeAFJSONOperation blockOperationWithBlock:^{ 110 | NSHTTPURLResponse *URLresponse = [[NSHTTPURLResponse alloc] initWithURL:request.URL statusCode:response.statusCode HTTPVersion:@"1.0" headerFields:response.httpHeaders]; 111 | 112 | if (response.statusCode >= 200 && response.statusCode < 205) { 113 | if (success) { success(request, URLresponse, json); } 114 | } else { 115 | if (failureCallback) { failureCallback(request, URLresponse, response.error, json); } 116 | } 117 | }]; 118 | 119 | fakeOp.responseObject = json; 120 | fakeOp.request = request; 121 | return (id)fakeOp; 122 | ``` 123 | 124 | So we create an operation, that either calls the `success` or the `failure` block in the inflected method depending on the data from inside the stub. Effectively closing the loop on our synchronous networking. From here, in the case of `ArtsyAPI` another object will tell the `ARFakeAFJSONOperation` to start, triggering the callback synchronously. 125 | 126 | 4. #### Request Failure 127 | 128 | Having a synchronous networking lookup system means that you can also detect when networking is happening when you don't have a stubbed request. 129 | 130 | We have a lot of code here, in order to provide some really useful advice to programmers writing tests. Ranging from a copy & paste-able version of the function call to cover the networking, to a full stack trace showing what function triggered the networking call 131 | 132 | TODO: Example of what one looks like 133 | 134 | With this in place, all your networking can run synchronously in tests. Assuming they all go through your point of inflection, it took us a while to iron out the last few requests that weren't. 135 | 136 | ### AROHHTTPNoStubAssertionBot 137 | 138 | I used a simplification of the above in a different project, to ensure that all HTTP requests we're stubbed. By using the same `OHHTTPStubs` private API, I could detect when a request was being ignored by the `OHHTTPStubs` singleton. Then I could create a stack trace and give a lot of useful information. 139 | 140 | ```objc 141 | @interface ARHTTPStubs : OHHTTPStubs 142 | @end 143 | 144 | @implementation ARHTTPStubs 145 | 146 | - (OHHTTPStubsDescriptor *)firstStubPassingTestForRequest:(NSURLRequest *)request 147 | { 148 | id stub = [super firstStubPassingTestForRequest:request]; 149 | if (stub) { return stub; } 150 | 151 | [... Logging out here] 152 | 153 | _XCTPrimitiveFail(spectaExample, @"Failed due to unstubbed networking."); 154 | return nil; 155 | } 156 | ``` 157 | 158 | Then I used "fancy" runtime trickery to change the class of the `OHHTTPStubs` singleton at runtime on the only part of the public API. 159 | 160 | ``` objc 161 | 162 | @interface AROHHTTPNoStubAssertionBot : NSObject 163 | + (BOOL)assertOnFailForGlobalOHHTTPStubs; 164 | @end 165 | 166 | @implementation AROHHTTPNoStubAssertionBot 167 | 168 | + (BOOL)assertOnFailForGlobalOHHTTPStubs 169 | { 170 | id newClass = object_setClass([OHHTTPStubs sharedInstance], ARHTTPStubs.class); 171 | return newClass != nil; 172 | } 173 | 174 | @end 175 | ``` 176 | 177 | This technique is less reliable, as it relies on whatever the underlying networking operation does. This is generally calling on a background thread, and so you lose a lot of the useful stack tracing. However, you do get some useful information. 178 | 179 | ### Moya 180 | 181 | Given that we know asynchronous networking in tests is trouble, for a fresh project we opted to imagine what it would look like to have networking stubs as a natural part of the API description in a HTTP client. The end result of this is [Moya](https://github.com/moya/moya). 182 | 183 | In Moya you have to provide stubbed response data for every request that you want to map using the API, and it provides a way to easily do synchronous networking instead. 184 | -------------------------------------------------------------------------------- /chapters/en-UK/async/will_and_xctest_6.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orta/pragmatic-testing/b2fcc710925034effacebdce4dd857f2db43ccda/chapters/en-UK/async/will_and_xctest_6.md -------------------------------------------------------------------------------- /chapters/en-UK/core_data/core_data.md: -------------------------------------------------------------------------------- 1 | # Core Data 2 | 3 | Core Data is just another dependency to be injected. It's definitely out of your control, so in theory you could be fine using stubs and mocks to control it as you would like. 4 | 5 | From my perspective though, I've been creating a blank in-memory `NSManagedObjectContext` for every test, for years, and I've been happy with this. 6 | 7 | ### Memory Contexts 8 | 9 | An in-memory context is a managed object context that is identical to your app's main managed object context, but instead of having a SQL `NSPersistentStoreCoordinator` based on the file system it's done in-memory and once it's out of scope it disappears. 10 | 11 | Here's the setup for our in-memory context in Folio: 12 | 13 | ```objectivec 14 | + (NSManagedObjectContext *)stubbedManagedObjectContext 15 | { 16 | NSDictionary *options = @{ 17 | NSMigratePersistentStoresAutomaticallyOption : @(YES), 18 | NSInferMappingModelAutomaticallyOption : @(YES) 19 | }; 20 | 21 | NSManagedObjectModel *model = [CoreDataManager managedObjectModel]; 22 | NSPersistentStoreCoordinator *persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model]; 23 | [... Add a memory store to the coordinator] 24 | 25 | NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; 26 | context.persistentStoreCoordinator = persistentStoreCoordinator; 27 | return context; 28 | } 29 | ``` 30 | 31 | This context will act the same as your normal context, but it's cheap and easy to fill. It's also going to run functions on the main-thread for you to if you use `NSMainQueueConcurrencyType` simplifying your work further. 32 | 33 | Having one of these is probably the first step for making tests against any code touching Core Data. 34 | 35 | ### Knowing when to DI functions 36 | 37 | It's extremely common to wrap the Core Data APIs, they're similar to XCTest and Auto Layout in that Apple provides a low-level standard library then everyone builds their own wrappers above it. 38 | 39 | The wrappers for Core Data tend to not be built with DI in mind, offering their own singleton access for a main managed object context. So you may need to send some PRs to allow passing an in-memory `NSManagedObjectContext` instead of a singleton. 40 | 41 | This means I ended up writing a lot of functions that looked like this: 42 | 43 | ```objectivec 44 | @interface NSFetchRequest (ARModels) 45 | 46 | /// Gets all artworks of an artwork container that can be found with current user settings with an additional scope predicate 47 | + (instancetype)ar_allArtworksOfArtworkContainerWithSelfPredicate:(NSPredicate *)selfScopePredicate inContext:(NSManagedObjectContext *)context defaults:(NSUserDefaults *)defaults; 48 | 49 | [...] 50 | ``` 51 | 52 | Which, admittedly would be much simpler in Swift thanks to default initialiser. However, you get the point. Any time you need to do fetches you need to DI the in-memory version somehow. This could be as a property on an object, or as an argument in a function. I've used both, a lot. 53 | 54 | ### Asserting on the Main Managed Object Context 55 | 56 | In my Core Data stack I use a `CoreDataManager` and the factory pattern. This means I can add some logic to my manager to raise an exception when it is running in tests. You can do this very easily by checking the `NSProcessInfo` class. 57 | 58 | ```objectivec 59 | 60 | static BOOL ARRunningUnitTests = NO; 61 | 62 | @implementation CoreDataManager 63 | 64 | + (void)initialize 65 | { 66 | if (self == [CoreDataManager class]) { 67 | NSString *XCInjectBundle = [[[NSProcessInfo processInfo] environment] objectForKey:@"XCInjectBundle"]; 68 | ARRunningUnitTests = [XCInjectBundle hasSuffix:@".xctest"]; 69 | } 70 | } 71 | 72 | + (NSManagedObjectContext *)mainManagedObjectContext 73 | { 74 | if (ARRunningUnitTests) { 75 | @throw [NSException exceptionWithName:@"ARCoreDataError" reason:@"Nope - you should be using a stubbed context in tests." userInfo:nil]; 76 | } 77 | [...] 78 | } 79 | ``` 80 | 81 | This is something you want to do early on in writing your tests. The later you do it, the larger the changes you will have to make to your existing code base. 82 | 83 | This makes it much easier to move to all objects to accept in-memory `NSManagedObjectContext` via Dependency Injection. It took me two days to migrate all of the code currently covered by tests to do this. Every now and again, years later, I start adding tests to an older area of the code-base and find that `mainManagedObjectContext` was still being called in a test. It's a great way to save yourself and others some frustrating debugging time in the future. 84 | 85 | 86 | ### Advantages of Testing with Core Data 87 | 88 | I like working with Core Data, remember that it is an object graph tool, and so being able to have a fully set up `NSManagedObjectContext` as a part of the arrangement in your test can make testing different states extremely easy, you can also save example databases from your app and move them into your tests as a version you could work against. 89 | 90 | Straight after setting up an in-memory store, we wanted to be able to quickly throw example data into our `NSManagedObjectContext`. The way we choose to do it was via a factory object. Seeing as the factory pattern works pretty well here, here's the sort of interface we created: 91 | 92 | ```objectivec 93 | @interface ARModelFactory : NSObject 94 | 95 | + (Artwork *)fullArtworkInContext:(NSManagedObjectContext *)context; 96 | + (Artwork *)partiallyFilledArtworkInContext:(NSManagedObjectContext *)context; 97 | + (Artwork *)fullArtworkWithEditionsInContext:(NSManagedObjectContext *)context; 98 | 99 | [...] 100 | @end 101 | ``` 102 | 103 | These would add an object into the context, and also let it return the newly inserted object in-case you had test-specific modifications to do. 104 | -------------------------------------------------------------------------------- /chapters/en-UK/core_data/core_data_migrations.md: -------------------------------------------------------------------------------- 1 | # Core Data Migrations 2 | 3 | The first time I released a patch release for the first Artsy App it crashed instantly, on every install. It turned out I didn't understand [Core Data Model Versioning](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreDataVersioning/Articles/Introduction.html). Now a few years on I grok the migration patterns better but I've still lived with the memories of that dark dark day. Since then I've had an informal rule of testing migrations with all the old build of Folio using a tool I created called [chairs](http://artsy.github.io/blog/2013/03/29/musical-chairs/) the day before submitting to the app store. 4 | 5 | Chairs is a tool to back up your application's documents and settings. This meant I would have backups from different builds and could have a simulator with data from past versions without having to compile and older build. 6 | 7 | The problem here is that the manual process takes a lot of time, is rarely done, and could be pretty easily automated. So I extracted the old sqlite stores from the older builds, added these files to my testing bundle as fixture data and starting writing tests that would run the migrations. 8 | 9 | Running the migration is a matter of applying the current `NSManagedObjectModel` to the old sqlite file if you are using lightweight migrations. 10 | 11 | ```objc 12 | NSManagedObjectContext *ARContextWithVersionString(NSString *string); 13 | 14 | SpecBegin(ARAppDataMigrations) 15 | 16 | __block NSManagedObjectContext *context; 17 | 18 | it(@"migrates from 1.3", ^{ 19 | expect(^{ 20 | context = ARContextWithVersionString(@"1.3"); 21 | }).toNot.raise(nil); 22 | expect(context).to.beTruthy(); 23 | expect([Artwork countInContext:context error:nil]).to.beGreaterThan(0); 24 | }); 25 | 26 | it(@"migrates from 1.3.5", ^{ 27 | expect(^{ 28 | context = ARContextWithVersionString(@"1.3.5"); 29 | }).toNot.raise(nil); 30 | expect(context).to.beTruthy(); 31 | expect([Artwork countInContext:context error:nil]).to.beGreaterThan(0); 32 | }); 33 | 34 | [...] 35 | 36 | 37 | SpecEnd 38 | 39 | NSManagedObjectContext *ARContextWithVersionString(NSString *string) { 40 | 41 | // Allow it to migrate 42 | NSDictionary *options = @{ 43 | NSMigratePersistentStoresAutomaticallyOption: @YES, 44 | NSInferMappingModelAutomaticallyOption: @YES 45 | }; 46 | 47 | // Open up the the _current_ managed object model 48 | NSError *error = nil; 49 | NSManagedObjectModel *model = [CoreDataManager managedObjectModel]; 50 | NSPersistentStoreCoordinator *persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model]; 51 | 52 | // Get an older Core Data file from fixtures 53 | NSString *storeName = [NSString stringWithFormat:@"ArtsyPartner_%@", string]; 54 | NSURL *storeURL = [[NSBundle bundleForClass:ARAppDataMigrationsSpec.class] URLForResource:storeName withExtension:@"sqlite"]; 55 | 56 | // Set the persistent store to be the fixture data 57 | if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) { 58 | NSLog(@"Error creating persistant store: %@", error.localizedDescription); 59 | @throw @"Bad store"; 60 | return nil; 61 | } 62 | 63 | // Create a stubbed context, check give it the old data, and it will update itself 64 | NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init]; 65 | context.persistentStoreCoordinator = persistentStoreCoordinator; 66 | return context; 67 | } 68 | 69 | ``` 70 | 71 | Nothing too surprising, but I think it's important to note that these tests are the slowest tests in the app that hosts them at a whopping 0.191 seconds. I'm very willing to trade a fraction of a second on every test run to know that I'm not breaking app migrations. 72 | 73 | These are tests that presume you still have people using older builds, every now and again when I'm looking at Analytics I check to see if any of these test can be removed. 74 | 75 | Finally, if you don't use Core Data you may still need to be aware of changes around model migrations when storing using `NSKeyedArchiver`. It is a lot harder to have generic future-proofed test cases like the ones described here. However, here is an example [in eigen](https://github.com/artsy/eigen/blob/2f3aae2c9db93348f845f5dbe87fc93712d04dcf/Artsy%20Tests/ArtworkTests.m#L90-L100). 76 | -------------------------------------------------------------------------------- /chapters/en-UK/foundations/dependency_injection.md: -------------------------------------------------------------------------------- 1 | # Dependency Injection 2 | 3 | Dependency Injection (DI, from here on in) is a way of dealing with how you keep your code concerns separated. On a more pragmatic level it is expressed elegantly in [Jame Shore's blog post](http://www.jamesshore.com/Blog/Dependency-Injection-Demystified.html) 4 | 5 | > Dependency injection means giving an object its instance variables. Really. That's it. 6 | 7 | This alone isn't really enough to show the problems that DI solves. So, let's look at some code and investigate what DI really means in practice. 8 | 9 | ### Dependency Injection in a function 10 | 11 | Lets start with the smallest possible example, a single function: 12 | 13 | ``` objc 14 | - (void)saveUserDetails 15 | { 16 | User *user = [[User currentUser] dictionaryRepresentation]; 17 | [[NSUserDefaults standardUserDefaults] setObject:user forKey:@"user"]; 18 | [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"injected"]; 19 | } 20 | ``` 21 | 22 | Testing this code can be tricky, as it relies on functions inside the `NSUserDefaults` and `User` class. These are the dependencies inside this function. Ideally when we test this code we want to be able to replace the dependencies with something specific to the test. There are many ways to start applying DI, but I think the easiest way here is to try and make it so that the function takes in its dependencies as arguments. In this case we are giving the function both the `NSUserDefaults` object and a `User` model. 23 | 24 | ``` objc 25 | - (void)saveUser:(User *)user inDefaults:(NSUserDefaults *)defaults 26 | { 27 | [defaults setObject:[user dictionaryRepresentation] forKey:@"user"]; 28 | [defaults setBool:YES forKey:@"injected"]; 29 | } 30 | ``` 31 | 32 | In Swift we can use default arguments to acknowledge that we'll most often be using the `sharedUserDefaults`as the `defaults` var: 33 | 34 | ``` swift 35 | func saveUser(user: User, defaults: Defaults = .standardUserDefaults()){ 36 | defaults.setObject(user.dictionaryRepresentation, forKey:"user") 37 | defaults.setBool(true, forKey:"injected") 38 | } 39 | ``` 40 | 41 | This little change in abstraction means that we can now insert our own custom objects inside this function. Thus, we can inject a new instance of both arguments and test the end results of them. Something like: 42 | 43 | ``` objc 44 | it(@"saves user defaults", ^{ 45 | NSUserDefaults *defaults = [[NSUserDefaults alloc] init]; 46 | User *user = [User stubbedUser]; 47 | UserArchiver *archiver = [[UserArchiver alloc] init]; 48 | 49 | [archiver saveUser:user inDefaults:defaults]; 50 | 51 | expect([user dictionaryRepresentation]).to.equal([defaults objectForKey:@"user"]); 52 | expect([defaults boolForKey:@"injected"]).to.equal(YES); 53 | }); 54 | ``` 55 | 56 | We can now easily test the changes via inspecting our custom dependencies. 57 | 58 | ## Dependency Injection at Object Level 59 | 60 | Let's expand our scope of using DI, a single function can use DI via its arguments, so then an object can expand its scope via instance variables. As the initial explanation said. 61 | 62 | ``` swift 63 | class UserNameTableVC: UITableViewController { 64 | var names: [String] = [] { 65 | didSet { 66 | tableView.reloadData() 67 | } 68 | } 69 | override func viewDidLoad() { 70 | super.viewDidLoad() 71 | MyNetworkingClient.sharedClient().getUserNames { newNames in 72 | self.names = newNames 73 | } 74 | } 75 | } 76 | ``` 77 | 78 | This example grabs some names via an API call, then sets the instance variable `names` to be the new value from the network. In this example the object that is outside of the scope of the `UserNameTableVC` is the `MyNetworkingClient`. 79 | 80 | This means that in order to easily test the view controller, we would need to stub or mock the `sharedClient()` function to return a different version based on each test. 81 | 82 | The easiest way to simplify this, would be to move the networking client into an instance variable. We can use Swift's default initialisers to set it as the app's default which means less glue code ( in Objective-C you would override a property's getter function with a default unless the instance variable has been set. ) 83 | 84 | ``` swift 85 | class UserNameTableVC: UITableViewController { 86 | var names: [String] = [] { 87 | [...] 88 | } 89 | 90 | var network: MyNetworkingClient = .sharedClient() 91 | 92 | override func viewDidLoad() { 93 | super.viewDidLoad() 94 | network.getUserNames { newNames in 95 | self.names = newNames 96 | } 97 | } 98 | } 99 | ``` 100 | This can result in simpler app code, and significantly easier tests. Now you can init a `UITableViewController` and set the `.network` with any version of the `MyNetworkingClient` before `viewDidLoad` is called, and you're all good. 101 | 102 | ## Dependency Injection at Global Scope 103 | 104 | #### Ambient Context 105 | 106 | When you have a group of objects that all need access to the same kind of dependencies. It can makes sense to bundle those dependencies into a single object. I generally call these context objects. Here's an example, directly from from [Artsy Folio](TODO_get_link): 107 | 108 | ``` objc 109 | [...] 110 | 111 | @interface ARSyncConfig : NSObject 112 | 113 | - (instancetype)initWithManagedObjectContext:(NSManagedObjectContext *)context 114 | defaults:(NSUserDefaults *)defaults 115 | deleter:(ARSyncDeleter *)deleter; 116 | 117 | @property (nonatomic, readonly, strong) NSManagedObjectContext *managedObjectContext; 118 | @property (nonatomic, readonly, strong) NSUserDefaults *defaults; 119 | @property (nonatomic, readonly, strong) ARSyncDeleter *deleter; 120 | 121 | @end 122 | ``` 123 | 124 | This object wraps a `NSManagedObjectContext`, a `NSUserDefaults` and a `ARSyncDeleter` into a single class. This means it can provide an ambient context for other objects. For example, this is a class that performs the analytics on a sync. 125 | 126 | ``` objc 127 | @implementation ARSyncAnalytics 128 | 129 | - (void)syncDidStart:(ARSync *)sync 130 | { 131 | [sync.config.defaults setBool:YES forKey:ARSyncingIsInProgress]; 132 | 133 | BOOL completedSyncBefore = [sync.config.defaults boolForKey:ARFinishedFirstSync]; 134 | [ARAnalytics event:@"sync_started" withProperties:@{ 135 | @"initial_sync" : @(completedSyncBefore) 136 | }]; 137 | } 138 | 139 | [...] 140 | ``` 141 | 142 | The `ARSyncAnalytics` doesn't have any instance variables at all, the sync object is DI'd in as a function argument. From there the analytics are set according to the `defaults` provided inside the `ARSync`'s context object. I believe the official name for this pattern is ambient context. 143 | 144 | 145 | Read more: 146 | 147 | http://www.bignerdranch.com/blog/dependency-injection-ios/ 148 | [http://www.objc.io/issue-15/dependency-injection.html](http://www.objc.io/issue-15/dependency-injection.html) 149 | -------------------------------------------------------------------------------- /chapters/en-UK/foundations/stubs_mocks_and_fakes.md: -------------------------------------------------------------------------------- 1 | ### Mocks 2 | 3 | A mock object is an object created by a library to emulate an existing object's API. In general there are two main types of mocks 4 | 5 | 1. _Strict Mocks_ - or probably just Mocks. These objects will only respond to what you define upfront, and will assert if they receive anything else. 6 | 7 | 1. _Nice (or Partial) Mocks_ which wrap existing objects. These mocks objects can define the methods that they should respond too, but will pass any function / messages they haven't been told about to the original. 8 | 9 | In Objective-C you can define mocks that act as specific instance of a class, conform to specific protocols or be a class itself. In Swift this is still all up in the air, given the language's strict type system. 10 | 11 | ### Stubs 12 | 13 | A stub is a method that is replaced at runtime with another implementation. It is common for a stub to not call the original method. It's useful in setting up context for when you want to use a known return value with a method. 14 | 15 | You can think of it as being method swizzling, really. 16 | 17 | ### Mocks and Stubs 18 | 19 | From a personal opinion I avoid stubbing and mocking code which is under my control. 20 | 21 | When you first get started, using Mocks and Stubs feel like the perfect tool for testing code, but it becomes unwieldy as it can quickly get out of sync with reality. They can be a great crutch, when you really can't figure out how to test something however. 22 | 23 | A great example of when to use stubbing is when dealing with an Apple class that you cannot easily replace or use your own copy. For example I regularly use partial mocks of `UIScreen` instances in order to emulate being on an iPad simulator when it's actually running on an iPhone simulator. This saves us time from running our test-suite twice, sequentially, on multiple simulators. 24 | 25 | When you own the code that you're working with, it can often be easier to use a fake. 26 | 27 | ### Fakes 28 | 29 | A Fake is an API compatible version of an object. That is it. Fakes are extremely easy to make in both Swift and Objective-C. 30 | 31 | In Objective-C fakes can be created easily using the loose typing at runtime. If you create an object that responds to the same selectors as the one you want to fake you can pass it instead by typecasting it to the original. 32 | 33 | In Swift the use of `AnyObject` is discouraged by the compiler, so the use of fudging types doesn't work. Instead, you are better off using a protocol. This means that you can rely on a different object conforming to the same protocol to make test code run differently. It provides a different level of coupling. 34 | 35 | For my favourite use case of Fakes, look at the chapter on Network Models, or Testing Delegates. 36 | -------------------------------------------------------------------------------- /chapters/en-UK/ops/creation_of_app-centric_it_blocks.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orta/pragmatic-testing/b2fcc710925034effacebdce4dd857f2db43ccda/chapters/en-UK/ops/creation_of_app-centric_it_blocks.md -------------------------------------------------------------------------------- /chapters/en-UK/ops/developer_operations_aka_automation.md: -------------------------------------------------------------------------------- 1 | # Developer Operations 2 | 3 | There are days when I get really excited about doing some dev-ops. There are some days I hate it. Let's examine some of the key parts of a day to day work flow for a pragmatic programmer: 4 | 5 | ## Single line commands 6 | 7 | We use a `Makefile` like its 1983. `Makefile`s are a very simple ancient mini-language built on top of shell scripts. These are commonly used to run tasks. We use them for a wide range of tasks: 8 | 9 | * Bootstrapping From a Fresh Repo Clone 10 | * Updating Mogenerator Objects from a .xcdatamodel 11 | * Updating App Storyboard Constants 12 | * Building the App 13 | * Cleaning the Apps build folder 14 | * Running Tests 15 | * Generating Version Info for App Store Deploys 16 | * Preparing for deploys 17 | * Deploying to HockeyApp 18 | * Making Pull Requests 19 | 20 | ## Code Review 21 | 22 | Code Review is an important concept because it enforces a strong deliverable. It is a statement of action and an explanation behind the changes. I use github for both closed and open source code, so for me Code Review is always done in the form of Pull Requests. Pulls Requests can be linked to, can close other issues on merging and have an extremely high-functioning toolset around code discussions. 23 | 24 | When you prepare for a code review it is a reminder to refactor, and a chance for you to give your changes a second look. It's obviously useful for when there are multiple people on a project ( in which case it is a good idea to have different people do code reviews each time ) but there’s also value in using code reviews to keep someone else in the loop for features that affect them. 25 | 26 | Finally Code Review is a really useful teaching tool. When new developers were expressed interest in working on the mobile team then I would assign them merge rights on smaller Pull Requests and explain everything going on. Giving the merger the chance to get exposure to the language before having to write any themselves. 27 | 28 | When you are working on your own it can be very difficult to maintain this, especially when you are writing projects on your own. A fellow Artsy programmer, [Craig Spaeth]() does this beautifully when working on his own, here are some example [pull requests](https://github.com/artsy/positron/pulls?utf8=✓&q=is%3Aclosed+is%3Apr+author%3Acraigspaeth+%40craigspaeth). Each Pull Request is an atomic set of changes so that he can see what the larger goal was each time. 29 | 30 | TODO: Craig's adddress ^ 31 | 32 | ## Continuous Integration 33 | 34 | Once you have some tests running you're going to want a computer to run that code for you. Pulling someone's code, testing it locally then merging it into master gets boring very quickly. There are three major options in the Continous Integration (CI) world, and they all have different tradeoffs. 35 | 36 | ### Self hosted 37 | 38 | #### Jenkins 39 | 40 | Jenkins is a popular language agnostic self-hosted CI server. There are many plugins for Jenkins around getting set up for github authentication, running Xcode projects and deploying to Hockey. It runs fine on a Mac, and you just need a Mac Mini set up somewhere that receives calls from a github web-hook. This is well documented on the internet. 41 | 42 | The general trade off here is that it is a high-cost on developer time. Jenkins is stable but requires maintenance around keeping up to date with Xcode. 43 | 44 | #### Buildkite.io 45 | 46 | Buildkite lets you run your own Travis CI-like CI system on your own hardware. This means easily running tests for Xcode betas. It differs from Jenkins in it’s simplicity. It requires significantly less setup, and require less maintenance overall. It is a program that is ran on your hardware 47 | 48 | #### Xcode Bots 49 | 50 | Xcode bots is still a bit of a mystery, though it looks like with it's second release it is now at a point where it is usable. I found them tricky to set up, and especially difficult to work with when working with a remote team and using code review. 51 | 52 | An Xcode bot is a service running on a Mac Mini, that periodically pings an external repository of code. It will download changes, run optional before and after scripts and then offer the results in a really beautiful user interface directly in Xcode. 53 | 54 | 55 | ### Services 56 | 57 | It's nice to have a Mac mini to hand, but it can be a lot of maintenance. Usually these are things you expect like profiles, certificates and signing. A lot of the time though it's problems with Apple's tooling. This could be Xcode shutting off halfway though a build, the Keychain locking up, the iOS simulator not launching or the test-suite not loading. For me, working at a company as an iOS developer I don't enjoy, nor want to waste time with issues like this. So I have a bias towards letting services deal with this for me. 58 | 59 | The flip-side is that you don't have much control, if you need bleeding-edge Xcode features, and you're not in control of your CI box, then you have to deal with no CI until the provider provides. 60 | 61 | #### Travis CI 62 | 63 | Travis CI is a CI server that is extremely popular in the Open Source World. We liked it so much in CocoaPods that we decided to include setup for every CocoaPod built with our toolchain. It is used by most programming communities due to their free-if-open-source pricing. 64 | 65 | Travis CI is configured entirely via a `.travis.yml` file in your repo which is a YAML file that lets you override different parts of the install/build/test script. It has support for local dependency caching. This means build times generally do not include pulling in external CocoaPods and Gems, making it extremely fast most of the time. 66 | 67 | I really like the system of configuring everything via a single file that is held in your repository. It means all the necessary knowledge for how your application is tested is kept with the application itself. 68 | 69 | ##### Circle CI 70 | 71 | We've consolidated on Circle CI for our Apps. It has the same `circle.yml` config file advantage as Travis CI, but our builds don't have to wait in an OSS queue. It also seems to have the best options for supporting simultaneous builds. 72 | 73 | ##### Bitrise.io 74 | 75 | Bitrise is a newcomer to the CI field and is focused exclusively on iOS. This is a great thing. They have been known to have both stable and betas builds of Xcode on their virtual machines. This makes it possible to keep your builds green while you add support for the greatest new things. This has, and continues to be be a major issue with Travis CI in the past. 76 | 77 | Bitrise differs from Travis CI in that it's testing system is ran as a series of steps that you can run from their website. Because of this it has a much lower barrier to entry. When given some of my simpler iOS Apps their automatic setup did everything required with no configuration. 78 | 79 | #### Build 80 | 81 | ## Internal Deployment 82 | 83 | We eventually migrated from Hockey for betas to Testflight. In part because it felt like it was starting to mature, and also because of a [bug/feature in iOS](). 84 | 85 | We deploy via Fastlane. 86 | 87 | TODO: Link to Eigen "App Launch Slow" 88 | 89 | ## iTunes deployment 90 | 91 | 2015 was a good year for deployment to the App Store, as [Felix Krause](http://www.krausefx.com) released a series of command line tools to do everything from generating snapshots to deploying to iTunes. This suite of tools is called [Fastlane](https://fastlane.tools) and I can’t recommend it enough. 92 | 93 | Getting past the mental barrier of presuming an iTunes deploy takes a long time means that you feel more comfortable releasing new builds often. More builds means less major breaking changes, reducing problem surface area between versions. 94 | -------------------------------------------------------------------------------- /chapters/en-UK/ops/fixtures_and_factories.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orta/pragmatic-testing/b2fcc710925034effacebdce4dd857f2db43ccda/chapters/en-UK/ops/fixtures_and_factories.md -------------------------------------------------------------------------------- /chapters/en-UK/ops/techniques_for_keeping_testing_code_sane.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orta/pragmatic-testing/b2fcc710925034effacebdce4dd857f2db43ccda/chapters/en-UK/ops/techniques_for_keeping_testing_code_sane.md -------------------------------------------------------------------------------- /chapters/en-UK/oss_libs/expanding_on_bdd_frameworks.md: -------------------------------------------------------------------------------- 1 | ### Custom Matchers 2 | -------------------------------------------------------------------------------- /chapters/en-UK/oss_libs/mocking_and_stubbing__ocmock_and_ocmockito_.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /chapters/en-UK/oss_libs/network_stubbing__ohttp_and_vcrurlconnection.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orta/pragmatic-testing/b2fcc710925034effacebdce4dd857f2db43ccda/chapters/en-UK/oss_libs/network_stubbing__ohttp_and_vcrurlconnection.md -------------------------------------------------------------------------------- /chapters/en-UK/prag_prog/improving_xcode.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orta/pragmatic-testing/b2fcc710925034effacebdce4dd857f2db43ccda/chapters/en-UK/prag_prog/improving_xcode.md -------------------------------------------------------------------------------- /chapters/en-UK/prag_prog/making_libraries_to_get_annoying_tests_out_of_your_app.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orta/pragmatic-testing/b2fcc710925034effacebdce4dd857f2db43ccda/chapters/en-UK/prag_prog/making_libraries_to_get_annoying_tests_out_of_your_app.md -------------------------------------------------------------------------------- /chapters/en-UK/prag_prog/using_xcode_pragmatically.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orta/pragmatic-testing/b2fcc710925034effacebdce4dd857f2db43ccda/chapters/en-UK/prag_prog/using_xcode_pragmatically.md -------------------------------------------------------------------------------- /chapters/en-UK/setup/getting_setup.md: -------------------------------------------------------------------------------- 1 | # Getting setup 2 | 3 | We're pragmatic, so we use [CocoaPods](http://cocoapods.org). It is a dependency manager for Cocoa projects, we're going to use it to pull in the required dependencies. If you're new to CocoaPods then read the [extensive guides website](http://guides.cocoapods.org) to help you get started. 4 | 5 | ### Adding a test Target 6 | 7 | If you don't have a test target in your application then you need to add one. This can be done by opening Xcode, clicking File in the menu-bar then going to `New` > `New Target`. You'll find the test target under Other in iOS. It's called `Cococa Touch Unit Testing Bundle`. Adding this to your project will add the required bundle files, and you should choose to test (be hosted by) your existing application. 8 | 9 | ### Setting up your Podfile 10 | 11 | I'm presuming you already have a `Podfile`, if you don't consult the [CocoaPods Getting Started](http://guides.cocoapods.org/using/getting-started.html) guide. We're going to make changes to add testing tools. This means adding a new section in the `Podfile`. These typically look like the following: 12 | 13 | ``` ruby 14 | target "App" 15 | pod 'ORStackView' 16 | [...] 17 | 18 | target "AppTests" do 19 | inherit! :search_paths 20 | 21 | pod 'Specta' 22 | pod 'Expecta' 23 | pod 'FBSnapshotTestCase' 24 | end 25 | ``` 26 | 27 | This links the testing Pods to only the test target. This inherits the app's CocoaPods (in this case ORStackView. ) CocoaPods will generate a second library for the testing pods Specta, Expecta and FBSnapshotTestCase. That is only linked to your Tests target. 28 | 29 | You can test that everything is working well together by either going to `Product > Test` in the menu or by pressing `⌘ + u`. This will compile your application, then run it and inject your testing bundle into the application. 30 | 31 | If that doesn't happen, it's likely that your Scheme is not set up correctly. Go to `Product > Scheme > Edit Scheme..` or press `⌘ + ⇧ + ,`. Then make sure that you have a valid test target set up for that scheme. 32 | -------------------------------------------------------------------------------- /chapters/en-UK/setup/how_i_got_started.md: -------------------------------------------------------------------------------- 1 | # How I got started 2 | 3 | Within the entire development team at Artsy, the iOS team was unique in that it didn't write tests. This is in part due to the issues around tooling, horror stories about lost productivity and a lack of non-trivial examples. 4 | 5 | The other part was that no-one else was doing it when they were a small team. You would hear about large companies with tens of employees creating a large testing structure, but very few small startups with a couple of programmers were talking about testing. 6 | 7 | We had experimented once or twice with adding testing to our applications, mainly doing Integration Testing with [KIF]((https://github.com/square/kif), but without team consensus the attempts fell flat. 8 | 9 | At the end of 2013 the [Bus Factor](http://en.wikipedia.org/wiki/Bus_factor) for all of the knowledge in the Artsy mobile apps became an all-time low. 10 | 11 | I was the only one with any knowledge of how our systems worked and what additional context anyone needed to know about making changes. I had been involved in all decisions, and all that domain knowledge was just in me. 12 | 13 | So, the mobile team expanded. Laura Brown and dB joined the mobile team from the web side. They helped raise our testing standards. From close-to zero, to something. 14 | 15 | These changes in our development culture helped turn a pretty insular team, into one that's world-renowned for it's documentation and accessibility of it's code-bases. We actively use blog-posts, videos and conference talks to document why and how things work in our team. 16 | 17 | When new members join, they have a wide variety of sources to understand how the team work, including this book - which helps to explain a lot of the decisions we made around testing methodologies and internal best-practices. 18 | 19 | Finally, I write tests because some day I will leave Artsy. I don't want those who have to continue the apps to remember me as the person who left a massive pile of hard-to-maintain code. Just little bits of that here and there, and the majority reasonably explained. 20 | -------------------------------------------------------------------------------- /chapters/en-UK/setup/introducing_tests_into_an_existing_application.md: -------------------------------------------------------------------------------- 1 | # Introducing Tests to an Existing App 2 | 3 | We introduced tests into the first Artsy iOS App around the time that it hit 100,000 lines of code (including `Pods/`). The app was the product of a hard and unmovable deadline. We pushed out two patch releases after that, then sat back to try and figure out how to switch make the app friendly to new developers. 4 | 5 | We introduced some ground rules: 6 | 7 | * All bug fixes get a test. 8 | * Nearly all new code gets tested. 9 | * Code you touch in the process should get cleaned, and tested. 10 | 11 | At the same time we agreed on a style change. Braces at the end of methods would move down to the next line. This meant we would know up-front whether code should be [considered legacy](http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052) or not. 12 | 13 | Needs tests 14 | ``` 15 | - (void)method { 16 | ... 17 | } 18 | ``` 19 | 20 | Covered 21 | ``` 22 | - (void)method 23 | { 24 | ... 25 | } 26 | ``` 27 | 28 | This style change was agreed on throughout our apps as a reminder that there was work to be done. 29 | 30 | I would still get started in an existing project without tests this way, make it obvious what is and what isn't under tests. Then start with some small bugs, think of a way to prove the bug wrong in code then add the test. 31 | 32 | Once we were confident with our testing flow from the smaller bugs. We discussed internally what parts of the app were we scared to touch. This was easy for us, and that was authentication for registering and logging in. 33 | 34 | It was a lot of hastily written code, as it had a large amount of callbacks and a lot of hundred line+ methods. That was the first thing to hit 100% test coverage. I'm not going to say it was easy, but I would have no issues letting people make changes there presuming the tests pass. 35 | 36 | This code became well tested, and eventually made its way out of the application and into a new CocoaPod on its own. A strategy for generating great Open Source. 37 | -------------------------------------------------------------------------------- /chapters/en-UK/setup/starting_a_new_application_and_using_tests.md: -------------------------------------------------------------------------------- 1 | # Introducing Tests to an New App 2 | 3 | Oh wow, this is like a dream for me. Let's say I needed to write our biggest app, Eigen, from scratch. So that's a big app, thousands of tests. With everything I know now up-front, what would I do? 4 | 5 | So, there's a healthy amount of restrictions in here. That's part of the point, stopping us from doing some of our biggest problems with the test-suite. 6 | 7 | The other thing, is that I'd have our CI servers perform additional metrics on the test logs. I work on a tool called [Danger](https://github.com/danger/danger) which makes it easy to enforce team cultural rules on CI builds. Some of those rules are in here. 8 | 9 | * **NSUserDefaults, Keychain, NSFileManager are all faked up-front.** 10 | 11 | This is because all of these will eventually leak into the developer's setup. Meaning tests can be different between developers. We have a series of Fakes for these classes in a library called [Forgeries](https://github.com/ashfurrow/forgeries) 12 | 13 | * **Use the `TZ` environment variable to ensure all runs of the testing suite has the same timezone.** 14 | 15 | This one causes flaky tests pretty often, especially when you have a team of remote developers. You can edit the Scheme for your app's tests, and add an Environment Variable named "TZ" with the value of "Europe/London". Now everyone is running their tests in Manchester. 16 | 17 | * **Synchronous Networking in Tests.** 18 | 19 | Eh, I've wrote a whole chapter or two on this, if you're not convinced after those, then a blurb won't change that. 20 | 21 | * **Any un-stubbed networking would be an instant `NSAssert` in tests.** 22 | 23 | We're doing this in all our apps, it catches a bunch of problems at the moment of test creation rather than later when it's harder to debug. 24 | 25 | * **I still would have a `ARPerformWorkAsynchronously` bool.** 26 | 27 | It's a constant battle against asynchronicity, and this, mixed with our [ar_dispatch](https://github.com/orta/ar_dispatch) and [UIView+Boolean](https://github.com/orta/uiview-boolean) are great tools in the battle. 28 | 29 | TODO: Verify URLs^ 30 | 31 | 32 | * **I would not include a mocking library** 33 | 34 | I think they have their place, but I'd like to see how long we can go without being forced into using one of these tools. 35 | 36 | * **I would try and ensure that our tests can run in a non-deterministic order.** 37 | 38 | Specta, the testing library, doesn't have a way of doing this, nor to my knowledge does Quick. However, I know _for sure_ that the test cases I have will only work in the same order that Xcode runs that as an implementation detail. Un-doing that would require some effort, and an improvement on tooling though. 39 | 40 | * **I would have a log parsers for Auto Layout errors, CGContext errors and tests that take a long time.** 41 | 42 | I would use [Danger](https://github.com/danger/danger) to look for Auto Layout errors, we see them all the time, and ignore them. We've done this for too long, and now we can't go back. Now we have the tech, but it's not worth the time investment to fix. 43 | 44 | I'd also debate banning `will` and `asyncWait` for adding extra complications to the test-suite. 45 | -------------------------------------------------------------------------------- /chapters/en-UK/what_is/how_can_i_be_pragmatic_with_my_testing.md: -------------------------------------------------------------------------------- 1 | ### What is Pragmatic Testing? 2 | 3 | Testing is meant to help you sleep at night, and feel nonchalant about shipping a build anytime. However, you want to be able to launch your test-suite, and not feel like it's a major chore. If you try to test every `if` in your app, you're going to have a bad time. 4 | 5 | My favourite WWDC video come from 2011, now too old to be in the search index on the website. It's called [Writing Easy-To-Change Code: Your Second-Most Important Goal As A Developer](https://developer.apple.com/videos/play/wwdc2011/112/). It talks about all the different ways in which you can build your codebase to evolve safely. Your tests shouldn't be getting in the way of your evolving product. Test coverage is about the constant battle between "perfect" and "enough", Pragmatic Testing is about siding towards "enough" more often. 6 | 7 | Sometimes you will need to say "I don't need to write tests for this." There _will_ be times when you'll be burned for that decision, and that's OK. It's much harder to simplify a time-expensive test-suite, and it's extremely time intensive to cover 100% of your codebase. You're [shipping apps, not codebases](http://artsy.github.io/blog/2015/08/31/Cocoa-Architecture-Dropped-Design-Patterns/). At the end of the day, the tests are for developers, even when it's very likely to increase the quality of the product. 8 | 9 | An easy example is model objects, when you have objects that have some simple functions that will probably never change, it's not worth testing the functions. If you have functions that do some complicated logic, you should make sure that's covered by multiple tests showing different inputs and outputs. 10 | 11 | Internally we talk about coverage by using a painting analogy. There are some tools that allow you to test a lot of logic at once, for example a snapshot test for a view controller. In doing a Snapshot test, you're testing: object initialization, `viewDidLoad` behavior, `viewDid/WillAppear` behavior, subview layouting and many more systems. On the other hand you have unit tests, which are a much finer brush for covering the edge cases which snapshots won't or can't cover. 12 | 13 | By using multiple testing techniques, you can cover the shapes of your application, but still offers the chance to have fewer places to change as you evolve your application. 14 | -------------------------------------------------------------------------------- /chapters/en-UK/what_is/what_and_why_of_the_book.md: -------------------------------------------------------------------------------- 1 | ## What is this book? 2 | 3 | This is a book that aims to be a down to earth guide to testing iOS applications. It came out of a long period of writing tests to multiple non-trivial Apps whilst working at [Artsy](http://artsy.net). All of which are open source, and available for inspection on the [Artsy Open Source page](http://artsy.github.io/open-source/#ios). 4 | 5 | I found very few consolidated resources for testing in iOS general. A lot of the best book advice revolved around reading books about Java and C# and applying the techniques to Objective-C projects. Like any creative output this book does not live in a vacuum, the books I used to get to this point are in the recommendations section. 6 | 7 | Finally this is not a generic iOS Testing book. I will not be objective. This is a pragmatic book from a pragmatic programmer known for making things, not architecting beautiful concepts. There will be things you disagree with, and I'm of the _strong opinions, weakly held_ camp, so you're welcome to send me feedback as issues on [orta/pragmatic-testing](https://github.com/orta/pragmatic-testing) 8 | 9 | I treat this book very similar to how I would a collection of smaller blog posts, so I aim to have it well hyperlinked. There are a lot of great resources out there, and this can send you out into other resources. I'd rather not re-write someone when I can quote. 10 | 11 | ## About the author, and contributors 12 | 13 | I'm the head of mobile at Artsy, and I gave myself the title Design Dictator at the open source project [CocoaPods](https://cocoapods.org). My interest in testing piqued when the entire Artsy mobile team became just me, and I realized that I'm going to get the blame for everything from this point forward. Better up my game. 14 | 15 | There are a lot of times that I say we, meaning the [Artsy Mobile team](https://github.com/artsy/mobile/). I don't think I would be writing this book without these people contributing testing ideas to our codebase. Thanks, Daniel Doubrovkine, Laura Brown, Ash Furrow, Eloy Durán, Sarah Scott, Maxim Cramer & Dustin Barker. I owe you all. 16 | 17 | Finally, I want to thank Danger. She gives me time and space to achieve great things. I wouldn't be the person I am without her. 18 | 19 | ## Who is it for? 20 | 21 | Anyone interested in applying tests to iOS applications. Which hopefully should be a large amount of people. I'm trying to aim this book at myself back in 2012, new to the ideas of testing and seeing a whole world of possibilities, but not knowing exactly where to start or how to continue once I've made one or two tests. 22 | 23 | ## Swift or Objective-C? 24 | 25 | It's easy to get caught up in what's new and shiny, but in reality there's a lot of existing Objective-C code-bases out there. I will aim to try and cover both Swift and Objective-C. As we have test-suites in both languages, some concepts work better in one language vs the other. If you can only read one language, I'm not apologising for that. It's not pragmatic to only focus on one language, a language is a language, in time you should know as many as possible. 26 | -------------------------------------------------------------------------------- /chapters/en-UK/wrap_up/books.md: -------------------------------------------------------------------------------- 1 | ### Books for Further Reading 2 | 3 | ### [The Pragmatic Programmer](https://en.wikipedia.org/wiki/The_Pragmatic_Programmer) 4 | 5 | Super useful for having solid foundations as a programmer. This book's name came from this book. You know what though, I've not read in about a decade, I should probably re-read it. 6 | 7 | ### [Working Effectively With Legacy Code](http://www.amazon.co.uk/gp/product/B005OYHF0A/) 8 | 9 | The basic pitch is that any un-tested code is legacy, and to add tests you'll probably have to do maintenance on those sections of code in order to make it testable. 10 | 11 | It comes with some useful terminology (Seams, Inflection Points) that make it easier to visualise how code composes together, and how you can find ways to address applying tests to existing code-bases. 12 | 13 | ### [Growing Object-Orienteds Software, Guided by Tests](http://www.amazon.co.uk/Growing-Object-Oriented-Software-Guided-Signature/dp/0321503627/ref=sr_1_1?ie=UTF8&qid=undefined&sr=8-1&keywords=growing+object-oriented+software+guided+by+tests) 14 | 15 | The story of the creation of Test Driven Development. This will help put a lot of ideas into the larger context of software development. Someone had to come up with these ideas, but how and why are usually left out. I left them out, for example. 16 | 17 | ### [Test-Driven iOS Development](https://www.amazon.com/Test-Driven-iOS-Development-Developers-Library/dp/0321774183) 18 | 19 | TODO: Add description of the book 20 | -------------------------------------------------------------------------------- /chapters/en-UK/wrap_up/recommended_websites.md: -------------------------------------------------------------------------------- 1 | ## Recommended Websites 2 | 3 | | Link | Concept | 4 | | --- | --- | 5 | | http://qualitycoding.org | Jon Reid's blog on testing strategies. | 6 | | http://iosunittesting.com | Ron Lisle's blog on TDD, Unit Testing and creating bug free code. | 7 | | [objc.io issue 15](http://www.objc.io/issue-15/index.html) | The objc.io issue on testing. | 8 | -------------------------------------------------------------------------------- /chapters/en-UK/wrap_up/twitter_follows.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orta/pragmatic-testing/b2fcc710925034effacebdce4dd857f2db43ccda/chapters/en-UK/wrap_up/twitter_follows.md -------------------------------------------------------------------------------- /chapters/en-UK/xctest/behavior_testing.md: -------------------------------------------------------------------------------- 1 | ### Behaviour Driven Development 2 | 3 | Behaviour Driven Development (BDD) is something that grew out of Test Driven Development (TDD). TDD is a practice and BDD expands on it, but only really is about trying to provide a consistent vocabulary for how tests are described. 4 | 5 | This is easier to think about when you compare the same tests wrote in both XCTest ( which has it's own structure for writing tests) and move towards a BDD appraoch. So lets take some tests from the Swift Package Manager [ModuleTests.swift](https://github.com/apple/swift-package-manager/blob/5040f9ebe6686e7f07be6fbae50dcf942584902c/Tests/Transmute/ModuleTests.swift#L35) which uses plain old XCTest. 6 | 7 | ```swift 8 | class ModuleTests: XCTestCase { 9 | 10 | func test1() { 11 | let t1 = Module(name: "t1") 12 | let t2 = Module(name: "t2") 13 | let t3 = Module(name: "t3") 14 | 15 | t3.dependsOn(t2) 16 | t2.dependsOn(t1) 17 | 18 | XCTAssertEqual(t3.recursiveDeps, [t2, t1]) 19 | XCTAssertEqual(t2.recursiveDeps, [t1]) 20 | } 21 | 22 | func test2() { 23 | let t1 = Module(name: "t1") 24 | let t2 = Module(name: "t2") 25 | let t3 = Module(name: "t3") 26 | let t4 = Module(name: "t3") 27 | 28 | t4.dependsOn(t2) 29 | t4.dependsOn(t3) 30 | t4.dependsOn(t1) 31 | t3.dependsOn(t2) 32 | t3.dependsOn(t1) 33 | t2.dependsOn(t1) 34 | 35 | XCTAssertEqual(t4.recursiveDeps, [t3, t2, t1]) 36 | XCTAssertEqual(t3.recursiveDeps, [t2, t1]) 37 | XCTAssertEqual(t2.recursiveDeps, [t1]) 38 | } 39 | 40 | func test3() { 41 | let t1 = Module(name: "t1") 42 | let t2 = Module(name: "t2") 43 | let t3 = Module(name: "t3") 44 | let t4 = Module(name: "t4") 45 | 46 | t4.dependsOn(t1) 47 | t4.dependsOn(t2) 48 | t4.dependsOn(t3) 49 | t3.dependsOn(t2) 50 | t3.dependsOn(t1) 51 | t2.dependsOn(t1) 52 | 53 | [...] 54 | This pattern of adding an extra `dependsOn`, 55 | and new t`X`s continues till it gets to test6 56 | } 57 | ``` 58 | 59 | Note: They are split up in Act, Arrange, Assert, but they do an awful lot of repeating themselves. As test bases get bigger, maybe it makes sense to start trying to split out some of the logic in your tests. 60 | 61 | So what about if we moved some of the logic inside each test out, into a section before, this simplifies out tests, and allows each test to have more focus. 62 | 63 | ```swift 64 | class ModuleTests: XCTestCase { 65 | let t1 = Module(name: "t1") 66 | let t2 = Module(name: "t2") 67 | let t3 = Module(name: "t3") 68 | let t4 = Module(name: "t4") 69 | 70 | func test1() { 71 | t3.dependsOn(t2) 72 | t2.dependsOn(t1) 73 | 74 | XCTAssertEqual(t3.recursiveDeps, [t2, t1]) 75 | XCTAssertEqual(t2.recursiveDeps, [t1]) 76 | } 77 | 78 | func test2() { 79 | t4.dependsOn(t2) 80 | t4.dependsOn(t3) 81 | t4.dependsOn(t1) 82 | t3.dependsOn(t2) 83 | t3.dependsOn(t1) 84 | t2.dependsOn(t1) 85 | 86 | XCTAssertEqual(t4.recursiveDeps, [t3, t2, t1]) 87 | XCTAssertEqual(t3.recursiveDeps, [t2, t1]) 88 | XCTAssertEqual(t2.recursiveDeps, [t1]) 89 | } 90 | 91 | func test3() { 92 | t4.dependsOn(t1) 93 | t4.dependsOn(t2) 94 | t4.dependsOn(t3) 95 | t3.dependsOn(t2) 96 | t3.dependsOn(t1) 97 | t2.dependsOn(t1) 98 | [...] 99 | } 100 | ``` 101 | 102 | This is great, we're not quite doing so much arranging, but we're definitely doing some obvious Acting and Asserting. The tests are shorter, more concise, and nothing is lost in the refactor. This is easy when you have a few immutable `let` variables, but gets complicated once you want to have your Arrange steps perform actions. 103 | 104 | Behaviour Driven Development is about being able to have a consistent vocabulary in your test-suites. BDD defines the terminology, so they're the same between BDD libraries. This means, if you use [Rspec](https://github.com/rspec/rspec), [Specta](https://github.com/specta/specta/), [Quick](https://github.com/quick/quick), [Ginkgo](https://github.com/onsi/ginkgo) and many others you will be able to employ similar testing structures. 105 | 106 | So what are these words? 107 | 108 | * `describe` - used to collate a collection of tests under a descriptive name. 109 | * `it` - used to set up a unit to be tested 110 | * `before/after` - callbacks to code that happens before, or after each `it` or `describe` within the current `describe` context. 111 | * `beforeAll/afterAll` - callbacks to run logic at the start / end of a describe context. 112 | 113 | They combine like this psuedocode version of [these tests: ARArtworkViewControllerBuyButtonTests.m](https://github.com/artsy/eigen/blob/2a14463c0bb1a14e9709496261f74622cca8b1e5/Artsy_Tests/View_Controller_Tests/Artwork/ARArtworkViewControllerBuyButtonTests.m#L16-L121): 114 | 115 | ``` swift 116 | describe("buy button") { 117 | beforeAll { 118 | // sets up a mock for a singleton object 119 | } 120 | 121 | afterAll { 122 | // stops mocking 123 | } 124 | 125 | before { 126 | // ensure we are in a logged out state 127 | } 128 | 129 | after { 130 | // clear all user credentials 131 | } 132 | 133 | it("posts order if artwork has no edition sets") { 134 | // sets up a view controller 135 | // taps a button 136 | // verifies what routes have been called 137 | } 138 | 139 | it("posts order if artwork has 1 edition set") { 140 | // [...] 141 | } 142 | 143 | it("displays inquiry form if artwork has multiple sets") { 144 | // [...] 145 | } 146 | 147 | it("displays inquiry form if request fails") { 148 | // [...] 149 | } 150 | } 151 | ``` 152 | 153 | By using BDD, we can effectively tell a story about what expectations there are within a codebase, specifically around this buy button. It tells you: 154 | 155 | * In the context of Buy button, it posts order if artwork has no edition sets 156 | * In the context of Buy button, it posts order if artwork has 1 edition set 157 | * In the context of Buy button, it displays inquiry form if artwork has multiple sets 158 | * In the context of Buy button, it displays inquiry form if request fails 159 | 160 | Yeah, the English gets a bit janky, but you can easily read these as though they were english sentences. That's pretty cool. These describe blocks can be nested, this makes contextualising different aspects of your testing suite easily. So you might end up with this in the future: 161 | 162 | * In the context of Buy button, when logged in, it posts order if artwork has no edition sets 163 | * In the context of Buy button, when logged in, it posts order if artwork has 1 edition set 164 | * In the context of Buy button, when logged in, it displays inquiry form if artwork has multiple sets 165 | * In the context of Buy button, when logged in, it displays inquiry form if request fails 166 | * In the context of Buy button, when logged out, it asks for a email if we don't have one 167 | * In the context of Buy button, when logged out, it posts order if no edition sets and we have email 168 | 169 | Where you can split out the `it` blocks into different `describes` called `logged in` and `logged out`. 170 | 171 | #### So what does this pattern give us? 172 | 173 | Well, first up, tests are readable, and are obvious in their dependents. 174 | 175 | The structure of how you make your tests becomes a matter of nesting `context` or `describes`s. This it's much harder to just name your tests: `test1`, `test2`, `test3` because you should easily be able to say them out loud. 176 | 177 | Being able to structure your tests as a hierarchy, making it easy to structure code to run `before`/`after` or `beforeAll`/`afterAll` at different points can be much simpler than having a collection of setup code in each test. This makes it easier for each `it` block to be focused on just the `arrange` and `assert`. 178 | 179 | I've never felt comfortable writing plain old XCTest formatted code, and so from this point on, expect to not see any more examples in that format. 180 | 181 | #### Matchers 182 | 183 | BDD only provides a lexicon for structuring your code, in all of the examples further on you'll see things like: 184 | 185 | ``` 186 | // Objective-C 187 | expect([item.attributeSet title]).to.equal(artist.gridTitle); 188 | 189 | // Swift 190 | expect(range.min) == 500 191 | ``` 192 | 193 | These types of expectations are not provided as a part of XCTest. XCTest provides a collection of ugly macros/functions like `XCTFail`, `XCTAssertGreaterThan` or `XCTAssertEqual` which does some simple logic and raises an error denoting the on the line it was called from. 194 | 195 | As these are pretty limited in what they can do, and are un-aesthetically pleasing, I don't use them. Instead I use a matcher library. For example Expecta, Nimble or OCHamcrest. These provide a variety of tools for creating test assertions. 196 | 197 | It's common for these libraries to be separate from the libraries doing BDD, in the Cocoa world, only Kiwi aims to do both BDD structures and matchers. 198 | 199 | From my perspective, there's only one major advantage to bundling the two, and that is that you can fail a test if there were no matchers ran ( e.g. an async test never called back in time. ) To my knowledge, only Rspec for ruby provides that feature. 200 | -------------------------------------------------------------------------------- /chapters/en-UK/xctest/integration_testing.md: -------------------------------------------------------------------------------- 1 | ## Integration Testing 2 | 3 | Integration Testing is a different concept to Unit Testing. It is the idea of testing changes in aggregate, as opposed to individual units. A good testing goal is to have a lot of the finer-grained ( thinner brush ) tests covered by unit testing, then Integration Testing will help you deal with larger ideas ( a paint roller. ) 4 | 5 | Within the context of Cocoa, integration tests generally means writing tests against things you have no control over. Which you could argue is all of UIKit, but hey, gotta do that to build an app. Seriously though, UIKit is the most common thing against which people have done integration testing. 6 | 7 | ### UI Testing 8 | 9 | UI Testing involves running your app as though there was a human on the other side tapping buttons, waiting for animations and filling in all of bits of data. The APIs make it easy to make tests like "If I've not added an email, is the submit button disabled?" and "After hitting submit with credentials, do it go to the home screen?" These let you write tests pretty quickly ( it's now built into Xcode ) and it can be used to provide a lot of coverage fast. 10 | 11 | The tooling for in the OSS world is pretty mature now. The dominant player is [Square's KIF](https://github.com/square/kif). [KIF's tests](https://github.com/mozilla/firefox-ios/blob/451665a7239c46cf2be3f47e3c903d88d2d710ec/UITests/ReaderViewUITests.swift#L8) generally look like this: 12 | 13 | ``` swift 14 | class ReaderViewUITests: KIFTestCase, UITextFieldDelegate { 15 | [...] 16 | func markAsReadFromReaderView() { 17 | tester().tapViewWithAccessibilityLabel("Mark as Read") 18 | tester().tapViewWithAccessibilityIdentifier("url") 19 | tester().tapViewWithAccessibilityLabel("Reading list") 20 | tester().swipeViewWithAccessibilityLabel("Reader View Test", inDirection: KIFSwipeDirection.Right) 21 | tester().waitForViewWithAccessibilityLabel("Mark as Unread") 22 | tester().tapViewWithAccessibilityLabel("Cancel") 23 | } 24 | [...] 25 | } 26 | ``` 27 | 28 | Where KIF will look or wait for specific views in the view hierarchy, then perform some actions. Apple's version of KIF, UITesting is similar, but different. 29 | 30 | It works by having a completely different test target just for UI Integration Tests, separate from your Unit Tests. It can build out your test-suite much faster, as it can record the things you click on in the simulator, and save the actions to your source files in Xcode. 31 | 32 | These tests look like vanilla XCTest, here's some examples from [Deck-Tracker](https://github.com/raiden007/Deck-Tracker/blob/aa6aba5dbfb2762f6e45aab9749c28fa5e8329c4/Deck%20TrackerUITests/About.swift) 33 | 34 | ``` swift 35 | class About: XCTestCase { 36 | 37 | let backButton = XCUIApplication().navigationBars["About"].buttons["Settings"] 38 | let aboutTitleScreen = XCUIApplication().navigationBars["About"].staticTexts["About"] 39 | let hearthstoneImage = XCUIApplication().images["Hearthstone About"] 40 | [...] 41 | 42 | override func setUp() { 43 | super.setUp() 44 | continueAfterFailure = false 45 | XCUIApplication().launch() 46 | let app = XCUIApplication() 47 | app.navigationBars["Games List"].buttons["More Info"].tap() 48 | app.tables.staticTexts["About"].tap() 49 | } 50 | 51 | func testElementsOnScreen() { 52 | XCTAssert(backButton.exists) 53 | XCTAssert(aboutTitleScreen.exists) 54 | XCTAssert(hearthstoneImage.exists) 55 | XCTAssert(versionNumberLabel.exists) 56 | XCTAssert(createdByLabel.exists) 57 | XCTAssert(emailButton.exists) 58 | XCTAssert(nounIconsLabel.exists) 59 | } 60 | [...] 61 | } 62 | 63 | ``` 64 | 65 | There are some good up-sides to this approach, it's really fast to set up and to re-create when something changes. It's a really wide-brushed approach to covering your tested. 66 | 67 | The biggest down-side is that it's slow. It requires running all the usual animations and networking would be performed as usual in the app. These can be worked around with some networking stubbing libraries, mainly VCR or HTTP Stubs but that adds a lot more complexity to what is generally a simple approach. 68 | 69 | ### API Testing 70 | 71 | If you have a staging environment for your API, it can be worth having your application run through a series of real-world networking tasks to verify the APIs which you rely on ( but don't necessarily maintain) continue to act in the way you expect. 72 | 73 | This can normally be built with KIF/UITesting, and can be tested 74 | -------------------------------------------------------------------------------- /chapters/en-UK/xctest/test_driven_development.md: -------------------------------------------------------------------------------- 1 | ### Test Driven Development 2 | 3 | It's been said recently [that Test Driven Development (TDD) is dead](TODO_tdd_is_dead). I'm not too sold on the idea that it's dead, personally. I think for the Cocoa community, TDD has barely even started. 4 | 5 | Test Driven Development is the idea of writing your application and your tests simultaneously. Just as you write out your app's code unit by unit, you cover each individual step with a test. The common pattern for this is: 6 | 7 | * Red 8 | * Green 9 | * Refactor 10 | 11 | This is the idea that, you start with a test, which will fail: 12 | 13 | ```swift 14 | it("has ten items") { 15 | let subject = KeyNumbers() 16 | expect(subject.items.count) == 10 17 | } 18 | ``` 19 | 20 | Then you do the minimum possible to get that test to pass: 21 | 22 | ``` swift 23 | class KeyNumbers: NSObject { 24 | let items = [0,1,2,3,4,5,6,7,8,9] 25 | } 26 | ``` 27 | 28 | This gives you a green (passing) test, from there you would refactor your code, now that you can verify the end result. 29 | 30 | You would then move on to the next test, which may verify the type of value returned or whatever you're really meant to be working on. The idea is that you keep repeating this pattern and at the end you've got a list of all your expectations in the tests. 31 | 32 | #### When to use TDD? 33 | 34 | TDD works really well when you have to work on a bug, you produce the test that represents the fixed bug first (red), then you can fix the bug (green) and finally you can clean up the code you've changed. (refactor.) 35 | 36 | I've found that TDD works well when I know a lot of the states of my view controllers up front, I would write something like this: 37 | 38 | ``` swift 39 | override func spec() { 40 | describe("cells") { 41 | var subject: LiveAuctionHistoryCell! 42 | 43 | pending("looks right for open") 44 | pending("looks right for closed") 45 | pending("looks right for bid") 46 | pending("looks right for final call") 47 | pending("looks right for fair warning") 48 | } 49 | } 50 | ``` 51 | 52 | Which eventually turned into: [LiveAuctionBidHistoryViewControllerTests.swift](https://github.com/artsy/eigen/blob/master/Artsy_Tests/View_Controller_Tests/Live_Auction/LiveAuctionBidHistoryViewControllerTests.swift) 53 | 54 | Which would give me an overview of the types of states I wanted to represent. Then I could work through adding snapshots of each state to the tests, in order to make sure I don't miss anything. 55 | 56 | In part, doing TDD with compiled languages can be tough, because you cannot easily compile against an API which doesn't exist. This makes the red step tricky. The best work around is to do stubbed data like my `items` example above. 57 | 58 | In ruby, I would write my tests first, in Cocoa I find this harder. So I'm not a true convert, this could be the decade of not doing tests providing a negative momentum though. Established habits die hard. There are definitely very productive programmers who do test first. 59 | -------------------------------------------------------------------------------- /chapters/en-UK/xctest/three_types_of_unit_tests.md: -------------------------------------------------------------------------------- 1 | # The Three Types of Unit Tests 2 | 3 | There are commonly three types of Unit Tests, we’ll be taking the examples directly from the source code of [Eigen](https://github.com/artsy/eigen/). Let’s go: 4 | 5 | ## Return Value 6 | 7 | ``` objc 8 | it(@"sets up its properties upon initialization", ^{ 9 | // Arrange + Act 10 | ARShowNetworkModel *model = [[ARShowNetworkModel alloc] initWithFair:fair show:show]; 11 | 12 | // Assert 13 | expect(model.show).to.equal(show); 14 | }); 15 | ``` 16 | 17 | > [ARShowNetworkModelTests.m](https://github.com/artsy/eigen/blob/6635bd8dc62186422ad6537dbc582e828bcb3776/Artsy%20Tests/ARShowNetworkModelTests.m#L18-L22) 18 | 19 | You can setup your subject of the test, make a change to it, and check the return value of a function is what you expect. This is what you think of when you start writing tests, and inevitably Model objects are really easy to cover this way due to their ability to hold data and convert that to information. 20 | 21 | ## State 22 | 23 | ``` objc 24 | it(@"changes selected to deselected", ^{ 25 | // Arrange 26 | ARAnimatedTickView *tickView = [[ARAnimatedTickView alloc] initWithSelection:YES]; 27 | 28 | // Act 29 | [tickView setSelected:NO animated:NO]; 30 | 31 | /// Assert 32 | expect(tickView).to.haveValidSnapshotNamed(@"deselected"); 33 | }); 34 | 35 | ``` 36 | > [ARAnimatedTickViewTest.m](https://github.com/artsy/eigen/blob/6635bd8dc62186422ad6537dbc582e828bcb3776/Artsy%20Tests/ARAnimatedTickViewTest.m#L27-L31) 37 | 38 | State tests work by querying the subject. In this case we’re using snapshots to investigate that the visual end result is as we expect it to be. 39 | 40 | These tests can be a little bit more tricky than straight return value tests, as they may require some kind of mis-direction depending on the public API for an object. 41 | 42 | ## Interaction Tests 43 | 44 | An interaction test is more tricky because it usually involves more than just one subject. The idea is that you want to test how a cluster of objects interact in order 45 | 46 | ``` objc 47 | it(@"adds Twitter handle for Twitter", ^{ 48 | 49 | // Arrange 50 | provider = [[ARMessageItemProvider alloc] initWithMessage:placeHolderMessage path:path]; 51 | 52 | // Act 53 | providerMock = [OCMockObject partialMockForObject:provider]; 54 | [[[providerMock stub] andReturn:UIActivityTypePostToTwitter] activityType]; 55 | 56 | // Assert 57 | expect([provider item]).to.equal(@"So And So on @Artsy"); 58 | }); 59 | ``` 60 | 61 | > [ARMessageItemProviderTests.m](https://github.com/artsy/eigen/blob/6635bd8dc62186422ad6537dbc582e828bcb3776/Artsy%20Tests/ARMessageItemProviderTests.m#L53-L61) 62 | 63 | In this case to test the interaction between the `ARMessageItemProvider` and the `activityType` we need to mock out a section of the code that does not belong to the domain we are testing. 64 | 65 | 66 | #### Full Details 67 | 68 | There is a [talk](https://www.youtube.com/watch?v=Jzlz3Bx-NzM) by Jon Reid of qualitycoding.org on this topic that is really the definitive guide to understanding how you can test a unit of code. 69 | 70 | TODO: Re-watch it and flesh this out a bit more 71 | -------------------------------------------------------------------------------- /chapters/en-UK/xctest/types_of_testing.md: -------------------------------------------------------------------------------- 1 | # Types of Testing 2 | 3 | A test is a way of verifying code, and making your assumptions explicit. Tests exist to show connections between objects. When you make a change somewhere, tests reveal implicit connections. 4 | 5 | In the Ruby world the tests are basically considered the application's documentation. This is one of the first port of calls for understanding how systems work after reading the overview/README. It's quite similar with node. Both languages are interpreted languages where even variable name typos only show themselves in runtime errors. Without a compiler, you can catch this stuff only with rigorous testing. 6 | 7 | In Cocoa, this is less of a problem as we can rely on the tooling more. Given a compiler, static type systems, and a well integrated static analyser -- you can know if your code is going to break someone else's very quickly. However, this is only a lint, the code compiling doesn't mean that it will still correctly show a view controller when you tap a button. In other words, the behaviour of your app can still be wrong. 8 | 9 | That is what testing is for. I will cover three major topics in this book. There are many, many other types of testing patterns -- for example, last week I was introduced to Model Testing. However, these three topics are the dominent patterns that exist within the Cocoa world, and I have experience with them. Otherwise, I'd just be copy & pasting from Wikipedia. 10 | 11 | ### Unit Testing 12 | 13 | Unit testing is the idea of testing a specific unit of code. This can range from a function, to a single Model to a full `UIViewController` subclass. The idea being that you are testing and verifying a unit of application code. Where you draw the boundary is really up to you, but for pragmatic reasons we'll mostly stick to functions and objects because they easily match our mental model of a "thing," that is, a Unit. 14 | 15 | ### Integration Testing 16 | 17 | Integration testing is a way of testing that your application code integrates well with external systems. The most common example of integration testing in iOS apps are user interface runners that emulate users tapping though you application. 18 | 19 | ### Behavioural Testing 20 | 21 | Behavioural testing is essentially a school of thought. The principles state that the way you write and describe your tests should reflect the behaviours of your objects. There is a controlled vocabulary using words like "it", "describe", "spec", "before" and "after" which mean that most BDD-testing frameworks all read very similar. 22 | 23 | This school of thought has also brought about a different style of test-driving the design of your app: instead of strictly focusing on a single unit and developing your app from the inside-out, behaviour-driven development sometimes favors working outside-in. This style could start with a failing integration test that describes a feature of the app and lead you to discover new Unit Tests on the way to make the feature test "green." Of course it's up to you how you utilize a BDD framework: the difference in typing test cases doesn't force you to change your habits. 24 | -------------------------------------------------------------------------------- /chapters/en-UK/xctest/unit_testing.md: -------------------------------------------------------------------------------- 1 | ### Unit Testing 2 | 3 | So, what is a unit of code? Well, that's subjective. I'd argue that it's anything you can easily, reliably measure. A unit test can generally be considered something that is set up (arrange), then you perform some action (act) and verify the end result (assert.) It's like science experiments, kinda. 4 | 5 | [Arrange, Act, Assert.](http://c2.com/cgi/wiki?ArrangeActAssert) 6 | 7 | We had some real-world examples from Swift Package Manager in the last chapter that were too small for doing Arrange, Act, Assert, so let's use [another example](https://github.com/apple/swift-package-manager/blob/30ea0a95ac18235c8f1fefae0fb8f3dc4512b55b/Tests/Utility/PathTests.swift#L96-L110): 8 | 9 | ```swift 10 | class WalkTests: XCTestCase { 11 | [...] 12 | 13 | func testRecursive() { 14 | let root = Path.join(#file, "../../../Sources").normpath 15 | var expected = [ 16 | Path.join(root, "Build"), 17 | Path.join(root, "Utility") 18 | ] 19 | 20 | for x in walk(root) { 21 | if let i = expected.indexOf(x) { 22 | expected.removeAtIndex(i) 23 | } 24 | } 25 | 26 | XCTAssertEqual(expected.count, 0) 27 | } 28 | [...] 29 | } 30 | ``` 31 | The first section, the author arranges the data-models as they expect them. Then in the `for` they act on that data. This is the unit of code being tested. Finally, they assert that the code has had the expected result. In this case, that the expected array has become empty. 32 | 33 | Using the Arrange, Act, Assert methodology, it becomes very easy to structure your test cases. It makes tests obvious to other practitioners, and you can determine code smells quicker by seeing large amounts of code in your arrange or assert sections. 34 | 35 | ### One Test, One Unit 36 | 37 | In the perfect theoretical world, every test case would be a single logical test of a unit of code. We're not talking theory here though, so from my perspective, [this](https://github.com/apple/swift-package-manager/blob/30ea0a95ac18235c8f1fefae0fb8f3dc4512b55b/Tests/Utility/PathTests.swift#L72-L78) is a great unit test: 38 | 39 | ``` swift 40 | func testParentDirectory() { 41 | XCTAssertEqual("foo/bar/baz".parentDirectory, "foo/bar") 42 | XCTAssertEqual("foo/bar/baz".parentDirectory.parentDirectory, "foo") 43 | XCTAssertEqual("/bar".parentDirectory, "/") 44 | XCTAssertEqual("/".parentDirectory.parentDirectory, "/") 45 | XCTAssertEqual("/bar/../foo/..//".parentDirectory.parentDirectory, "/") 46 | } 47 | ``` 48 | 49 | It shows a wide array of inputs, and their expected outputs for the same function. Someone looking over these tests would have a better idea of what `parentDirectory` does, and it covers a bunch of use cases. Great. 50 | 51 | Note, the [Quick](https://github.com/Quick/Quick) documentation really shines on [ArrangeActAssert](https://github.com/Quick/Quick/blob/d30f9e93402b6fcc7013c86afeb77da4c38e9f27/Documentation/en-us/ArrangeActAssert.md) - I would strongly recommend giving it a quick browse. 52 | 53 | Further reading: 54 | 55 | - [Quick/Quick](https://github.com/Quick/Quick/) documentation: [ArrangeActAssert.md](https://github.com/Quick/Quick/blob/d30f9e93402b6fcc7013c86afeb77da4c38e9f27/Documentation/en-us/ArrangeActAssert.md) 56 | -------------------------------------------------------------------------------- /chapters/en-UK/xctest/what_is_xctest_how_does_it_work.md: -------------------------------------------------------------------------------- 1 | # What is the XCTest framework? 2 | 3 | Now as a default when you create a new Xcode project apple creates a test target for you. Testing has been a part of Xcode since [OCUnit](http://www.sente.ch/software/ocunit/) the predecessor to XCTest was included Xcode 2.1. 4 | 5 | XCTest owes it's architectural decisions to [SUnit](http://sunit.sourceforge.net) the first testing framework, built for smalltalk apps. OCUnit is an Objective-C implementation of SUnit. 6 | 7 | The xUnit format is quite simple. There are collections of test-suites, which contain test cases and test cases contain individual tests. A test runner is created which loops through all suites, their test cases and runs specific methods. If running the method raises an exception then that test is considered a failure, and the runner moves to the next method. The final part is a logger to output the results. 8 | 9 | In XCTest the convention is that you subclass a XCTestCase object, and the test runner will call any method that begins with the word `test`. E.g. `- (void)testImageSpecifiesAspectRatio`. 10 | 11 | The actual implementation of XCTest works by creating a bundle, which can optionally be injected into an application ( Apple calls this hosting the tests. ) The bundle contains test resources like dummy images or JSON, and your test code. There is a version of [XCTest open source'd](https://github.com/apple/swift-corelibs-xctest) by Apple. 12 | 13 | XCTest provides a series of macros or functions [based on OCUnit's](https://github.com/jy/SenTestingKit/blob/master/SenTestCase_Macros.h#L82) for calling an exception when an expectation isn't met. For example `XCTAssertEqual`, `XCTFail` and `XCTAssertLessThanOrEqual`. You can explore how the XCT* functions work in [the OSS XCTest](https://github.com/apple/swift-corelibs-xctest/blob/96772ca6e01e664e153d0c844fff69e94605ef17/Sources/XCTest/XCTAssert.swift#L29-L45). 14 | 15 | Here's a real example of an XCTest case subclass taken from the [Swift Package Manager](https://github.com/mxcl/swift-package-manager/blob/aa1700c0b7b94a5639c54d746e60404fbbda597f/Tests/Utility/ShellTests.swift): 16 | 17 | ``` swift 18 | import Utility 19 | import XCTest 20 | 21 | class ShellTests: XCTestCase { 22 | 23 | func testPopen() { 24 | XCTAssertEqual(try! popen(["echo", "foo"]), "foo\n") 25 | } 26 | 27 | func testPopenWithBufferLargerThanThatAllocated() { 28 | let path = Path.join(#file, "../../Get/DependencyGraphTests.swift").normpath 29 | XCTAssertGreaterThan(try! popen(["cat", path]).characters.count, 4096) 30 | } 31 | 32 | func testPopenWithBinaryOutput() { 33 | if (try? popen(["cat", "/bin/cat"])) != nil { 34 | XCTFail("popen succeeded but should have failed") 35 | } 36 | } 37 | } 38 | ``` 39 | 40 | It has three tests, that each test their own expectations. 41 | 42 | - The first ensures that when `popen(["echo", "foo"])` is called, it returns `"foo\n"` 43 | - The second ensures that when `popen(["cat", path])` is called, it returns a number of characters greater than `4096` 44 | - Finally the third one checks an expectation, and if it's wrong, it will fail the test. 45 | 46 | ##### What is the difference between hosted test targets and unhosted 47 | 48 | When talking pragmatically, we're really talking about writing tests against apps or libraries. Depending on whether you have dependencies on Cocoa or UIKit, you end up having to make a choice. Hosted, or not hosted. 49 | 50 | The terminology has changed recently, a hosted test used to be known as Application Tests, and "unhosted" was known as Logic Tests. The older terminology gives a better hint at how the tests would be ran. 51 | 52 | A hosted test is ran inside your application after `application:didFinishLaunchingWithOptions:` has finished. This means there is a fully running application, and your tests run with that happening around it. This gives you access to a graphics context, the application's bundle and other useful bits and pieces. 53 | 54 | Un-hosted tests are useful if you're testing something very ephemeral/logical and relying only on Foundation, but anything related to UIKit/Cocoa subclasses will eventually require you to host the test bundle in an application. You'll see this come up every now and again when setting up test-suites. 55 | 56 | Further reading: 57 | 58 | * History of SenTestingKit on [objc.io](http://www.objc.io/issue-1/testing-view-controllers.html#sentestkit) by Daniel Eggert 59 | * How XCTest works on [objc.io](http://www.objc.io/issue-15/xctest.html#how_xctest_works) by Daniel Eggert and Arne Schroppe 60 | -------------------------------------------------------------------------------- /generate.rb: -------------------------------------------------------------------------------- 1 | Dir["./generators/*.rb"].each {|file| require_relative file } 2 | 3 | TableOfContents.new.create 4 | Epub.new.create 5 | GitBook.new.create 6 | 7 | todos = `grep -r TODO chapters/`.strip 8 | if todos.length 9 | puts "- TODOs:" 10 | todos.lines.each do |l| 11 | puts " " + l 12 | end 13 | end 14 | 15 | puts "Applied Auto Generatedness." 16 | -------------------------------------------------------------------------------- /generators/generate_epub.rb: -------------------------------------------------------------------------------- 1 | require 'tempfile' 2 | require_relative 'structure_of_pragmatic_programming' 3 | 4 | class Epub 5 | 6 | def create 7 | unless `which pandoc`.length > 0 8 | puts "Pandoc needs to be installed to generate the ePub" 9 | return 10 | end 11 | 12 | markdown_files = MARKDOWN_FILES 13 | all_markdown = get_markdown_files 14 | diff = (markdown_files|all_markdown) - (markdown_files & all_markdown) 15 | if diff.length > 0 16 | puts "Looks like #{diff.join(", ")} is/are missing." 17 | end 18 | 19 | `pandoc -f markdown -t epub+smart --epub-cover-image=assets/Cover.png -o pragmatic_testing.epub --toc --css=assets/pragmatic_style.css #{ markdown_files.join(" " )}` 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /generators/generate_gitbook.rb: -------------------------------------------------------------------------------- 1 | require 'tempfile' 2 | require_relative 'structure_of_pragmatic_programming' 3 | 4 | class GitBook 5 | 6 | def create 7 | markdown_files = MARKDOWN_FILES 8 | 9 | body = "### Summary\n\n" 10 | 11 | markdown_files.each do |path| 12 | title = path[0..-4].gsub("_", " ").gsub(/\w+/) { |word| word.capitalize }.gsub("Ios", "iOS") 13 | 14 | body += "* [#{title}](#{path})\n" 15 | end 16 | 17 | File.open("SUMMARY.md", 'w') { |f| f.write body } 18 | File.open("gitbook_summary.md", 'w') { |f| 19 | file = File.read("chapters/en-UK/what_is/what_and_why_of_the_book.md") 20 | f.write file 21 | } 22 | 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /generators/generate_toc.rb: -------------------------------------------------------------------------------- 1 | require_relative 'structure_of_pragmatic_programming' 2 | 3 | class TableOfContents 4 | 5 | def create 6 | 7 | readme = File.open("README.md", 'rb') { |f| f.read } 8 | 9 | start_split = "##### Existing Pages" 10 | end_split = "##### Generating the ebook" 11 | 12 | start = readme.split(start_split)[0] 13 | rest = readme.split(start_split)[1] 14 | finale = rest.split(end_split)[1] 15 | 16 | template = start_split + "\n\n| Topic | Last Updated | State | Length | \n| -------|------|---|-----|\n" 17 | 18 | template = add_markdown_files_to template 19 | estimates = rough_completion_estimate 20 | 21 | template += "\n\nOver 200 words: " + estimates[:covered] + "%" 22 | template += "\nOver 300 words: " + estimates[:solid] + "%" 23 | template += "\nTODOs: " + `grep -r TODO chapters/`.strip.lines.count.to_s 24 | template += "\nWords: " + estimates[:total] 25 | 26 | new_file = start + template + "\n\n\n" + end_split + finale 27 | File.open("README.md", 'w') { |f| f.write new_file } 28 | 29 | end 30 | 31 | def add_markdown_files_to template 32 | mdfiles = get_markdown_files 33 | 34 | left_overs = MARKDOWN_FILES - mdfiles 35 | 36 | (MARKDOWN_FILES + left_overs).each do |mdfile| 37 | title = mdfile[0..-4].gsub("_", " ") 38 | .gsub(/\w+/) { |word| word.capitalize } 39 | .gsub("Ios", "iOS") 40 | .gsub("Chapters/", "") 41 | .gsub("En-Uk/", "") 42 | .gsub("Oss", "OSS") 43 | .gsub("Xctest", "XCTest") 44 | 45 | last_updated = `git log -1 --date=short --pretty=format:"%ad" #{mdfile}` 46 | words = `wc -w #{mdfile}`.split(" ").first 47 | 48 | template += "|[#{title}](#{mdfile})|#{last_updated}|#{state(words.to_i)}|Words: #{words}|\n" 49 | end 50 | template 51 | end 52 | 53 | def state(words) 54 | if words < 100 55 | "✍🏾" 56 | elsif words < 200 57 | "📎" 58 | elsif words < 300 59 | "📋" 60 | else 61 | "💌" 62 | end 63 | end 64 | 65 | def rough_completion_estimate 66 | over_three_hundred = 0 67 | over_two_hundred = 0 68 | total = 0 69 | 70 | MARKDOWN_FILES.each do |mdfile| 71 | words = `wc -w #{mdfile}`.split(" ").first.to_i 72 | 73 | over_three_hundred += 1 if words > 300 74 | over_two_hundred += 1 if words > 200 75 | total += words 76 | end 77 | 78 | return { 79 | :solid => ((over_three_hundred / MARKDOWN_FILES.count.to_f) * 100).round(1).to_s, 80 | :covered => ((over_two_hundred / MARKDOWN_FILES.count.to_f) * 100).round(1).to_s, 81 | :total => total.to_s 82 | } 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /generators/structure_of_pragmatic_programming.rb: -------------------------------------------------------------------------------- 1 | MARKDOWN_FILES = %w[ 2 | chapters/en-UK/what_is/what_and_why_of_the_book.md 3 | chapters/en-UK/what_is/how_can_i_be_pragmatic_with_my_testing.md 4 | 5 | chapters/en-UK/xctest/what_is_xctest_how_does_it_work.md 6 | chapters/en-UK/xctest/types_of_testing.md 7 | chapters/en-UK/xctest/unit_testing.md 8 | chapters/en-UK/xctest/three_types_of_unit_tests.md 9 | chapters/en-UK/xctest/behavior_testing.md 10 | chapters/en-UK/xctest/test_driven_development.md 11 | chapters/en-UK/xctest/integration_testing.md 12 | 13 | chapters/en-UK/foundations/dependency_injection.md 14 | chapters/en-UK/foundations/stubs_mocks_and_fakes.md 15 | 16 | chapters/en-UK/oss_libs/expanding_on_bdd_frameworks.md 17 | chapters/en-UK/oss_libs/mocking_and_stubbing__ocmock_and_ocmockito_.md 18 | chapters/en-UK/oss_libs/network_stubbing__ohttp_and_vcrurlconnection.md 19 | 20 | chapters/en-UK/setup/how_i_got_started.md 21 | chapters/en-UK/setup/getting_setup.md 22 | chapters/en-UK/setup/introducing_tests_into_an_existing_application.md 23 | chapters/en-UK/setup/starting_a_new_application_and_using_tests.md 24 | 25 | chapters/en-UK/ops/developer_operations_aka_automation.md 26 | chapters/en-UK/ops/techniques_for_keeping_testing_code_sane.md 27 | chapters/en-UK/ops/creation_of_app-centric_it_blocks.md 28 | chapters/en-UK/ops/fixtures_and_factories.md 29 | 30 | chapters/en-UK/async/dispatch_asyncs__ar_dispatch_etc.md 31 | chapters/en-UK/async/techniques_for_getting_around_async_testing.md 32 | chapters/en-UK/async/techniques_for_getting_around_async_networking.md 33 | chapters/en-UK/async/networking_in_view_controllers__network_models.md 34 | chapters/en-UK/async/animations.md 35 | chapters/en-UK/async/will_and_xctest_6.md 36 | 37 | chapters/en-UK/app_testing/techniques_for_testing_different_aspects_of_the_app.md 38 | chapters/en-UK/app_testing/views__snapshots.md 39 | chapters/en-UK/app_testing/scroll_views.md 40 | chapters/en-UK/app_testing/user_interactions.md 41 | chapters/en-UK/app_testing/ipad_and_iphone.md 42 | chapters/en-UK/app_testing/testing_delegates.md 43 | 44 | chapters/en-UK/core_data/core_data.md 45 | chapters/en-UK/core_data/core_data_migrations.md 46 | 47 | chapters/en-UK/prag_prog/making_libraries_to_get_annoying_tests_out_of_your_app.md 48 | chapters/en-UK/prag_prog/using_xcode_pragmatically.md 49 | chapters/en-UK/prag_prog/improving_xcode.md 50 | 51 | chapters/en-UK/wrap_up/books.md 52 | chapters/en-UK/wrap_up/twitter_follows.md 53 | chapters/en-UK/wrap_up/recommended_websites.md 54 | ] 55 | 56 | def get_markdown_files 57 | mdfiles = [] 58 | Dir.glob('chapters/*/*/*') do |item| 59 | next if item == '.' or item == '..' 60 | next if item[-2..-1] != "md" 61 | next if item == "README.md" 62 | mdfiles << item 63 | end 64 | mdfiles 65 | end 66 | 67 | -------------------------------------------------------------------------------- /gitbook_summary.md: -------------------------------------------------------------------------------- 1 | ## What is this book? 2 | 3 | This is a book that aims to be a down to earth guide to testing iOS applications. It came out of a long period of writing tests to multiple non-trivial Apps whilst working at [Artsy](http://artsy.net). All of which are open source, and available for inspection on the [Artsy Open Source page](http://artsy.github.io/open-source/#ios). 4 | 5 | I found very few consolidated resources for testing in iOS general. A lot of the best book advice revolved around reading books about Java and C# and applying the techniques to Objective-C projects. Like any creative output this book does not live in a vacuum, the books I used to get to this point are in the recommendations section. 6 | 7 | Finally this is not a generic iOS Testing book. I will not be objective. This is a pragmatic book from a pragmatic programmer known for making things, not architecting beautiful concepts. There will be things you disagree with, and I'm of the _strong opinions, weakly held_ camp, so you're welcome to send me feedback as issues on [orta/pragmatic-testing](https://github.com/orta/pragmatic-testing) 8 | 9 | I treat this book very similar to how I would a collection of smaller blog posts, so I aim to have it well hyperlinked. There are a lot of great resources out there, and this can send you out into other resources. I'd rather not re-write someone when I can quote. 10 | 11 | ## About the author, and contributors 12 | 13 | I'm the head of mobile at Artsy, and I gave myself the title Design Dictator at the open source project [CocoaPods](https://cocoapods.org). My interest in testing piqued when the entire Artsy mobile team became just me, and I realized that I'm going to get the blame for everything from this point forward. Better up my game. 14 | 15 | There are a lot of times that I say we, meaning the [Artsy Mobile team](https://github.com/artsy/mobile/). I don't think I would be writing this book without these people contributing testing ideas to our codebase. Thanks, Daniel Doubrovkine, Laura Brown, Ash Furrow, Eloy Durán, Sarah Scott, Maxim Cramer & Dustin Barker. I owe you all. 16 | 17 | Finally, I want to thank Danger. She gives me time and space to achieve great things. I wouldn't be the person I am without her. 18 | 19 | ## Who is it for? 20 | 21 | Anyone interested in applying tests to iOS applications. Which hopefully should be a large amount of people. I'm trying to aim this book at myself back in 2012, new to the ideas of testing and seeing a whole world of possibilities, but not knowing exactly where to start or how to continue once I've made one or two tests. 22 | 23 | ## Swift or Objective-C? 24 | 25 | It's easy to get caught up in what's new and shiny, but in reality there's a lot of existing Objective-C code-bases out there. I will aim to try and cover both Swift and Objective-C. As we have test-suites in both languages, some concepts work better in one language vs the other. If you can only read one language, I'm not apologising for that. It's not pragmatic to only focus on one language, a language is a language, in time you should know as many as possible. 26 | -------------------------------------------------------------------------------- /pragmatic_testing.epub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orta/pragmatic-testing/b2fcc710925034effacebdce4dd857f2db43ccda/pragmatic_testing.epub --------------------------------------------------------------------------------