• Like
Taking a Test Drive
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

Taking a Test Drive

  • 430 views
Published

Recipes for test-driven development of iOS applications.

Recipes for test-driven development of iOS applications.

Published in Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
    Be the first to like this
No Downloads

Views

Total Views
430
On SlideShare
0
From Embeds
0
Number of Embeds
0

Actions

Shares
Downloads
5
Comments
0
Likes
0

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • The idea is to provide a set of recipes that you can use in testing iPhone apps. Note that I won’t talk about the automated UI testing Instrument - that’s not TDD or unit testing.\n
  • The idea is to provide a set of recipes that you can use in testing iPhone apps. Note that I won’t talk about the automated UI testing Instrument - that’s not TDD or unit testing.\n
  • The idea is to provide a set of recipes that you can use in testing iPhone apps. Note that I won’t talk about the automated UI testing Instrument - that’s not TDD or unit testing.\n
  • The idea is to provide a set of recipes that you can use in testing iPhone apps. Note that I won’t talk about the automated UI testing Instrument - that’s not TDD or unit testing.\n
  • The idea is to provide a set of recipes that you can use in testing iPhone apps. Note that I won’t talk about the automated UI testing Instrument - that’s not TDD or unit testing.\n
  • I’ve previously said that NeXT should never have allowed ObjC code that didn’t follow MVC to compile. I still stand by that.\n
  • The most common thing I get wrong with outlets is to not set them in the NIB. My strategy for dealing with this is to assert that the outlet is not nil so the app will die if I forget.\n\n
  • There’s no problem with programmatically creating views in test code, you don’t even need to provide a frame if not necessary. That avoids relying on the XIB in test cases, which is environmental data. For complicated views, it may be easier to fake them.\n
  • The arrow shows increasing “tell, don’t ask”-ness. If you use the first action type, the action method has to find out what it needs to know. That means introspection, which is harder to test than explicit demand (“no, use _this_ button”).\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • The Builder pattern allows you to decouple the construction of objects from their use. This is automatic to some extent in ObjC because of the way Class objects work, but in this case it’s good to have an object that returns a class of a type you define.\n
  • \n
  • \n
  • It’s also important to look at what a unit test is not. How to avoid I/O -> fake/mock objects. In-memory CoreData. Textcast has 212 unit tests that run in 0.6 seconds.\n\n“Tests that do these things aren't bad. Often they are worth writing, and they can be written in a unit test harness. However, it is important to be able to separate them from true unit tests so that we can keep a set of tests that we can run fast whenever we make our changes”\n
  • \n
  • Note that this does violate the “don’t depend on data” principle, but Core Data is useless without a MOM so you need to do this. The real deal is the in-memory store. Note that an error is ignored in -tearDown, but if you have that error then you should write a test anyway. As I did to ensure that the store is correctly set up.\n
  • Note that this does violate the “don’t depend on data” principle, but Core Data is useless without a MOM so you need to do this. The real deal is the in-memory store. Note that an error is ignored in -tearDown, but if you have that error then you should write a test anyway. As I did to ensure that the store is correctly set up.\n
  • Note that this does violate the “don’t depend on data” principle, but Core Data is useless without a MOM so you need to do this. The real deal is the in-memory store. Note that an error is ignored in -tearDown, but if you have that error then you should write a test anyway. As I did to ensure that the store is correctly set up.\n
  • Note that this does violate the “don’t depend on data” principle, but Core Data is useless without a MOM so you need to do this. The real deal is the in-memory store. Note that an error is ignored in -tearDown, but if you have that error then you should write a test anyway. As I did to ensure that the store is correctly set up.\n
  • NSNotificationCenter is a good example of a singleton that app code often makes use of. Singletons encourage “ask, don’t tell” because it’s easy to grab the shared instance from anywhere. The problem is that this means relying on the code’s environment, which is bad. Let’s try and get out of that.\n
  • NSNotificationCenter is a good example of a singleton that app code often makes use of. Singletons encourage “ask, don’t tell” because it’s easy to grab the shared instance from anywhere. The problem is that this means relying on the code’s environment, which is bad. Let’s try and get out of that.\n
  • NSNotificationCenter is a good example of a singleton that app code often makes use of. Singletons encourage “ask, don’t tell” because it’s easy to grab the shared instance from anywhere. The problem is that this means relying on the code’s environment, which is bad. Let’s try and get out of that.\n
  • Statement of the problem.\n
  • This is the best solution. Another approach is to swizzle the -defaultCenter method to return your mock. That’s a bit dangerous: though test code isn’t production code so if it works for you, do it. Beware the pitfalls though.\n
  • \n
  • \n
  • \n
  • If I missed any recipe you would like to have seen, let me know and I’ll write it up.\n

