are code... that run your app's code... that run your app's code... and make sure it behaves and make sure it behaves properly. properly. Properly? Properly? Consistent output for certain input. Consistent output for certain input. Proper behaviour for weird input. Proper behaviour for weird input.
to write better code. Less crapware shipped to customers. Less crapware shipped to customers. Diminished technical debt. Diminished technical debt. Implement new features with Implement new features with confidence. confidence. Detect Regressions (things that Detect Regressions (things that used to work but broke) used to work but broke)
whether your code runs in test It's useful to know whether your code runs in test mode or in debug/release/testflight mode. mode or in debug/release/testflight mode. Compile time (preprocessor macros) Compile time (preprocessor macros) Run-time Run-time Launch-time arguments Launch-time arguments Process info environment Process info environment ObjC Runtime ObjC Runtime
{ // get launch arguments from NSProcessInfo return NSProcessInfo.processInfo().arguments.contains("isTesting") } Declare it in your AppDelegate/Helpers Declare it in your AppDelegate/Helpers Use it here and there... Use it here and there... Runtime (Launch Args) Runtime (Launch Args)
{ // get launch arguments from NSProcessInfo return NSProcessInfo.processInfo().environment["XCInjectBundle"] != nil } Essentially checks whether Testing Essentially checks whether Testing Bundle has been injected... Bundle has been injected... Declare it in your AppDelegate/Helpers Declare it in your AppDelegate/Helpers Use it here and there... Use it here and there... Runtime (Process Info Environment) Runtime (Process Info Environment)
{ return NSClassFromString("XCTestCase") != nil } Checks with the runtime if `XCTestCase` Checks with the runtime if `XCTestCase` class has been linked class has been linked Declare it in your AppDelegate/Helpers Declare it in your AppDelegate/Helpers Use it here and there... Use it here and there... Runtime (ObjC Runtime) Runtime (ObjC Runtime)
integration Good Xcode integration Spec-ing close to your needs/style Spec-ing close to your needs/style BDD/TDD BDD/TDD Mocking/Stubbing support Mocking/Stubbing support MATCHERS MATCHERS Convenient DSL Convenient DSL Async Expectations Async Expectations
as per Jan 2016 XCUnit XCUnit BDD/TDD BDD/TDD MATCHER MATCHER Nimble Nimble Best Xcode integration Best Xcode integration Harness is very flexible (barebones) Harness is very flexible (barebones) Looks fast so far (122 tests in 5secs) Looks fast so far (122 tests in 5secs) Nimble has great built-in functions Nimble has great built-in functions and good DSL and good DSL
{ override func setUp() { super.setUp() } override func tearDown() { super.tearDown() } func testItCorrectlyAddsTwoNumbers() { } func generateNumbers() -> (a: Int, b: Int) { return (a: randomNum(), b: randomNum()) } } Expose your app's public/internal APIs Runs before each function that starts with "test" Runs after each function that starts with "test" A test case! A helper method in this test suite A test suite
must learn. Speed they give you. Shortcuts you must learn. Speed they give you. ctrl ctrl ⌥ ⌥ ⌘ ⌘ U U ⌘ ⌘ 5 5 Run all test suites and all their test cases Run all the cases in current test suite Show the test navigator
sees. Closest to what your end user sees. Test the crap out of them. Test the crap out of them. But remember, keep them thin. But remember, keep them thin. Xcode UI Tests are cool. Xcode UI Tests are cool. But quite slow... But quite slow... Treat them as integration-ish tests. Treat them as integration-ish tests. Unit-test their dependencies. Unit-test their dependencies.
A setup to trigger viewDidLoad() A setup to trigger viewDidLoad() Reference IBOutlets and trigger events. Reference IBOutlets and trigger events. A way to detect view controller A way to detect view controller state/hierarchy (UIAlertControllers etc) state/hierarchy (UIAlertControllers etc) Ocassionally, a UI stack for Ocassionally, a UI stack for pushing/popping vc's. pushing/popping vc's.
setup to trigger viewDidLoad() func testViewControllerSetsValueOnViewDidLoad() { let vc = MyViewController() let _ = vc.view // This triggers viewDidLoad() expect(vc.someValueSetOnViewDidLoad) == true } Accessing view property triggers Accessing view property triggers viewDidLoad() viewDidLoad() No luck for viewDid/WillAppear() and the No luck for viewDid/WillAppear() and the rest though... rest though...
A UI stack for poping/pushing VCs func testViewControllerSetsValueOnViewDidLoad() { let s = UIStoryboard(name: "Main", bundle: nil) vc = s.instantiateViewControllerWithIdentifier("VcStoryboardId") as! MyViewController let dl = UIApplication.sharedApplication().delegate as! AppDelegate dl.window = UIWindow(frame: UIScreen.mainScreen().bounds) dl.window!.rootViewController = UINavigationController(rootViewController: vc) dl.window!.makeKeyAndVisible() let _ = vc.view // This triggers viewDidLoad() } Now we have a UIWindow that can interpret touch Now we have a UIWindow that can interpret touch events etc. events etc. We can push pop view controllers using storyboard We can push pop view controllers using storyboard
MyLoginViewController() let _ = vc.view expect(vc.logoutButton).to(beAnInstanceOf(UIButton)) // "tap" the logout button vc.logoutButton.sendActionsForControlEvents(.TouchedUpInside) expect(MyAuthManager.sharedInstance.isLoggedIn) == false } Reference IBOutlets and trigger events. Reference IBOutlets and trigger events. IBOutlets are "public" properties of the VC IBOutlets are "public" properties of the VC Sending tap actions actually tests they are wired with a UIAction (do Sending tap actions actually tests they are wired with a UIAction (do not just call their selector) not just call their selector)
whole system Unit Tests Integration Tests Dirty Hybrids Good Good Bad! (Unclear goal, high maintainance) Always search for the "sweet spot" Always search for the "sweet spot"
dog = Dog(breed: "German Shepherd") // then expect(dog.furColor) == UIColor.brownColor() } func testGermanShepherdDogBreedHasProperColor() { // given let dog = Dog(breed: "German Shepherd") // then expect(dog.furColor) == UIColor.brownColor() } Good Bad! Shitty names Shitty names
Private means Private means Private Private dude. dude. If you feel the urge to test private methods, there's If you feel the urge to test private methods, there's something wrong with that method. something wrong with that method. It's probably doing too much. It's probably doing too much. What to do? What to do? Uhmm.. refactor? Uhmm.. refactor? Test the private methods through the public Test the private methods through the public API API Extract method into a static class with a contract Extract method into a static class with a contract
tests with implementation details NOWHERE in your tests you should call APIs of NOWHERE in your tests you should call APIs of external libraries. external libraries. Mocking/stubbing is ok. But make sure you test the Mocking/stubbing is ok. But make sure you test the mocked/stubbed logic independently. mocked/stubbed logic independently. Testing constructors Testing constructors Constructors don't and shouldn't have Constructors don't and shouldn't have behaviour behaviour They are "implementation details" They are "implementation details"