Transcript

  • 1. TAKING A TEST DRIVE Graham Lee, Fuzzy Aliens Limited fuzzyaliens.com
  • 2. TAKING A TEST DRIVE Graham Lee, Fuzzy Aliens Limited
  • 3. Pants Pants✘ ✔ ✘ Trousers
  • 4. PREVIOUSLY ON VTM…STAssertTrue(thisThing.state == NSOnState, @”Is this thing on?”);
  • 5. PREVIOUSLY ON VTM…STAssertTrue(thisThing.state == NSOnState, @”Is this thing on?”);
  • 6. PREVIOUSLY ON VTM…STAssertTrue(thisThing.state == NSOnState, @”Is this thing on?”); @ddribin
  • 7. Episode 1 SummarySoftware should satisfy the users’ needsTesting demonstrates that this is true Unit testing can be done by the developer Cheap Well-situated Test-Driven Development is a design practice
  • 8. This week, on VTM…- (void)testBlah { STAssertTrue(thisThing.state == NSOnState, @”Is this thing on?”);}
  • 9. This week, on VTM…OC_TEST_CASE(“General/IsOn”, “Is this thing on?”) { REQUIRE(thisThing.state == NSOnState);} https://github.com/philsquared/Catch @phil_nash
  • 10. Rule 0
  • 11. Rule 0Model-View-Controller
  • 12. Recipe: IBOutlets- (void)viewDidLoad { NSAssert(myOutlet != nil); //...}
  • 13. Recipe: IBOutlets- (void)setUp { ctrl = [[MyViewController alloc] initWithNibName: nil bundle: nil]; label = [[UILabel alloc] init]; label.text = @"Label"; ctrl.label = label;}OC_TEST_CASE(“MyViewController/SetText”, “Text should be updated”) { [ctrl setText]; REQUIRE([label.text isEqualToString: @"Set"]);}- (void)tearDown { [ctrl release]; [label release];}
  • 14. Recipe: IBActions- (IBAction)doSomething;- (IBAction)doSomething: (id)sender;- (IBAction)doSomething: (id)sender forEvent: (UIEvent *)event;
  • 15. Recipe: Core Data
  • 16. Recipe: Core DataRule 1 of testing Core Data:
  • 17. Recipe: Core DataRule 1 of testing Core Data:don’t talk about Core Data
  • 18. Recipe: Core Data
  • 19. Recipe: Core Data
  • 20. Recipe: Core Data- (IBAction)addStaff: (id)sender { NSManagedObject *employee = [[NSManagedObject alloc] initWithEntity: employeeEntity insertIntoManagedObjectContext: moc]; employee.lastName = @"Smith";}- (IBAction)addStaff: (id)sender { id <Employee> employee = [self.employeeBuilder newEmployee]; employee.lastName = @"Smith";}
  • 21. Aside: Builder
  • 22. Recipe: Core Data
  • 23. Recipe: Core DataRule 2 of testing Core Data:
  • 24. Recipe: Core DataRule 2 of testing Core Data: don’t use a database
  • 25. A test is not a unit test if:It talks to the database Michael Feathers, idealized unit testIt communicates across the networkIt touches the file systemIt cant run at the same time as any of your other unit testsYou have to do special things to your environment
  • 26. Recipe: Core Data@interface TheTest : NSObject <OcFixture> {@private NSPersistentStoreCoordinator *coord; NSManagedObjectContext *ctx; NSManagedObjectModel *model; NSPersistentStore *store;}@end
  • 27. Recipe: Core Data@implementation TheTest- (void)setUp { model = [[NSManagedObjectModel mergedModelFromBundles: nil] retain]; coord = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: model]; store = [coord addPersistentStoreWithType: NSInMemoryStoreType configuration: nil URL: nil options: nil error: NULL]; ctx = [[NSManagedObjectContext alloc] init]; [ctx setPersistentStoreCoordinator: coord];}- (void)tearDown { [ctx release]; ctx = nil; [coord removePersistentStore: store error: NULL]; store = nil; [coord release]; coord = nil; [model release]; model = nil;}OC_TEST_CASE("CoreData/Store", "Ensure the persistent store was set up.") { REQUIRE(store != nil);}@end
  • 28. Recipe: Core Data@implementation TheTest- (void)setUp { model = [[NSManagedObjectModel mergedModelFromBundles: nil] retain]; coord = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: model]; store = [coord addPersistentStoreWithType: NSInMemoryStoreType configuration: nil URL: nil options: nil error: NULL]; ctx = [[NSManagedObjectContext alloc] init]; [ctx setPersistentStoreCoordinator: coord];}- (void)tearDown { [ctx release]; ctx = nil; [coord removePersistentStore: store error: NULL]; store = nil; [coord release]; coord = nil; [model release]; model = nil;}OC_TEST_CASE("CoreData/Store", "Ensure the persistent store was set up.") { REQUIRE(store != nil);}@end
  • 29. Recipe: Core Data@implementation TheTest- (void)setUp { model = [[NSManagedObjectModel mergedModelFromBundles: nil] retain]; coord = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: model]; store = [coord addPersistentStoreWithType: NSInMemoryStoreType configuration: nil URL: nil options: nil error: NULL]; ctx = [[NSManagedObjectContext alloc] init]; [ctx setPersistentStoreCoordinator: coord];}- (void)tearDown { [ctx release]; ctx = nil; [coord removePersistentStore: store error: NULL]; store = nil; [coord release]; coord = nil; [model release]; model = nil;}OC_TEST_CASE("CoreData/Store", "Ensure the persistent store was set up.") { REQUIRE(store != nil);}@end
  • 30. Recipe: Core Data@implementation TheTest- (void)setUp { model = [[NSManagedObjectModel mergedModelFromBundles: nil] retain]; coord = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: model]; store = [coord addPersistentStoreWithType: NSInMemoryStoreType configuration: nil URL: nil options: nil error: NULL]; ctx = [[NSManagedObjectContext alloc] init]; [ctx setPersistentStoreCoordinator: coord];}- (void)tearDown { [ctx release]; ctx = nil; [coord removePersistentStore: store error: NULL]; store = nil; [coord release]; coord = nil; [model release]; model = nil;}OC_TEST_CASE("CoreData/Store", "Ensure the persistent store was set up.") { REQUIRE(store != nil);}@end
  • 31. Recipe: Core Data@implementation TheTest- (void)setUp { model = [[NSManagedObjectModel mergedModelFromBundles: nil] retain]; coord = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: model]; store = [coord addPersistentStoreWithType: NSInMemoryStoreType configuration: nil URL: nil options: nil error: NULL]; ctx = [[NSManagedObjectContext alloc] init]; [ctx setPersistentStoreCoordinator: coord];}- (void)tearDown { [ctx release]; ctx = nil; [coord removePersistentStore: store error: NULL]; store = nil; [coord release]; coord = nil; [model release]; model = nil;}OC_TEST_CASE("CoreData/Store", "Ensure the persistent store was set up.") { REQUIRE(store != nil);}@end
  • 32. Recipe: NSNotificationCenterActually talking about Singletons in generalRule #1 of Singletons is don’t use SingletonsCocoa Touch doesn’t give us much choice
  • 33. Recipe: NSNotificationCenterActually talking about Singletons in generalRule #1 of Singletons is don’t use SingletonsCocoa Touch doesn’t give us much choice Remember, a singleton is a global in sheep’s clothing.
  • 34. Recipe: NSNotificationCenter- (void)watchForThingsHappening { [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(workDidFinish:) name: WorkDidFinishNotification object: worker];}OC_TEST_CASE("ViewController/WorkerFinished", "Ensure weknow when the works done") { [controller watchForThingsHappening]; REQUIRE(/*???*/);}
  • 35. Recipe: NSNotificationCenter- (void)watchForThingsHappening: (NSNotificationCenter *)center { [center addObserver: self selector: @selector(workDidFinish:) name: WorkDidFinishNotification object: worker];}OC_TEST_CASE("ViewController/WorkerFinished", "Ensure we know whenthe works done") { [controller watchForThingsHappening: mockNotificationCenter]; REQUIRE([mockNotificationCenter hasObserver: controller forNotificationNamed: WorkDidFinishNotification]);}
  • 36. Conclusions
  • 37. ConclusionsTDD implies a particular approach to writing APIs
  • 38. ConclusionsTDD implies a particular approach to writing APIsCertain patterns support this approach way
  • 39. ConclusionsTDD implies a particular approach to writing APIsCertain patterns support this approach wayThe patterns you’ve seen today work well with TDD and with theCocoa Touch SDK
  • 40. THANKS!http://blog.securemacprogramming.com/ @iamleeg