Unit Testing iOS Applications with Xcode 4

Unit Testing iOS Applications with Xcode 4

with Ron Lisle

 


Unit testing increases productivity and raises code quality. In this course, author Ron Lisle explains installation and effective use of unit testing tools and techniques for iOS projects, to ensure more stable, functional apps. An example application is provided to demonstrate testing and as the course progresses, developers are shown how to tackle the increasingly complex problems encountered when unit testing iOS development projects.
Topics include:
  • What is unit testing?
  • Comparing the unit testing frameworks for iOS
  • Using unit testing in the refactoring workflow
  • Writing a unit test with OCUnit
  • Writing GHUnit tests
  • Debugging product code with unit tests
  • Using mock objects
  • Testing iOS ViewControllers
  • Testing iOS notifications
  • Testing gesture recognition

show more

author
Ron Lisle
subject
Developer, Mobile Apps
software
iOS , Xcode 4
level
Advanced
duration
2h 57m
released
Apr 30, 2012

Share this course

Ready to join? subscribe


Keep up with news, tips, and latest courses.

submit Course details submit clicked more info

Please wait...

Search the closed captioning text for this course by entering the keyword you’d like to search, or browse the closed captioning text by selecting the chapter name below and choosing the video title you’d like to review.



Introduction
Welcome
00:04Hi! I am Ron Lisle and welcome to Unit Testing iOS Applications with Xcode 4.
00:10In this course, we will look at unit testing and how to unit test iOS applications.
00:15We will use the support built into Xcode 4 called OCUnit and also a couple of
00:20open-source unit test frameworks called GHUnit and OCMock.
00:25I will start off easy by explaining what unit testing is and how it differs from
00:30other types of testing.
00:31I'll show you how to install and use the OCUnit support built into Xcode 4,
00:37and then we'll see how to add and use the additional unit testing capabilities
00:41provided by GHUnit.
00:43Then we will move up to some more advanced techniques including the use of mock
00:48objects using OCMock.
00:51Finally, we'll look at some of the problem areas when unit testing iOS
00:55applications and how these tools can be used to solve difficult
00:59testing situations.
01:00So let's get started with Unit Testing iOS Applications with Xcode 4.
Collapse this transcript
What you should know
00:00This course assumes that you are already familiar with creating iOS applications.
00:05You should already be familiar with using Xcode 4.
00:09I recommend that you have a copy of Xcode 4 and the iOS Developer toolset
00:13installed on your system so you can work along with the videos.
00:17If you need some brushing up on your iOS development knowledge, I recommend
00:21watching one of the lynda.com iOS SDK training videos, such as iOS SDK
00:28Essential Training.
00:29So let's get started.
Collapse this transcript
Using the exercise files
00:00If you are a Premium member of the lynda.com online training library or
00:05purchased this title on a disk, then you have access to the exercise files for this course.
00:10Exercise files are organized by chapter and movie number.
00:14So for example, 0403 contains the Xcode project files for the third movie in Chapter 4.
00:21Note that I use a slightly different numbering for 0209.
00:25This movie uses two different projects in the same movie; hence are named 0209a and 0209b.
00:32You can open the project in Xcode by double-clicking the project file.
00:37If you are a Monthly or an Annual member, then you don't have access to
00:41the exercise files.
00:42You can still follow along.
00:44Throughout the course, I'll explain how you can set up your project files the
00:48same way so that you can follow along and build a similar application and test on your own.
00:53In the beginning, we will use Xcode to create a new project and add additional
00:58functions and testing as we go.
01:00This project in code will then be used by the subsequent videos.
01:03Let's get started then, and create an Xcode project and run some unit tests.
Collapse this transcript
1. Unit Testing Concepts
What is unit testing?
00:00What is in unit testing?
00:02Unit testing is the low-level testing of each class method and function in an application.
00:09Every developer does it; if not automatically then manually.
00:13Perhaps, stepping through code using the debugger to verify that it works correctly.
00:19Unit tests verify that each method of each class produces the expected result.
00:25This may include return values, side effects, such as setting the value of a
00:29property or throwing an exception under some circumstances.
00:34Unit testing is a key element in modern agile software development methodologies
00:40and code refactoring best practices.
00:43Unit tests validate the low-level specific behavior of the code that we write.
Collapse this transcript
Understanding different types of testing
00:00There are various types of software tests.
00:03Whereas testing covers the low-level implementation of classes other types of
00:08software testing include integration tests.
00:12These tests that the separate software components work correctly when combined
00:18or integrated together.
00:20Acceptance tests -- these verify that software as a whole performs the required
00:26and expected functionality and a whole slew of other specialized tests to ensure
00:33that software meets other requirements, such as usability, performance,
00:39scalability, security, and so forth.
00:42In this video course, we will be focusing on automated unit tests.
00:47These test the low-level behavior of our code.
Collapse this transcript
Why unit test?
00:00So why should we write unit tests?
00:02There are a lot of good reasons -- they facilitate change, simplify integration,
00:08and provide living documentation.
00:10Writing unit tests improve the overall design, which can reduce App Store
00:15rejection and result in easier to maintain code.
00:18And it reduces the time a developer spends in the debugger, which improves
00:23developer productivity.
00:25Most reasons for not writing unit tests don't really hold up under honest scrutiny.
00:30Perhaps, the most common excuse for not unit testing is that there isn't time.
00:35The reality confirmed over and over by developers experienced with unit
00:40testing, is that projects with unit tests take about the same amount of time as
00:45a project without unit tests.
00:48The difference is that projects without unit tests require a lot more time
00:53debugging and fixing bugs after the code is supposed to be done.
00:58It's a lot easier to estimate and schedule the writing of unit tests as the code
01:03is being written than it is to anticipate how much time will be needed to debug
01:08and fix bugs after the code is written.
01:10The really big advantage of code with Unit Tests occurs later on when the code
01:15needs to be changed or extended.
01:18Having unit Tests in place makes it much easier and quicker and safer to
01:23make those changes.
01:25There are a lot of good reasons for using automated unit testing. My favorite
01:29reason, all that quality and productivity stuff aside, is that it allows me to
01:35spend more time doing what I'd really like to do, which is writing good
01:39robust bug free code.
Collapse this transcript
Understanding how to unit test
00:00So how is unit testing performed?
00:03As I mentioned in an earlier video, the developer can do this manually, using
00:08the debugger to step through the code and observing the results.
00:12This approach is very time consuming, repetitive, meaning boring, and can
00:17easily be ineffective.
00:19To be consistent and reproducible there needs to be a written list of
00:23steps called a test plan.
00:26Alternatively, testing can be performed automatically using a testing framework.
00:31Instead of creating a test plan that lists all the things that should be tested
00:36and performing each step manually, a test script or unit tests are created to
00:42test each of these same things automatically.
00:45These tests can even be set up to be run automatically each time code changes
00:50are checked into the code repository.
00:53This is a key piece of what is referred to as continuous integration.
00:57We will talk about this more in another video.
01:00Unit testing can be done manually or automatically.
01:04We will focus on learning how to automate unit testing in this course.
Collapse this transcript
Working with unit testing frameworks for iOS
00:00Writing automated unit tests is simplified through the use of unit test frameworks.
00:06Unit testing frameworks are available for most programming environments. Most
00:11arrive from a framework created for Smalltalk then becoming mainstream for Java.
00:16These have names like JUnit for Java, NUnit for .net, PHPUnit for PHP, and
00:24OCUnit for Objective-C.
00:26These all use a common structure.
00:29A test class is created to contain individual tests.
00:33Optional setup and teardown methods in each file perform common test
00:38initialization and cleanup before and after each test is run.
00:42Multiple independent test methods perform tests on each method of the class
00:48under test to ensure correct operation.
00:50There is typically one test case file per class being tested.
00:55Typically there are three or four tests for each method being tested.
01:00Assertions are used to verify correct return values and other behavior from the
01:05method being tested.
01:06There are three frameworks that have become the workhorses for unit testing in
01:11iOS. OCUnit, previously known as the SenTestingKit, is integrated into Xcode.
01:20GHUnit is a separate framework, but has some features that make it attractive to
01:25use in some circumstances.
01:28OCMock is not a test framework like the others, but it provides the ability to
01:33create mock objects, which simplify writing unit tests.
01:37Unit testing frameworks are available that will simplify using automated unit
01:42testing in our iOS projects.
01:44We will take a close look at those frameworks in other videos.
Collapse this transcript
Understanding test-driven development
00:00What is test-driven development?
00:03Test-driven development, TDD, is a key component of today's agile
00:08development methodologies.
00:10In this approach unit tests are written before product code is written.
00:15The test help to clarify what each small piece of product code is expected to do.
00:20Each input and output is carefully considered.
00:23Tests are written to verify that the code behaves as expected given various
00:28inputs, boundary conditions, and even erroneous inputs.
00:32Only after the tests have been written is the product code written.
00:36One of the principles of test-driven development is that once unit tests are
00:41written only the minimal code necessary to pass the test is written.
00:46This tends to keep code small and simple as additional features or behaviors are
00:51identified, new tests are written, and code created to make the new tests pass.
00:57Since the minimal amount of code is written for each test, the test themselves
01:02provide clear documentation of what the code is supposed to do.
01:07Unit test frameworks often display the results of tests in a bar that grows
01:12as each test is run.
01:14As long as each test is passing the bar remains green.
01:17If any test fails, the bar turns red.
01:20You may hear references to green bar or red bar regarding test passing or failing.
01:27Writing tests first can help clarify as well as document exactly what the
01:32code is expected to do.
Collapse this transcript
Understanding the use of unit tests in refactoring
00:00Unit tests are an essential piece of modern software refactoring best practices.
00:06Refactoring means modifying or rewriting existing software in order to make it
00:11more readable, perform better, or easier to maintain and modify.
00:15During refactoring there should be absolutely no change to what the code does,
00:20only how it does it.
00:22Understandably, refactoring depends on having good unit tests to ensure that
00:27there is no change in what the code does.
00:30Without unit tests, refactoring can be a dangerous operation.
00:34Bugs can appear in the code, perhaps caused by unknown side effects.
00:38This becomes somewhat of a catch-22 situation.
00:42We are reluctant to refactor code that is complex and hard to
00:45understand, because we might break it and yet that is the very code that
00:50should be refactored.
00:52On the positive side refactoring of code that doesn't have unit tests provides a
00:57great opportunity to add those tests to the code.
01:01Since you're going to need to carefully review the code line-by-line in order to
01:05understand exactly what the code does, you should then understand what the unit
01:10tests should be testing.
01:12It maybe just as quick to create unit tests as it would be to keep a carefully
01:16written set of notes.
01:18Because refactoring is dangerous without unit tests if the code doesn't already
01:23have unit tests, then that's a good time to add them.
Collapse this transcript
A summary of unit testing concepts
00:00Let's summarize what we've covered in this section.
00:03We've covered what unit testing is, how it improves our design and keeps us out
00:08of the debugger, and how it differs from other software testing such as
00:13integration and acceptance tests.
00:15We've seen what unit testing frameworks are available for iOS and what
00:20test-driven development means, and we heard about unit tests critical
00:24roles during refactoring.
00:26Now let's move on to actually using these frameworks to perform unit testing in
00:31an Xcode iOS project.
Collapse this transcript
2. Getting Started with OCUnit Tests
What is OCUnit?
00:00What is OCUnit? OCUnit is the unit testing framework integrated into Xcode 4.
00:07OCUnit was previously called SenTestingKit.
00:11Apple renamed it OCUnit, when they integrated it into Xcode, but you'll still
00:16see references to SenTestingKit headers and libraries within the code.
00:21The important thing to remember is that these are the same things.
00:25Since OCUnit is integrated into Xcode, including it in a project is as easy as
00:31setting a check box, during project creation.
00:34Likewise adding it to an existing project is as easy as adding a new project
00:39target, and selecting the Cocoa Touch Unit Testing Bundle.
00:43OCUnit documentation has been pretty sparse in the past.
00:47As of the time of this recording, the best OCUnit documentation is in the Apple
00:53iOS App Development Workflow Guide,
00:56in the Unit Testing Application section and in Appendix A, Unit-Test
01:00Result Macro Reference.
01:02I recommend that you bookmark these, since you'll need to reference them when
01:06you're unit testing using OCUnit.
01:09Apple has been steadily improving its support for OCUnit in Xcode.
01:13Xcode 4.2 added additional support, and I would expect to see additional
01:19improvements in future releases of Xcode also.
01:22OCUnit uses introspection to locate unit tests within a project.
01:27This eliminates the need to keep any sort of list of tests within your code.
01:32OCUnit will locate within the project's unit test target, all classes
01:37derived from SenTest case.
01:39It will then execute all methods of those classes that begin with the letters, test.
01:44This makes it very easy to add unit tests along with the production code.
01:49A good workflow that I use is to create a unit test case file, to coincide with
01:55each product class file.
01:56Then create a test method, to coincide with each method added to the product class.
02:02OCUnit makes it easy to add unit test case files and unit tests for each class
02:07and method in your product code.
02:09Don't be confused by references to SenTestingKit though.
02:13OCUnit and SenTestingKit are the same thing.
Collapse this transcript
Using OCUnit with Xcode 4 vs. Xcode 3
00:00OCUnit has undergone improvements with each new release of Xcode.
00:04Most significant, have been the changes from Xcode 3 to Xcode 4.
00:09Prior to Xcode 4, debugging unit test cases was difficult.
00:14Often developers would get frustrated with OCUnit, and resort to using a
00:18third-party unit test framework, such as GHUnit.
00:21Once Xcode 4 was released, however, unit test debugging became easy and the tight
00:27integration of OCUnit within Xcode made it a snap to use.
00:32One problem that remained for OCUnit, even in Xcode 4 was the scarcity of documentation.
00:39Since much had changed between Xcode 3 and Xcode 4, much of the existing
00:43documentation was incorrect.
00:46Even today you will need to be careful when reading about OCUnit that it isn't
00:50referring to the older version in Xcode 3.
00:54More recently, Apple has updated the Unit Testing Applications section in the
00:59iOS App Development Workflow Guide.
01:02This page provides really important information on using both logic and
01:06application tests in OCUnit.
01:08We'll talk some more about those in a later video.
01:12One final point about changes in Xcode versions. Xcode 4.2 added additional
01:18improvements and I expect that we'll see more improvements with future versions.
01:21Review the official Apple documentation to become aware of improvements as new
01:27versions of Xcode are released.
01:28Xcode 4 brought with it some great improvements for unit testing.
01:33Be careful though, when reading blog posts and other documentation about OCUnit,
01:38to be sure that the information is current.
01:40There is a lot of obsolete Xcode 3 based information still floating around
01:45the Internet.
Collapse this transcript
Including unit tests in a new project
00:00 Perhaps the quickest way to get started with unit testing in Xcode is by
00:04 running a unit test.
00:06 So let's create an iPhone application, and take a look at Xcode 4's
00:10 built-in unit test support.
00:13 Launch Xcode and click Create new Xcode project.
00:17 Select Single View Application and click Next.
00:20 I'll call this WhatsMySpeed; use com .lynda for the Company Identifier.
00:28 To keep things simpler, I'll create just an iPhone app, and understand that
00:33 everything that we'll be doing works the same on both iPhone and iPad.
00:37 Make sure that check boxes are all set, especially the Include Unit Tests check box.
00:44 Setting this will cause Xcode to include application unit testing support. Click Next.
00:51 Select a location and then click Create.
00:55 With the project selected in the Project Navigator, we can see that the project
00:59 contains two targets, one for the application and one for the unit tests.
01:05 There are also groups in the Project Navigator named WhatsMySpeed for the
01:10 application, and WhatsMySpeedTests for the unit tests.
01:14 We can run the app at this point, by selecting Simulator and clicking Run.
01:20 This will just bring up a blank app, since we haven't put anything into it yet.
01:30 So here's the app running.
01:32 We'll Command+Q to exit that.
01:35 We can also run the default unit test, created as a result of our having
01:39 selected the Include Unit Test check box.
01:43 Select Product > Test to run the test, and we've received one error.
01:52 The Issue Navigator shows any failing tests.
01:55 I'm going to Command+Q to close Simulator.
01:59 Clicking on the failing test in the Issue Navigator opens the failing test
02:04 code in the Editor.
02:06 Here we can see that a test example was created, with a single line of code that
02:13 forces the test to fail and display a message.
02:17 This is just a placeholder.
02:18 We'll replace it with code to actually test something in a later video.
02:23 So here we've seen that Xcode will include support for running automated unit
02:28 tests, and include a skeleton test file, just by setting the Include Unit Tests
02:34 check box when creating a new project.
02:37
Collapse this transcript
Adding unit tests to an existing project
00:00Let's look at how to add unit tests to an existing project.
00:04We'll start by creating a simple iPhone project without unit tests.
00:09Launch Xcode, and select File > New Project.
00:15Select Single View Application and click Next.
00:19We'll call this WhatsMySpeed.
00:20I'll use com.lynda for the Company Identifier.
00:26To keep things simpler we'll create just an iPhone app, but understand that
00:30everything that we'll be doing works the same on both iPhone and iPad.
00:33Make sure that Include Unit Tests check box is not set. Click Next.
00:40Select a location and click Create.
00:45With the project selected in the Project Navigator, we can see that the project
00:49only contains one target.
00:52It does not include unit tests.
00:53We'll need to add them now.
00:55Select the project, and click Add Target.
01:00Select Other, and then Cocoa Touch Unit Testing Bundle. Click Next.
01:07Let's call this LogicTests. Click Finish.
01:14Note the LogicTests target has been created.
01:17A new group, LogicTests, has been created that contains the unit test code.
01:24There is also a new schema named LogicTests.
01:29We'll select the Simulator.
01:32We can now run the unit tests.
01:34Select LogicTests scheme with Sim.
01:37Product > Test or Command+U, and this will run the tests.
01:43One error is reported, as expected.
01:46Issue Navigator shows the failing test, and clicking on that opens up the Code
01:52Editor with the failing test code.
01:55Here we can see that a test example was created, with a single line of code that
02:00forces the test to fail and display a message.
02:03This is just a placeholder.
02:05We'll replace it with code to actually test something in a later video.
02:09So we've seen how to add unit tests to an existing project.
02:14Later on we'll look at how to write unit tests to actually test our code.
Collapse this transcript
OCUnit: application tests vs. logic tests
00:00OCUnit supports two different types of tests: Logic Tests and Application Tests.
00:06Currently these tests look very similar, but behave quite differently.
00:10It's also not very easy to distinguish one from the other.
00:13I'll show you how to do that in just a moment.
00:16Logic tests are what I would consider to be traditional unit tests.
00:20They load and run quickly.
00:22You include all of the code to be tested.
00:26Any other objects or environment needed by the test must be created by the test itself.
00:31Logic tests run on the Simulator only.
00:34Support for logic test is added when you use the Add Target, to add a unit test
00:40as we saw previously.
00:41Application tests on the other hand, are run in the context of the
00:46loaded application.
00:48The application is built and loaded into either the Simulator or device and
00:53the test code then run.
00:55The tests are said to be injected into the application bundle.
00:59Application test can do things that logic test cannot, such as verifying that an
01:03IP outlet has been connected in the nib correctly.
01:07This comes at a price though.
01:09Loading the entire application bundle before each test is run is time consuming.
01:14One powerful capability of OCUnit in Xcode is that you can have both logic and
01:20application tests in a single project.
01:22Let's look at how to do that now.
01:25Open Xcode and select New Project, so File > New > New Project.
01:32We'll select the Single View Application and click Next.
01:37We'll call this WhatsMySpeed. Be sure the check boxes are all set, including
01:43the Include Unit Tests.
01:45This will cause us to include our application tests in this project, and click Next.
01:52We'll select a location, and click Create.
01:56As of Xcode 4.2, selecting the Include Unit Tests causes application tests to be created.
02:04This behavior may change in the future.
02:07I expect that Apple will eventually give you a choice as to which type of
02:11unit tests are created.
02:13But as of Xcode 4.2 or 4.3, this always creates application unit tests.
02:21Xcode has created a project with application tests. See that here.
02:27Let's rename our application test to keep things clear.
02:30We'll click twice on the group name and change WhatsMySpeedTests to
02:36ApplicationTests, and let's open the implementation file.
02:42We'll double-click on the name of the object, right-click, select
02:48Refactor > Rename, and we'll rename this ApplicationTests also.
02:56Make sure the check box is set to Rename the related files and click Preview, and then Save.
03:05I'm going to ignore the snapshot popup for now.
03:10Now our ApplicationTests file have been renamed ApplicationTests.
03:15Refactor however doesn't change our comments.
03:20So to clean this up we'll want to do that ourselves.
03:23We'll double-click and then copy the new name.
03:28Double-click and Paste. Double-click and Paste.
03:35Command+S to save this file, and we'll do the same thing in the implementation.
03:41Double-click, Paste and double-click, Paste.
03:47Let's run the test now; verify that things build okay and run okay.
03:51I'll select Product > Test.
03:56There should be one failure caused by the default STFail, sometimes when code
04:04behaves unexpectedly.
04:06In this case we expected one failure and received none.
04:10The problem is that Xcode has cached some files and has not rebuilt them correctly.
04:17When this happens, choose Product, hold the Option button, and click Clean Build Folder.
04:24This will cause previously built files to be deleted and force a complete
04:30rebuild of all the code.
04:32The test it started, or tried to start and didn't complete.
04:35So we'll stop them now, and we've cleaned the project.
04:40So now we'll try running Product > Test again, and this time we should see one failure.
04:49Simulator pops up, and there's our failure.
04:52So I'm going to close the Simulator.
04:55So now let's add LogicTests.
04:59We'll add a new target, Add Target button.
05:05We'll select from the other category, the Cocoa Touch Unit Testing Bundle, and click Next.
05:12We'll call this LogicTests. Click Finish.
05:17Now we have a project that supports both application and logic tests.
05:22To run the logic tests, we change the scheme to LogicTests > Simulator, and we can
05:30select Product > Test to run the LogicTests now.
05:34We'll expect the same one failure from the default STFail statement, which we
05:40can see here, and if we look we can see that we have two different sets of test
05:46files: ApplicationTests, with its default STFail test, and LogicTests, with its
05:54default test example STFail.
05:57Tests can now be created and added to either Application or Logic Tests.
06:02Since Logic Tests run faster, I recommend creating Logic Tests where possible,
06:08and using Application Tests for those things that cannot or are very difficult
06:13to be tested as Logic Tests.
06:15The combination of Application and Logic Tests, provide a powerful
06:18testing capability.
06:20You can use both in a project, and I recommend that you do.
06:24Then you can choose the type best suited for each test.
06:28
Collapse this transcript
Writing a logic unit test
00:00Previously, we saw how to enable unit testing in both new and existing projects.
00:05When you add unit testing support to a project, Xcode creates a placeholder test case file.
00:11We're going to look at modifying that placeholder test to actually test
00:14something in our code.
00:16I've added a Location model class.
00:19This class encapsulates the core location information used to calculate the user speed.
00:24It isn't absolutely required that you understand how core location works.
00:29But if you'd like to brush up on core location before proceeding, you can stop
00:33the video at this point and go review the core location documentation on the
00:38Apple developer web site.
00:40Let's look at the Location model class code and write some unit tests for it.
00:45I've created a new group called Models, and created the Location class within it.
00:50The Location class imports the CoreLocation framework and implements the
00:57CLLocationManagerDelegate.
01:00That means we need to include the CoreLocation framework in our project, in both
01:07the Project and the LogicTests target.
01:11The Location.m file is included in both targets also.
01:15The Location class exposes two properties.
01:20On lines 14 and 15, locationManager contains a reference to the
01:26CLLocationManager object.
01:29The speed property will persist our last speed calculation.
01:33The Location class also exposes two methods, startLocationUpdates will be called
01:40to start CLLocationManager.
01:43The reason this isn't done in init is just to simplify testing.
01:47We'll explore this concept more in later videos.
01:50calculateSpeedInMPH, CoreLocation provides speed information in MetersPerSecond.
01:57This method will convert that data to miles per hour.
02:01In the implementation file, I synthesize both properties on line 17 and 18.
02:08In the init method, I perform the standard call to super, returning if nil is returned.
02:15I create an instance of the locationManager, and assign it to the
02:20locationManager property in line 26.
02:24I set the Delegate to self in line 27, and lines 28 and 29, I set the
02:31DesiredAccuracy and DistanceFilter.
02:34The startLocationUpdates method simply calls the locationManager
02:41StartUpdatingLocation method.
02:42calculateSpeedInMPH we already mentioned; proceeds speedInMetersPerSecond.
02:50Come returns, speedInMilesPerHour, and finally there is the required
02:56LocationManager Delegate method.
02:58This method is called with location and speed information by the
03:03CLLocationManager instance, because we have said its delegate to point to this object.
03:08The method will extract the speed information, from the newLocation argument
03:13passed, and save the converted MPH value in the speed property.
03:20Meanwhile, back in the ViewController, I've defined a new location property at
03:26line 14, and synthesized it in the implementation file.
03:30Then I create an instance of the location object in the viewDidLoad method and
03:37free it by setting the property to nil, and the viewDidUnload method.
03:42So let's write some tests.
03:46First off, let's rename the default LogicTests class to accurately reflect that
03:51we will be using this test case file to test the Location model object.
03:57We'll use Refactor on the object name, Refactor > Rename.
04:03Let's call this LocationTests.
04:06We'll leave the Rename related files check box set, and click Preview, and Save.
04:17As before we'll clean up our comments. I'll double-click Copy,
04:22double-click Paste, double-click Paste, and in the LocationTests header
04:30file also, double-click Paste.
04:34Next let's create an instance of the location object to perform our tests on.
04:40The setup and tear-down methods are intended to be used for this sort of test
04:43preparation and cleanup.
04:45In the Tests case header file, we'll create a property, contain the location
04:51object, and we'll synthesize that in the implementation file.
05:01In the setUp method, we'll allocate an instance of the location object, and
05:06assign it to the location property, and in tearDown we'll free it by setting the
05:15reference to nil, saving our files.
05:20We'll need to import the location header file.
05:23So now let's write some tests.
05:27So at this point our test will create an instance of the location class before
05:34each test and remove it after each test.
05:38Now let's create a test method.
05:40Remember that all test methods must begin with the text string test.
05:44We'll reuse test example for this next test.
05:48Let's start with an easy test.
05:50Methods that perform a calculation on an input argument are the easiest to test.
05:55So let's create a test for the CalculateSpeedInMPH method.
06:00For convenience, we'll display the code that we'll be testing on the right side
06:05by holding Option and clicking Location .m. Then close the Utilities View, and
06:12we'll scroll down to where we can see the code will be tested.
06:16To test this method, we'll pass it a value in meters per second and compare the
06:23return miles per hour value.
06:25So let's name our test beginning with test.
06:28Then we'll use the name of the method that we're testing.
06:32So CalculateSpeedInMPH, and delete the old failing statement.
06:43Now we'll create some Constance, just to my test a little more readable.
06:48Now we're going to call CalculateSpeedInMPH and save the value that's returned.
06:56We're going to pass it 55 miles per hour, convert it to meters per second, and
07:03now we'll compare or assert that the value that's returned is equal to 55, and
07:13display an error message if the value is incorrect.
07:19We can use normal formatting commands within the error message text string.
07:26So here we'll return the returned mph value, which will be displayed if it's not 55.
07:33Now let's run the tests by selecting the LogicTests
07:37scheme > Simulator > Product > Test.
07:42Our test run -- we see no issues.
07:45We'll open the Console View, and here we can see the one test was executed with no failures.
07:52There's something I'd like to point out about Xcode at this point.
07:55Xcode in the Simulator appear to have issues with caching files.
08:01Sometimes I'll make a change to a file, including test files, and then run the
08:05code and test, and not see the change.
08:08It appears that things get cached and are not always updated.
08:12We saw that earlier.
08:14Be alert to this behavior, and if things don't appear to have been updated as
08:19expected, try cleaning before running.
08:22This can be done from the Product menu, hold the Option key and then
08:27select Clean Build Folder.
08:31Select Clean, and all of the previously built files should be erased.
08:36On the next run of Test or go, all of the code should be compiled fresh again,
08:43and here we see executed one test with no failures.
08:46So we've seen how to use the OCUnit test case file structure to instantiate an
08:51object to test before each test.
08:54Then we call a method to test it, and then the Tear-down is called to destroy
09:00that object before the next test.
09:02We passed the test code in own quantity, and verify that it returned the
09:07expected value using an Assert.
09:09This is the most basic type of unit test.
09:12We'll look at progressively more complex test scenarios, as we go through
09:16this course.
09:17
Collapse this transcript
Understanding the rules for writing good unit tests
00:00Now we are going to locate examples of a few unit testing best practices.
00:05We will create some additional unit tests and location tests to demonstrate these concepts.
00:11The first unit testing best practice is to keep tests small and independent of each other.
00:17No should depend on the effects of any other test.
00:21Let's open LocationTests implementation file.
00:24Here we've already set up our tests to be independent of each other using the
00:29setUp and tearDown methods to create a fresh instance of a location object for each test.
00:35Let's create a new test to simply verify that init method instantiates a location object.
00:42To do this we will just check that the object created in setup isn't nil.
00:47We will AssertNotNil and display a message if it is.
00:58We'll save; run this test.
01:10Opening up the console log, we can see that two test ran with no failures.
01:16Note also that because the tests are independent of each other it doesn't matter
01:20in which order the tests run.
01:22That's good because OCUnit doesn't make any guarantees about the order in
01:27which it runs the tests.
01:28In fact, looking at our test run here it appears the OCUnit has run the tests in
01:35the opposite order they appear in the file.
01:37So testCalculateSpeedInMPH, which is the second test, was run before testInit,
01:44which appears above it.
01:46Another unit testing best practice is to test just one thing with each test.
01:51Often this means making just a single assertion, but sometimes multiple
01:56assertions can be used to verify a single thing.
01:59For example, let's create a new test to verify that the location class init
02:04method correctly instantiates an instance of the CLLocationManager class in its
02:10location manager property.
02:11We will create one test for this, but we will choose to assert first that the
02:16reference is not nil.
02:18Then that the object that the reference points to is actually of the correct
02:23CLLocationManager class.
02:26Close the Console view.
02:27The right view shows the init method that we will be testing, and we will create
02:32a new test, testThatInitSetsLocationManager.
02:41Now we will assert that the LocationManager property is not nil.
02:49Also, we will assert that the LocationManager's class is CLLocationManager.
02:58Now we will run our test, Command+U, open the Console log, see that we now have
03:06three tests with no failures.
03:09Another best practice is to create a separate test case file to test each
03:14class in the application.
03:16If you are using test-driven development, then you will create each test case
03:21file prior to creating each new class file.
03:24In this example we created a LocationTests test case file to contain our
03:31LocationClass unit tests.
03:34A best practice is to create one or more tests for each method in the
03:38class being tested.
03:40Test each of the possible behaviors of the method with separate tests.
03:45There we created two tests for the init method.
03:48For example, test what the method is expected to do if an argument passed to
03:54it is nil or invalid.
03:56If an exception is expected to be thrown, write a test to verify that the correct
04:00exception is thrown.
04:02And finally, don't test Apple's code.
04:06By this I mean don't test the boilerplate code that Xcode generates
04:09automatically for you or calls made to Apple's frameworks.
04:13Let Apple test their own code.
04:16Just worry about testing the code that you write or modify.
04:20Writing good unit tests isn't difficult.
04:23Remain focused on small sections of code and write tests to cover all
04:27possible expected behaviors.
Collapse this transcript
Writing an application unit test
00:00Now I am going to show you how to create OCUnit application tests that will
00:04verify that the connections between outlets in our ViewController and the
00:08nib file have been made.
00:09The connections are done in Interface Builder and so aren't very easy to test
00:14using logic tests, but they are quite simple to verify using application tests.
00:20In application tests, the application bundle is loaded before the tests are run.
00:24So we don't need to create instances of objects that exist in the application
00:28like we do in logic tests.
00:31Instead, we can get references to the loaded applications instances.
00:35In this example, we will get a reference to the ViewController and then test
00:39that the outlets have been set.
00:40We will be working with just the applications tests.
00:44So be careful that the LogicTests scheme isn't selected when running tests.
00:48Let's start by getting a reference to the ViewController.
00:52We will need to import the header files for the ViewController and the AppDelegate.
01:01We will create a weak reference to the ViewController. Save it.
01:11We will synthesize that property.
01:18We'll reuse the test example, testThatViewControllerIsntNil.
01:20Delete the STFail code, and we will replace that with a STAssertNotNil.
01:36Saving and run this test.
01:38In this case, I've forgotten and left a device selected.
01:41I am going to switch to the Simulator since I don't have the device plugged in,
01:46and we will test, test run, and the test failed as expected.
01:54This approach is test-driven development.
01:56We've created the test and watched it fail before writing code to make the test pass.
02:02Now let's write the code.
02:04In setUp we'll set our ViewController's reference from the AppDelegate, which we
02:10can get from the UIApplication.
02:11Then we are going to reference to the Delegate.
02:19Now we will give a reference to the Window, and finally, set our ViewController
02:25property from the Windows rootViewController.
02:33In tearDown, we will go ahead and remove nil out that reference.
02:41Now saving and running our test.
02:42We should see them passing now.
02:44We will close the Simulator, expand our view, and we can see that All tests is
02:58indicated so we know that we are running ApplicationTests, and we see that the
03:03test started and ran.
03:05I am not seeing the number of tests run.
03:08So let's run the tests again.
03:10You can see that the Simulator comes up, the test run.
03:16Close the Simulator, and we get the Executed 1 test with 0 failures.
03:21So our tests have passed.
03:23Let's extend our test application now to display a MapKit view.
03:26We will add a test to verify that its IBOutlet has been set.
03:31We could try to use TDD and write a test first, but without an IBOutlet property
03:36defined yet the test won't compile.
03:40Xcode provides a convenient drag-and- drop method for creating outlets.
03:44So we are going to use that first, then create the tests.
03:48Let's close the Console view, and we will select the Storyboard in our left
03:55pane. Select Automatic.
03:59We will open the Utilities view, scroll, and go over here, and we will drag a
04:11Map view into our view.
04:11Now we will open both the storyboard, we will close the Utility view, and
04:20open the header file, and we will Ctrl +Drag from the view into the header
04:26file to create the outlet.
04:29We will call this outlet mapView.
04:31Adjust the spacing here a little bit.
04:38Now we need to add MapKit to the project.
04:40That's why we are seeing this error here.
04:44So come up to the Project > Build Phases > Link Binary With Libraries and select MapKit.
04:56We will drag it down into our Frameworks to clean things up.
05:06Now in our ViewController header file we will need to import the MapKit also.
05:12We save our ViewController, come down to our ApplicationTests, and we will add a
05:24test to verify that the mapView outlet gets connected.
05:28So here we can copy this test.
05:32So it already does most of what we want.
05:37Change the name of the test to testThatMapViewIsntNil.
05:39Then we will change our object from ViewController to self.mapView, and then
05:51change your message.
05:54And here we will check that the ViewController's MapView property is set.
06:03Now if we run our test, Test Build and Run and All Tests Succeeded.
06:11Close Simulator, double check the log to be sure, and we see that in this case
06:17it's still not saying they passed.
06:20So let's run test again. Here we go.
06:25Now we see that two tests were run, testThatViewControllerIsntNil and
06:28testThatMapViewIsntNil, both completed.
06:36We will want to track the user's location.
06:39So let's add an application test to verify that show user location is set.
06:44We will use test-driven development and write the test first.
06:49Close the Console log.
06:50Let's create a test for vc mapView ShowsUserLocation is yes.
06:58Then we will Assert that our ViewController's mapView showsUserLocation property
07:10is true and display an error message if it doesn't.
07:14Save and run our test.
07:25And here we have the expected failure, because we are not setting this property yet.
07:29Close the Simulator.
07:30Now in this case, we can set this property using Interface Builder.
07:37So let's display the MainStoryboard, select the Map View, and we will show the
07:44Properties, and here this check box at the top Shows User Location.
07:51If we set that, that will set that property.
07:54Now if we save and run our tests again, tests are running, and all tests pass.
08:06So let's close our Simulator; double-check our log to be sure.
08:13See that's all passing.
08:15Let's run that again, Test, and here we can see that all three tests passed.
08:24Now according to our tests, the user location should be shown.
08:29Let's run it and see if the code is actually doing that.
08:34Getting a prompt, allow it to access our location. We will say OK.
08:39Now the Map view is displayed and our position is over Cupertino, which is the default.
08:48Since the Simulator doesn't have an actual GPS it's simulating that location.
08:54By default it's Apple's headquarters.
08:57We still need one more change to make this view look good.
08:59Well, we want the map centered on the display.
09:04So we will need to set the userTrackingMode to MKUserTrackingModeFollow.
09:09So let's do that now.
09:12We will quit out of the Simulator.
09:16In running we saw two MapViews;
09:18somehow I dragged out a second MapView here, so we will delete this one.
09:22So I am clicking Delete, make sure that this one is centered.
09:27Okay, now we have just one.
09:33So now we are going to create a test that the TrackingModeFollow is set.
09:39Going back to ApplicationTests.
09:44Once again, we will copy this previous test.
09:48This is a pattern that you will see quite often when you are unit testing.
09:54That's a bit daunting at first to think about having several tests for every
09:58method that you test until you realize the most of the time you just write a
10:02single test, and then copy that with minor changes multiple times.
10:06So we are going to change the testThatShowsUserLocation, and we are going to
10:12change that to testThatUserTrackingFollow, and we are going to change this
10:20property from showsUserLocation to userTracking = MKUserTrackingModeFollow
10:32selected of our list here, and then change our error message to
10:37UserTrackingMode is not follow. Save that.
10:42We can run our tests, test-driven development, and expect this test to fail
10:47since we've got implemented this code yet.
10:50Tests are running and indeed we get the expected failure.
10:54Close the Simulator.
10:55There is our failing test.
10:58Now we'll go implement that code.
11:02Now we if we go look at Interface Builder, select the Map, we see that we don't
11:10have a property exposed here for setting UserTrackingModeFollow.
11:15So we will need to do that with code.
11:16So we will come back to our ViewController, and in the viewDidLoad, we will add
11:24[self mapView] setUserTrackingMode to follow to the viewDidLoad.
11:28Then we will set this to MKUserTrackingModeFollow.
11:33Save, rerun our tests, layer console.
11:41Now we have four tests and all tests passing.
11:45So as we have just seen application tests can be quite useful for testing things
11:50like IBOutlets, checking those connections, while not the best choice for
11:56general unit testing application tests can be quite powerful.
Collapse this transcript
Exploring Xcode dependencies and schemes
00:00Xcode has a couple features that can be helpful to understand and use when unit
00:05testing, target dependencies and schemes.
00:09Target dependencies allow you to specify that whenever one target is built or
00:14run, other targets need to be built first.
00:18This can be useful when unit testing in a couple ways.
00:21First, when a separate target is used for application unit tests, the product
00:27code being tested needs to be built prior to building and running the
00:30application unit tests.
00:32To ensure that this happens, set the product target as a dependency of the
00:37application unit test target.
00:39Conveniently, the dependency build will only occur if needed; if the dependency
00:45has changed and not already been built.
00:48Here we see that Xcode created this dependency when it created the unit tests.
00:54This was done as a result of setting the Include Unit Test check box when we
00:59created this project.
01:00Another use for Target Dependencies in unit testing is to cause logic unit tests
01:06to be run whenever the product target is built or run.
01:10To do this, add the logic unit test target as a dependency of the product target.
01:20This is helpful for use in automated continuous integration build systems.
01:25CIBuild typically hook into the source code control system to automatically kick
01:30off a build whenever new or modified code is checked in.
01:35When this happens unit tests should be run as a part of that build process.
01:40In this way, the build breaks as a result of any unit test failures, in addition
01:45to any compiler or linker errors encountered with the updated code.
01:49Xcode schemes can be used to configure which tests are run for each scheme.
01:55To edit any of the schemes in a project, select Schemes > Edit Schemes. Select the
02:01scheme to edit. I am selecting LogicTests. Select the Test tab and the tests
02:10that are run are displayed in the right.
02:12So here we can see a set of tests.
02:15The check box is on the right under the Test column to allow you to selectively
02:19enable or disable any or all of the tests.
02:22This can be handy when working on tests or using tests to debug code.
02:27You can disable all of the tests except the ones you're working with.
02:33One other important reason in unit testing for editing schemes is to select the
02:38test target the runs when the scheme's Test Action is selected. For example,
02:44if you have an existing project that was selected without unit tests then the
02:49product scheme's Test Action will be grayed out.
02:53This is the case in this example.
02:55This is because no test target has been assigned to the scheme's Test Action.
03:00Let's edit that scheme.
03:04When you create a new unit test target. Xcode creates a new scheme for it.
03:10It doesn't automatically add the new test target to any existing targets' test action.
03:17This forces you to switch schemes between running and testing.
03:25This is inconvenient because the product target can only be built and run while
03:31the test target cannot be run.
03:36This is easily fixed by adding the test target to the project's test action.
03:40To do that select Edit Scheme, select the product target, select Test, click
03:47plus (+), and add the LogicTests to the test action.
03:54Now we can run tests from the product scheme.
03:59Since there isn't any need to select the test target scheme anymore, we can use
04:05Manage Schemes to hide the test target scheme altogether.
04:14Now there is only one scheme, and it supports both run and test.
04:20Xcode provides some convenient features for running unit tests using target
04:24dependencies and schemes.
04:27Understanding and using these can make unit testing quicker and easier.
Collapse this transcript
3. Getting Started with GHUnit Tests
What is GHUnit?
00:00Now let's take a look at another unit testing framework for iOS and how it
00:04differs from OCUnit.
00:06GHUnit is a third-party unit testing framework for iOS written by Gabriel
00:12Hanford, hence the GH.
00:14It's similar to OCUnit, with the few key differences.
00:17GHUnit isn't integrated into Xcode but it isn't tough to add.
00:22Tests are run as a separate executable target.
00:26Tests can be run and debugged on both the Simulator and a device.
00:31GHUnit supports its own unique test format. in addition to being able to
00:36run OCUnit Logic Tests.
00:39GHUnit supports asynchronous tests; for example, you can create tests that send a
00:44request to a Web service, and then verify the results returned by the Web service
00:49when the response is received.
00:51The combined use of OCUnit and GHUnit makes for a powerful unit testing workflow.
00:58At the start of a project, include both OCUnit and GHUnit as described in other videos.
01:04Create OCUnit Logic Tests when and where possible.
01:08Create GHUnit asynchronous tests and those specific test situations that require
01:14asynchronous testing.
01:15And debug tests in OCUnit on the Simulator, and if you have to use a hardware
01:21device debug on GHUnit.
01:24GHUnit isn't as convenient to use as OCUnit, but for asynchronous tasks such as
01:29API testing, it's a great choice.
01:31Used in combination with OCUnit, it makes for a powerful workflow.
Collapse this transcript
Adding GHUnit to a project
00:00Let's look at how you add GHUnit to an Xcode project.
00:04The project we've been using contains both OCUnit application and LogicTests,
00:10and a few tests of both type.
00:12We'll see how GHUnit works hand-in- hand with these to provide additional
00:16unit test capability.
00:18First, we'll need to get the documentation and add a copy of GHUnit.
00:24The code is currently hosted on github.
00:27Open a browser, search to gh-unit, and select the github repository or use the URL here.
00:34Scroll down a ways to the Docset section and follow these instructions.
00:41We'll copy this atom feed address, switch to Xcode, and in Xcode Preferences,
00:52select the Downloads tab, Documentation > Add (+), and Paste the atom feed address we copied.
01:02If the Install button appears, click it. You may need to enter your password at that point.
01:11At this point the documentation for GHUnit has been added to Xcode, and since
01:16it's an atom feed, it will be updated automatically.
01:19Now download the GHUnit framework.
01:22Select Downloads. GHUnit is available for both Mac and iOS.
01:31Be sure to select the latest iOS version to download.
01:36Once it is downloaded, open the file to unzip it. We'll be using this
01:43framework in just a moment.
01:45Now let's add the GHUnit framework to our project.
01:49Back in Xcode, select Add Target.
01:56Select iOS Empty Application. Click Next. Name the target GHUnitTests.
02:05Turn off the Include Unit Tests check box. Click Finish.
02:11Add the GHUnit framework to this target by right clicking on Frameworks and
02:16selecting Add Files.
02:21Select the GHUnit framework;
02:24ensure that only the GHUnitTests check box is set.
02:29Set the Copy items check box and then click Add.
02:35We can see that the framework has been added to our Frameworks group.
02:39Now edit the Build Settings for the GHUnit test target.
02:45Let's close the Console view.
02:47In the Other Linker Flags field, double- click to add, and we'll add the ObjC flag
03:01and the all_load flags.
03:05Over in the Project Navigator, open up the GHUnitTests group. Delete all of the
03:11files except the Supporting Files group. Select. Right-click. Delete.
03:19Open Supporting Files. Open the GHUnitTests-Info.plist.
03:26Verify that main nib file base name is not present;
03:30if it is, delete it.
03:32Edit main.m and in main.m replace NSStringFromClass ([AppDelegate class]) with
03:41the string GHUnitIPhoneAppDelegate. Save the file, Command+S. Now run the GHUnit
03:55scheme, not test but run.
04:02The Simulator is loading and GHUnit is running.
04:06GHUnit displays a list of tests since we've not added any tests to the GHUnit
04:12target, the list is empty at this point.
04:15So let's add the existing OCUnit LogicTests.
04:19Close Simulator. Select LogicTests.m from the LogicTests group,
04:27and we'll add it to the GHUnitTests target.
04:32We'll need to add to SenTestingKit framework to the GHUnit target also, so
04:38select the framework and click the GHUnitTests.
04:42We'll also need to add CoreLocation since that's used in some of our tests;
04:48select that and click GHUnitTests also.
04:51Location.m will need to be added to GHUnit also, since it's being tested, and we
04:58should be able to run our tests at this point.
05:02GHUnitTests scheme is selected. Click Run.
05:08Now we see three tests that we just added. These are the previously the OCUnit
05:13Logic Tests. GHUnit is picking them up.
05:17Now we can tap the Run button to run these three tests.
05:21We see that all three tests have run.
05:25GHUnit will display any failing tests in red.
05:29Since these are all black, they've completed successfully.
05:32We can see the status at the bottom -- tests completed, three out of three tests
05:37passed, no failures.
05:39If an error does fail and is displayed in red, you can click on it and it will
05:44display diagnostic information and any messages generated by that test to help
05:51explain why the test failed.
05:54Note that you can display just the failing tests by clicking the Failed
05:58button at the bottom.
06:00Tapping Run while Failed is displayed will run just the failing test.
06:05This is great when you're debugging a particular test or a particular set of
06:10tests that are failing; you don't have to run all of them again.
06:13As we've just seen GHUnit can run our OCUnit LogicTests, but it cannot run
06:19application tests if they depend on the application bundle having been loaded,
06:24which is after all why we would use application tests.
06:27My recommendation for which test framework to use in a new project is to create
06:32targets for all three.
06:34Favor adding OCUnit LogicTests and use the other two as needed.
06:39GHUnit is fairly easy to add to a project and it can be added to a project that
06:44also uses OCUnit application and LogicTests.
06:47Be sure to check the GHUnit web site though for latest instructions.
Collapse this transcript
Writing GHUnit unit tests
00:00So now let's take a look at writing a GHUnit test.
00:03GHUnit has its own unique format, which provides some features not available in OCUnit.
00:09Using the GHUnit format you can do some additional things.
00:13So now let's look at writing some GHUnit tests.
00:16In addition to running OCUnit test, GHUnit provides its own test format.
00:22This format provides some additional features not available in the OCUnit format.
00:26So it's worthwhile to become familiar with these.
00:28We will be looking later on at using those advanced GHUnit features for
00:33writing asynchronous tests.
00:35But let's just start with the basic GHUnit test at this point.
00:39To create a GHUnit test, I will need to create a new file. We will put it into
00:43the GHUnit test group that we created.
00:47So right-click, New file.
00:49We will select from the Cocoa Touch section the Objective-C class
00:55template. Click Next.
00:56We'll call this LocationGHTests and click Next.
01:02We will make sure that the test is associated with the GHUnitTests target; it's
01:08going into the GHUnitTests folder. Click Create.
01:13And here we see that it's created the normal two files, a header and an
01:17implementation file.
01:19It's common with unit tests to do away with the header files, so we will delete
01:24that and we will put everything into the implementation file itself.
01:29To create the test file, we will use the template provided by GHUnit on the
01:34web site, so we will delete this.
01:37Now we will switch over to the GHUnit web site, and in their documentation we will
01:42copy the first test template they provide.
01:47Select and copy, switch back to Xcode, and we will paste it into our test.
01:53The test template imports the GHUnit header file.
01:57We will rename the test to LocationGHTests for both the interface definition and
02:06the implementation, and now we will try building and running this.
02:12Remember that a GHUnit test doesn't use the test action; it is its own
02:17test runner application.
02:18So we will click Run and we see that the build fails.
02:22What's happened is that this test template was created before ARC and as such
02:28doesn't quite support it.
02:29They do provide assertions that do work though. We'll change from a NULL
02:35testing for the NotNil.
02:37Now we will rebuild and rerun.
02:39We see that the build succeeds okay, and we will run those tests, and we see
02:45that they all pass.
02:47If we click on a test's name, it will show us the console log for that test.
02:52In addition we see this extra Log statement, "I can log to the GHUnit test console:
02:58a string."
02:59If we look over at our test, we see that that test message is coming from this
03:04GHTestLog statement.
03:06This is a very cool feature of GHUnit.
03:08We are able within our tests to output formatted string information, and it'll be
03:15available on the device or the Simulator when you run the tests.
03:18So let's cancel out of the Simulator, and let's add an additional text.
03:24One of the features I really like about GHUnit is its assertion for equal strings.
03:30So let's create a simple test to demonstrate that.
03:33So we will test EqualStrings. I am going to create a GHAssertion,
03:38GHAssertEqualStrings. We will select that off the list, and I am going to create
03:45a couple strings that are the same with different case.
03:50Now for the description if this were OCUnit, I would need to provide a formatted
03:55string that would display the strings that were compared.
03:59OCUnit would normally only provide a message like not true.
04:03GHUnit itself will go a step further though and without providing a formatted
04:08string will provide that information itself. So let's see that.
04:12This test should fail, we are not providing a message for it.
04:15So we will save and run this.
04:20We see our new test here, let's go ahead and run the test. The test fails.
04:25We look at the test log, because this test failed GHUnit is providing a full
04:32stack trace, I don't find that really helpful myself, but in some circumstances
04:38I could see how that would be.
04:39But here it's given us a reason, and not only has it just said that the strings
04:44weren't equal, but it's showing us what the strings are.
04:47This is a very nice feature.
04:48So as we have just seen, GHUnit can run both OCUnit format tests and its own
04:55GHUnit format tests, which provide additional features.
05:00So it's worthwhile to get familiar with the GHUnit format.
05:04Review the GHUnit documentation on the web site for more information about the
05:08features available with GHUnit.
Collapse this transcript
Looking at asynchronous tests
00:00One of the advantages of GHUnit over OCUnit is its built-in support
00:04for asynchronous tests.
00:07To demonstrate the type of code where asynchronous testing can be useful, I've
00:11extended the Location model class.
00:14I've added a few lines of code that will use the iOS 5 CLGeocoder class to
00:20perform reverse geocoding.
00:21This means that will pass it a longitude and latitude, and it will return
00:26address information for that location.
00:28In this case, all we're really interested in is the postal code.
00:33Later on, we could use that postal code to get weather information for
00:36our current location.
00:38But, for now, we'll get the postal code and save it to a new postalCode property
00:43in the Location model object.
00:46If you're not familiar with the CoreLocation.framework or CLGeocoder
00:50specifically, refer to the Apple Developer Documentation.
00:55Looking at Location.h, we can see that two new properties have been created in an ivar.
01:01CLGeocoder *geocoder is our reference to the geocoder, postalCode is an NSString
01:08that will contain the returned postal code, and geocodePending is an ivar used
01:14to serialize access to the geocoder.
01:17In the implementation file, you will see that I have synthesized the two properties.
01:22Down in init, I initialized the two properties in the ivar;
01:27one of them being an instance of the CLGeocoder.
01:31I've created a new method, updatePostalCode, and down in the LocationManager,
01:36didUpdateToLocation from Location method and make a call to the
01:42updatePostalCode, passing in an event handler.
01:46Let's look at how to test the updatePostalCode method.
01:49When this method is called, it in turn calls the geocoder, its
01:54reverseGeocodeLocation method, passing it a completionHandler.
01:59This method is described in the Apple documentation.
02:03The documentation explains that this call is asynchronous, invoking the past
02:08block when the location data is available.
02:12It also states not to call it again after initiating a request.
02:16So I've created the geocodePending ivar to serialize access to the geocoder.
02:22So to test this method, we'll need to verify that first the geocodePending is
02:28initially NO; that was set up in init up here.
02:32Secondly, that after calling updatePostalCode, geocodePending becomes YES,
02:39and thirdly, that the call to geocoder results in the completionHandler being called.
02:46The first two items can be done using standard OCUnit synchronous tests.
02:51The third item, however, requires that we wait for the call to
02:55reverseGeocodeLocation to complete and ensuring that the completionHandler is called.
03:02Right off the bat, we see a problem with the new changes to the Location class.
03:07We'll need to access the geocodePending ivar from within our tests.
03:12Standard object-oriented best practices recommend that our implementation detail
03:17should remain hidden.
03:18This is referred to as encapsulation.
03:21This is good, but it is a hindrance to our testing efforts, and one will often run into.
03:27Objective-C provides a fairly simple solution to this;
03:31the class extension.
03:32A class extension is a category on a class defined without a name; perhaps the
03:38best way to understand this is to see it used.
03:40So to enable our test to access geocodePending, let's first convert it from
03:47an ivar to a property.
03:50In Location.h, we'll remove the ivar, and we'll create a property instead.
04:00Then in the implementation file, we'll synthesize that property.
04:09Now we can access geocodePending, but unfortunately, so can everyone using this class.
04:15This breaks our encapsulation.
04:17So we need to do something else.
04:20A good approach to use when unit testing is to move the things that you want
04:24to remain hidden into the class extension, and put the class extension in a separate file.
04:29We will give the special file a name to indicate its purpose.
04:35In this case, let's call it LocationForTesting.h. You could use other names like
04:40LocationInternal.h or LocationPrivate.h. I prefer LocationForTesting because it
04:47reminds me of why I created the file.
04:50Note also that we can and should put methods that we want to keep private in
04:55addition to properties into the class extension also.
05:00So first, let's run our test and make sure everything builds before making changes.
05:06GHUnit, we'll run that.
05:07It builds, loads, and runs okay, goes in the Simulator. We'll select our
05:17LogicTests and run those.
05:20Those all pass, and finally we'll build our code.
05:26Make sure we haven't broken the compile, and that all runs okay.
05:32So it's looking all right.
05:34Let's split the Location Header file into separate Location.h and
05:38LocationForTesting.h. Let's right-click on the file, show in Finder, and we'll
05:45use the right-click, Duplicate function.
05:49Let's rename LocationCopy to LocationForTesting, and back into Xcode, and let's
05:59add that file to the project.
06:01LocationForTesting, we don't need to copy it; it's already there.
06:08We're going to add it to all of the targets since these are either tests or the
06:13product code itself. Click Add.
06:17Now, we can see that the new file is up here.
06:20Let's open the new file, LocationForTesting; update our comments.
06:27We'll change the class definition to a class extension, which means putting the
06:33open and close parentheses, deleting all that.
06:37We'll delete everything except the two things we want kept secret except for our tests.
06:43In this case, it's the property BOOL geocodePending, and the calculateSpeedInMPH
06:50method. We'll keep that internal also; save that.
06:53Let's go over to our Location.h file, the original header, and we'll remove the
06:59things that we just moved into the class extension.
07:02So we will delete geocodePending, and we'll delete calculateSpeedInMPH.
07:08We can remove the ivar, don't need it anymore, and we'll save that.
07:14Now we'll need to import the new LocationForTesting header file to the two
07:19other files that need the location information.
07:22So we'll import it into LocationTests.h and we'll import it into GHUnitTests, and
07:37make sure that our location object itself has that.
07:43At this point, our code should build okay.
07:46Let's run our LogicTests; make sure they pass.
07:48LogicTests are good. We build our code, Command+B to build, and it builds okay
07:58with no compiler errors.
07:59Now we're ready to create an asynchronous test for the updatePostalCode method.
08:06I've created a new GHUnit asynchronous test case file.
08:11We'll add that to our project.
08:16It's in the GHUnitTests group, and we'll add it only to the GHUnitTests target.
08:23Since it's already there, I don't need to copy it, and it appears now.
08:29Looking at this code, I've already added the Location and the
08:32LocationForTesting headers.
08:34It includes the GHUnit.h file.
08:38It defines the LocationGHAsyncTests class file and implements it;
08:44it has a single method to testUpdatePostalCode.
08:49The way that GHAsyncTests cases work, as we can see here, is first you call self prepare.
08:56GH asynchronous test cases need to be told when the test is starting.
09:02It does some initialization.
09:05Secondly, we perform whatever asynchronous tasks we need to. In this case,
09:09we're creating a location object, then calling the updatePostalCode method
09:14of that, passing an event handler, and in an event handler, we've put some
09:19test code in there.
09:20We'll come back to this.
09:21Now we can perform whatever GHAsserts we need.
09:26In this case, we're verifying that geocodePending is set to YES right after the
09:31call to updatePostalCode, and then we wait for the asynchronous event to occur.
09:38This is a GHUnit asynchronous test mechanism used to tell the GHUnit
09:43asynchronous framework to wait the specified timeout for a successful completion status.
09:52Up in our event handler, we're passing that successful status through the
09:57self-notify, forSelector, and we pass in the selector to the test that
10:03initiated the request.
10:04Since this is asynchronous, and GHUnit might be running multiple requests at
10:10the same time, this tells it which one to associate the successful completion status with.
10:17When that happens, this will complete and the test will pass.
10:21So let's run this test and see what happens.
10:23I will select the GHUnitTests, run them, we build okay, we'll run our tests, and
10:33everything passes okay.
10:34Look at the log, nothing of interest there.
10:37Let's look briefly at what happens if the asynchronous event doesn't happen.
10:43Close our Simulator;
10:45let's comment out the call to update postal code.
10:49This should prevent our status from ever being posted or notified, and we'll run this.
11:01Rerun our test. We can see that the test failed, but the reason for the
11:07failure is not because of the lacking notification, but because our
11:11geocodePending was not set to YES.
11:14So that's as expected.
11:18That's this assertion failing.
11:20So let's comment this out, and we'll run our test again.
11:24Now the only thing that should fail would be the lacking notification.
11:30Let's rerun our test. It's running, and after five seconds, the request
11:36fails. It timed out.
11:37Let's go back up here.
11:39Let me show you how it looks on this screen.
11:42When we run an asynchronous test, if it's waiting, an asterisk appears next to the text.
11:46So this isn't telling you it's done, it's telling you that it's currently
11:50waiting, and then once it completed, of course, because it failed it turned red.
11:55So we've seen that ivars can be exposed to our tests by using properties
12:00instead of ivars, and placing them into a special header file containing a class extension.
12:06That's a great technique for dealing with this situation that comes up quite often.
12:10And we've also seen how to create a GHUnit asynchronous test to test methods
12:15that execute asynchronously.
Collapse this transcript
Testing an API with GHUnit asynchronous tests
00:00A powerful application of GHUnit's native asynchronous testing capability is the
00:05testing of host-based APIs.
00:08Let's take a look at how to test a web service API using GHUnit
00:13asynchronous tests.
00:15From my personal experience, problems with host-based APIs are the most
00:20problematic area of iOS development.
00:23Most applications interact with at least one web service.
00:27Web service API problems can occur from many reasons.
00:30Sometimes the API isn't understood correctly or the API server goes down or
00:35isn't implemented fully, or networking problems prevent accessing the server,
00:40among other problems.
00:42Something that can greatly improve the odds of success for a project is to
00:46create a set of tests that call the API and verify that the data returned looks as expected.
00:53This is especially true and helpful when the API is being developed
00:57simultaneously with the iOS client application.
01:02Let's look at how to create API tests.
01:05One of the features that we could add to WhatsMySpeed would be a display of
01:10the weather forecast.
01:11We'll query the weather web service to obtain the local forecast data.
01:16Google has an unofficial weather web service that we can use for this.
01:20Before we try to use this API, we'll write some GHUnit asynchronous tests to
01:25verify that the API works and also to confirm exactly what the data returned
01:31by the API looks like.
01:33To start off, we'll create a new TestCase file using the standard Async example
01:38provided with GHUnit.
01:40Come over to GHUnitTests. We'll right- click. Select New File. we'll use the
01:48Objective-C class. Click Next. We'll call this WeatherAPITests. Click Next.
01:52I'm going to assign this just to GHUnitTests, put it in the GHUnitTests
02:03folder, click Create.
02:07As with the other tests, we'll delete the header file.
02:14Now we'll go, copy the sample code provided with GHUnit and this is on the
02:19Documentation page under the ExampleAsyncTest.
02:21We need to make sure we pick up the event handlers, also not just the test.
02:33Come back to Xcode now, then we'll paste that here. Okay.
02:40Let's clean up these comments.
02:46Now the example as posted on GHUnit currently has some typos on it.
02:53So here we have this extraneous string the compiler is warning us this is wrong.
03:00Also, this code is not ARC enabled.
03:03So we'll need to get rid of any releases or autorelease.
03:07Here's another autorelease.
03:17Now the code looks fairly clean.
03:19We have a warning here because connection is not being used that was used for the release.
03:24We'll ignore that.
03:27Save this, and let's run this test, GHUnit in the Simulator.
03:34The build succeeded and we see our new testURLConnection test that was added.
03:40Let's run it, and it passes okay.
03:45Looking at the log, we see that the log is having the full response from the API written out.
03:52That's real handy.
03:53This is a good way to look at data release link and see what's being returned.
03:57Okay, so let's quit out of the Simulator. Let's go modify our test to test
04:03the API we care about.
04:06We'll rename our class, WeatherAPITests, and we'll change the name of our
04:13test to testWeatherAPI.
04:17One of the things we're going to need to do with this test, this sample code
04:22strictly tests for a response.
04:25So after the connection did finish loading is when the notify is sent.
04:32In the connection didReceiveData, the data is being written to the log but it's
04:37not being saved anywhere where we can test it.
04:40So we'll need to add a property to this. We'll call that our response and
04:46we'll synthesize it here.
04:53Now we'll need down in the connection didReceiveData instead of just writing up
04:59the string, we want to save it.
05:01So we'll create an NSString and we'll take the data where it's getting it right here.
05:07Okay, so we're saving the response. Go ahead and write that out to the test
05:16log like it had been.
05:18I'll also set that to our ApiResponse property.
05:25Now up in our test, following the Wait, we'll put a STAssertion in here.
05:31But right now, we don't know what to assert for.
05:34So let's go look that.
05:36First, let's change our vanilla google. com URL that was provided with the test,
05:41and let's change that to the weather API.
05:45I'm going to use an Austin postal code, and now we'll go look at the data and see
05:51what kind of data is returned.
05:52We're still writing it out to the log.
05:55So let's run GHUnit.
05:56We are going to ignore that unused variable.
06:00That was there because we deleted the variable that we saved in order to release it.
06:06Let's run our tests.
06:10Okay, we missed one other line of code. I'm going to quit out of this.
06:14We can see that there was a failed test there and the problem we have is that
06:20because we changed the name of the method up here, we need to remember to
06:26associate the selector with the response down here.
06:30So where we're issuing the notify with the selector, we have to give it the
06:34correct selector name.
06:36So I'm copying our method name up here, we'll try this again now.
06:42So running, saving and running, and here's our WeatherAPITests.
06:50Select that and we'll rerun it.
06:54Here we see that the test passed and the check mark indicates that.
06:59We can also tell this hasn't been updated.
07:04Let's run it again; let it update the display.
07:07Your testWeatherAPI is black with a check mark.
07:10Again, the check mark is shown in the log also.
07:16Now we're looking at the data that's coming back from the WeatherAPI.
07:19We'll need to be careful that we don't pick a string that will change with the weather.
07:25Looking at the response, we see this string here, forecast_information city data=Austin, TX.
07:32That's probably going to be good no matter what the weather does.
07:35So we'll use that in our test to confirm that this data is coming back from the API.
07:39So quitting out of Simulator, go back to Xcode.
07:43We'll want to insert the test after the wait for the response.
07:49So here we're going to add another GHAssert; True in this case.
07:56When performing unit tests, I recommend keeping them as simple as possible.
08:02So instead of trying to parse the full XML or anything like that, we're just
08:06going to do a simple substring check.
08:10So something that'll work well for this sort of test is to use the NSString
08:14rangeOfString method.
08:16We don't care about all the rest of that data in response, just that Austin, TX response.
08:21So we're going to GHAssertTrue, compare with the response,
08:27apiResponse rangeOfString.
08:35So the NSString rangeOfString will return NSNotFound if it's not there in
08:42the location property.
08:43So we'll compare against that and then for our description, we'll just say that
08:48the response didn't contain the string we're looking for.
08:52Okay, now let's run the tests.
08:55Down here in our WeatherAPI it failed, which says that we probably typed
09:08this information in incorrectly, and let's quit out of here and let's go see what we've done.
09:16So we have this string here.
09:17Well that's our problem.
09:20We're missing that trailing, closing slash.
09:26Try again and run our tests, and we'll rerun this failing test and now it passes;
09:36we have a check box there. Good to go!
09:40Using this sort of approach, we can add additional tests for however many API
09:44calls we need to verify.
09:46Typically, we will want to provide other arguments to the request to select and
09:51verify each of the APIs to be verified.
09:54So we've seen how easy it is to test web service APIs using GHUnit's
09:59asynchronous test capability.
Collapse this transcript
A summary of GHUnit
00:00 We've looked at some of the reasons for using GHUnit, its ability to run as a
00:05 standalone application on either the Simulator or on a device.
00:10 We've seen that it provides some additional assertions, not available in OCUnit.
00:16 We've seen asynchronous tests which are especially powerful for testing web service APIs.
00:22 GHUnit requires manual setup to add it to a project, but this isn't very difficult.
00:28 GHUnit is not as convenient to use as OCUnit, but provides additional
00:33 capabilities that can provide the solution needed in some circumstances.
00:38
Collapse this transcript
4. Debugging Unit Tests
An overview of debugging
00:00 When writing unit tests, sometimes you'll need to debug and fix problems that
00:04 occur in your test code.
00:05 We'll look at how to do that in this chapter.
00:07 Debugging unit tests is exactly the same as debugging other code.
00:11 Breakpoints are set into the code, a test run.
00:15 Once the breakpoint is hit, then you can step through the code line by line.
00:18 You can examine the variables and so forth.
00:24 One of the interesting aspects of debugging with unit tests is the fact that you
00:28 can disable all but the failing unit test.
00:31 Editing the scheme, selecting the test, all of the tests can be individually
00:36 enabled or disabled.
00:37 If we were debugging for example the testCalculateSpeed, we would turn the other
00:41 tests off, and now when we run our test, only the one test is running.
00:54 In the event that a bug is discovered in code that does not have unit tests, a
00:58 unit test can often be quickly created to expose the bug.
01:01 This then allows getting quickly to the failing code for iterating over all debugging.
01:06 We'll be primarily focusing in this chapter on using OCUnit and Logic Tests
01:10 running on the Simulator, but understand that the same can be done on an iOS
01:14 device using OCUnit application test or GHUnit.
01:18 Unit tests are easy to debug and are done the same way that you debug other code.
01:22 Unit tests can also simplify debugging of your product code by making it easier
01:27 to get directly to the failing code.
01:29
Collapse this transcript
Debugging OCUnit tests
00:00 Let's look at how to debug a bug in the unit test code itself.
00:04 I've added a bug to one of our unit tests, so let's look at how to debug it.
00:08 Let's open our code and run the test.
00:09 Let's clean and run them again.
00:12 Now, we see the failure.
00:17 It's telling us that the mph = 55, but it's not.
00:21 So we can set a breakpoint and step through this code; Product > Test.
00:26 We stop on the first line, we'll step over it, and we'll inspect the mph, and it's 198,000.
00:31 So that number is way too high.
00:34 Using a calculator, I had previously calculated that the meters per second value
00:39 to pass to calculateSpeedInMPH method to result in a 55 mile per hour in
00:44 response should be 55 * 1609.344 / 3600, which would equal 24.5872.
00:52 So let's replace that with the calculated value 24.5872 and we'll comment the
01:01 rest of the line out.
01:02 Save that and we'll run our test.
01:07 Stop the previous test, and test runs hits our breakpoint, step over it, and now
01:12 our MPH is 55 as we expected.
01:15 So obviously, the error is somewhere in this line of math here.
01:19 We're going to go ahead and stop the debugger.
01:23 And inspecting the code, I see that we probably have made a mistake, and that
01:29 we should have grouped our 60 * 60, so the operation of operators in this case is left to right;
01:36 so we'll need either grouping around that.
01:38 Even better would be to break those arguments out, instead of having hard
01:42 numbers, give them more descriptive names.
01:45 So metersPerMile 16.9, secondsPerHour, and now we'll perform the
01:50 calculation again, replace this with metersPerMile, and divide by not
01:57 60 * 60, but secondsPerHour.
01:59 We'll save that, remove our breakpoint, running our test again, and the tests all succeed.
02:08 We get three passing tests, no failures.
02:10 Now, when we were getting that failure before, okay, so let's reproduce the error again.
02:16 We'll replace our secondsPerHour with 60 * 60, which we know resulted in
02:21 error the first time.
02:22 I save that, and let's run those tests again.
02:28 This time we see the failure again, but notice that in our console log both
02:33 our message up in the code and within the log itself, the actual error was
02:37 reported and it told us that it was expecting 55 mph, but was reported back as 198,000 something.
02:45 Well, that's the same information we got by breaking and stepping through the code.
02:50 This is an example of how using good, meaningful error messages with formatted
02:55 data in them can save us having to get into the debugger at all.
02:59 Using standard Xcode debugging techniques, bugs in your unit test code can be
03:03 quickly identified, fixed, and verified.
03:06 Even better though, providing the details of a failure in the assertions failure
03:10 message can allow determination of the cause of a failure without having to even
03:14 step through the code with the debugger.
03:17
Collapse this transcript
Providing debug info in STAsserts
00:00 When writing unit tests, providing useful information in the assert messages can
00:05 greatly speed determining the cause of any failure.
00:08 The STAssert macro supports displaying an error message, which can contain
00:12 standard printf/NSLog parameter placeholders with formatting codes;
00:17 when a Test Assert fails, the error message is displayed.
00:20 First though, let's see what happens if the optional error message string isn't
00:25 provided for a failing test.
00:27 Let's rerun the failure we saw in the previous video.
00:30 We'll run the test.
00:34 We saw an unexpected result, so we will clean the code first, and run the test again.
00:42 Now, we see the test failure.
00:45 We see looking in the console log or the message displayed in the Code Editor
00:49 that because we've provided a meaningful message string, we're able to determine
00:53 that MPH is way off the scale 198,000 instead of 55.
00:58 So we'll be looking for some sort of growth here.
01:00 Let's look at what would happen though had we not provided an error message.
01:04 Go in and we will remove the message, and let's run our test again.
01:12 We get the failure.
01:13 This time the only information we get is mph = 55 should be true. Looking in
01:19 the console log, we get the same thing; 55 should be true.
01:23 So let's restore our message. Here we go!
01:31 The failing value is now displayed, not just in the console log, but also in the
01:35 error message display for the failing assert in the test code.
01:39 More often than not, providing debug information for each test can eliminate the
01:43 need to use the debugger to collect that information.
01:46 Providing useful information into Unit Test Assert messages can speed up
01:50 determining the cause of problems often without resorting to using the debugger.
01:55
Collapse this transcript
Debugging GHUnit tests
00:00 Debugging GHUnitTest is a bit different from debugging an OCUnit.
00:05 Let's take a quick look at those differences.
00:07 We'll use the Simulator, but this works exactly the same way using an iOS device.
00:12 We'll use the same bug we've been looking at in the previous video and we'll see
00:16 what some of the differences in GHUnit are.
00:18 So, let's run the test, run with GHUnit instead of selecting the Test Action,
00:24 it's run, just require the Simulator to come up, build is happening.
00:28 Now we need to run the test in the Simulator and our failure is reported.
00:32 We can click on the failure, this opens the detail view and here we see the failing test.
00:38 Reason displays our formatted test message from our assert, and here we can see
00:43 the failure of mph being 198,000.
00:46 Following reason is a stack trace.
00:48 This could be useful if an exception occurs somewhere in the code besides a test assertion.
00:54 We'll ignore it in this case.
00:56 Note also, since GHUnit runs as a standalone application, it doesn't
01:01 automatically cause the failing test line to be highlighted like OCUnit does.
01:05 However, we have the log to help us.
01:08 Let's close the Simulator and let's insert a break at the first line.
01:13 Now, let's run it again.
01:16 Code is rebuilt, and reloaded.
01:18 Let's select our test and just run it, rerun, debugger hits our breakpoint. Let's step over.
01:25 We can hover on our values and inspect them;
01:28 so that's all the same.
01:29 Let's stop the debug session.
01:32 So, putting our parentheses around 60 * 60 is still the fix for this issue, save
01:38 it, move the breakpoint, run it.
01:41 Note that GHUnit remembers the status of previously run test.
01:45 The status won't change until you rerun the test again.
01:48 Let's go ahead and run them and the previously failing test is passing now.
01:52 So, when debugging a test, we're able to rerun that test several ways.
01:56 One, we can tap into the test, and rerun just it, or we can run all of the
02:03 tests, or we can select all failed tests, and just run the failing ones.
02:07 Debugging GHUnit test is not much different than debugging any other code, and
02:12 can be done on both the Simulator and a real iOS device.
02:16
Collapse this transcript
Debugging product code using unit tests
00:00 Now, let's take a look at how to use unit test to help debug a bug in the product code.
00:06 I'm going to add a bug to the location model code and show you how unit tests can
00:09 help in debugging it.
00:11 Let's open up our code and let's insert a bug.
00:13 So we'll select in the location model; let's make what could be a pretty easy
00:18 bug to accidentally do. We'll change what should be a float to an int.
00:22 Code looks basically right.
00:23 If you weren't thinking about it, that could easily occur.
00:26 Now let's run our test; Product > Test.
00:28 You see that it executed the test with one failure.
00:33 Now, since the bug is in the product code and not in the test code, we won't
00:39 have the source of the bug highlighted.
00:42 The failing test will be highlighted, and we can see that the expected 55 miles
00:48 an hour came out wrong.
00:49 It was actually 54.999428, which is just a slight difference in truncating the
00:55 floating value to an int.
00:57 Okay, now let's debug this.
00:58 So let's set a breakpoint in beginning of our test, and we'll rerun the test.
01:05 Once the debugger hits the breakpoint, it will stop and we can begin to step
01:08 through, looking at each value as it's calculated.
01:12 So this is our test setting up.
01:15 Now, this is the point at which we're going to call the code under test.
01:19 So let's try to step into that and here is our location property, continue to step into.
01:25 Now, notice that we did not step into the calculateSpeedInMPH like we had hoped to.
01:31 That's okay.
01:32 We can go ahead and move into our model code and set the breakpoint there.
01:36 So let's stop this test, go into our model code that was being called, and we'll
01:43 set a breakpoint here.
01:44 So we'll run our test again. The debugger, when it calls into our model code,
01:51 stop on the debugger and we can step over there.
01:54 Step over and evaluate our code there.
01:57 So this is a pretty good indication of what's wrong right there.
02:00 We're seeing a big negative value come back, because it actually should be a float.
02:04 Okay, so let's go ahead and fix our problem.
02:07 We'll change this back to a float, save that, move our breakpoint, rerun our
02:12 test again, stop the currently running test, restart, and this time, we see
02:19 three tests with no failures.
02:21 So we've seen that unit tests can get you to the failing code quickly.
02:25 If you already have unit tests, then take advantage of them when debugging
02:29 your code.
02:30
Collapse this transcript
5. Using Mock Objects
What are mock objects?
00:00 Mock objects are simulated objects that mimic the behavior of real objects for testing.
00:06 Unit tests are by design intended to focus on a single unit of code.
00:10 Often though, the unit of code under test references other objects.
00:15 In order to run the code under tests, these other objects must be created also.
00:19 For example, to test this method, we'll need to create an instance of the
00:24 fooObject and pass it to methodUnderTest.
00:26 We'll then need to know the value of property1 on fooObject to verify that this
00:33 method returns that value.
00:35 So we could do something like this.
00:37 Here we have a test method.
00:39 In the test method, we'll create the ObjectUnderTest, which will be calling.
00:43 Then, we'll create an instance of the fooObject to pass to it.
00:48 Then, we'll read the fooObject property1 to determine what value we expect from that.
00:54 Then, we'll make the call to testObjects methodUnderTest, passing it our fooObject.
00:59 Then, we'll compare the value that was returned with the value that we expected,
01:04 and if they're not equal, we'll issue our message, and our error message,
01:08 following unit testing best practices, we'll display the value we expected and
01:13 the value that was returned.
01:14 But this approach has several potential problems.
01:17 What if the value returned by fooObject's property1 changes between the two calls?
01:23 This could happen for example if foo's property1 uses real-time data such as a
01:28 timestamp or if it increments a counter every time it's called.
01:32 What if there is a bug in foo's property1 that there was an exception?
01:36 We're not trying to test problems with fooObject 1;
01:40 we don't want issues in foo to cause errors to be reported from method under test.
01:44 What if the dependent object in turn depends on other objects in order to be
01:49 instantiated or before running the required method?
01:52 To get around these sorts of problems, we can substitute a fake object for the real one.
01:58 In order to test this method, we just need the fake object to return a known
02:02 test value when its property1 is read.
02:05 Then, we can test our method to ensure that it returns that test value.
02:09 A fake object that returns a predefined value for testing is referred to as a stub.
02:16 Let's change our test.
02:17 So in our test, we'll create the fake object, the stub, we'll pass it then
02:22 into methodUnderTest;
02:23 methodUnderTest in turn will then call the stub instead of a real object.
02:29 The stub returns the known fixed test value, which then is returned back to our
02:34 test and our test compares those two values.
02:37 Another type of fake object is a mock object.
02:40 A mock object is used to verify whether or not methods and properties on the
02:45 fake object are called as expected.
02:47 So, in this example, we would expect that property1 will be called
02:51 by methodUnderTest.
02:53 If it isn't, then something is wrong.
02:55 This difference between a stub and a mock object can be a bit confusing. To
03:00 restate, a stub is used to return a predefined value and a mock is used to
03:05 verify that an expected method call occurs and/ or that expected property reads and writes occur.
03:12 In the upcoming videos in this course, we will be looking at how to install and
03:16 use the OCMock framework to create mocks and stubs.
03:20
Collapse this transcript
Installing OCMock
00:00 Creating mock objects and stubs in Xcode is easy using the OCMock framework.
00:05 Let's look at how to add the OCMock framework to a project.
00:08 First, to get a copy of OCMock, search the Internet for OCMock.
00:12 As of the time of this video, OCMock version 1.77 is available for download from ocmock.org.
00:21 Select the Download section.
00:23 The Source code is also available; currently on github.
00:27 Once you have located the OCMock executable package, download it and open the DMG file.
00:34 This will open a folder with both a Source and Release folder.
00:38 The Release folder contains both a library and a framework.
00:42 The framework is the easiest to use, but as of the time of this video, does not
00:47 support running on an iOS device.
00:50 So let's use the Library, so that we can run our tests on both Simulator and an iOS device.
00:55 Remember that GHUnit and OCUnit application tests can run on the devices as
01:00 well as the Simulator.
01:02 If you're running only OCUnit LogicTests then the framework won't work.
01:06 So let's copy the Library folder to our Project folder, and we name it OCMock.
01:19 Back in Xcode, we will add the OCMock library to the LogicTests > Build Phase > Link
01:26 Binary With Libraries.
01:34 Verify that the Library is added to LogicTests target only.
01:39 In addition, we will need to add the header files to the LogicTests > Build
01:44 Settings > Header Search Paths.
01:54 We'll also need to set the other linker flags to add the -Objc-all load
02:00 other linker flags.
02:02 At this point we want to run our unit test to make sure we haven't broken
02:05 anything and our test build and execute okay.
02:13
Collapse this transcript
Creating OCMock stubs
00:00 Stubs are a type of fake object used in unit testing;
00:04 they allow the codeundertest to make calls to methods on other objects during the test.
00:09 They often return a predefined value.
00:12 OCMock provides the ability to quickly create stubs for unit testing.
00:16 Having added OCMock to our project, let's look at how to use it to create a stub.
00:21 Looking at the Location model and the WhatsMySpeed project, there are several
00:25 methods that are not yet been tested.
00:27 One that appears problematic to test is the LocationManager did update to
00:31 location from location delegate method.
00:34 This delegate method handles location updates.
00:37 It does two things.
00:38 First, it calls the UpdatePostalCode method to calculate and then update our
00:44 postal code. Secondly, it updates the speed property using the data provided from
00:49 that location update.
00:50 So we're going to need two tests in order to test both of these things.
00:54 To test the method updates the speed property, we can pass a newLocation object
00:59 to the delegate method that contains a stub for the speed property that will
01:03 return a specific speed value that we specified.
01:06 That's sort of a mouthful;
01:07 let's see how that actually works.
01:10 So in LocationTests, we'll add a new test here to test that.
01:15 So first, we'll create a new test, call it
01:18 testLocationManagerDidUpdateSetsSpeed.
01:21 Now within this new method, we'll create a mock CLLocation object.
01:26 Now we'll pass this fake CLLocation object to the Location
01:33 ManagerDidUpdateToLocation fromLocation delegate method in both the two location
01:37 and the from location parameters.
01:39 The locationManager parameter isn't used, so we'll just pass nil.
01:44 After calling the delegate method, we'll test that our speed property has been
01:48 properly set to 55 miles an hour.
01:50 Since our method under test is doing two things and we're only testing one,
01:55 we'll need to make sure that the other things don't interfere with our test.
01:59 Looking at the other thing, which is calling updatePostalCode we see that this
02:04 method returns immediately, if the geocodePending property is set to YES.
02:09 So let's set geocodePending to YES to remove it from being executed during this test.
02:18 Now let's run our test and see what happens.
02:25 Our test is failing;
02:26 looking at the console log, we see that the error is caused because unexpected
02:30 method invoked speed.
02:33 Actually, this is expected because we haven't defined the stub for the speed
02:37 property method yet.
02:39 So let's do that now.
02:42 The mock stub method informs the mock to expect a call, and we can tell it to
02:47 return a specific value. If we're passing in a set value, then we'll need to use
02:53 the OCMOCK_VALUE macro as opposed to passing in an object.
02:58 We're going to be passing in a double metersPerSecond.
03:01 So we'll use the macro, OCMOCK_VALUE and we'll tell it to expect to receive the speed method.
03:08 Now we get another here. In this case, OCMock cannot tell what the speed
03:14 method signature is.
03:15 So we'll need to help it out by casting the mock object to the specific object
03:20 we're impersonating.
03:21 In this case, CLLocation and that clears up that error.
03:25 Now we'll rerun our tests, and the test passes.
03:31 So let's review our test case code.
03:33 First, we are creating an OCMockObject that will simulate or fake the CLLocation
03:39 class while calculating our metersPerSecond, and then we're telling the mock
03:43 object that it'll ReturnValue.
03:46 We instruct the mock object to provide a stub for speed and return that
03:50 calculated metersPerSecond value.
03:52 Then we set Geocode to YES.
03:55 This is a bit of a hack and we look further around on how to do this in a better way.
03:59 But this skips a lot of the code that we are calling, so we're not a testing
04:03 it also this point.
04:04 Now we make the call to the method under test passing our mock object into it,
04:09 and then we verify that the speed property was set as expected to 55 mph, and
04:14 that's how to use OCMock to create a stub.
04:17 OCMock can be used to create stubs that can be called by your code under test.
04:23 The stubs can return specific predefined values that make testing much simpler.
04:28
Collapse this transcript
Expecting and verifying calls to a mock
00:00 Mock objects can be used to confirm that the code we are testing makes calls to
00:05 specific methods on an object.
00:07 Let's look at an example in our WhatsMySpeed code where this is useful.
00:11 There are still a couple methods in our Location model file, Location.m, that do
00:16 not have any unit tests, startLocationUpdates and updatePostalCode.
00:20 Looking at the updatePostalCode with handler we see that it does three things.
00:25 First, it returns immediately if geocodePending is Yes; otherwise it sets
00:31 geocodePending to Yes and then calls the geocoder reverseGeocodeLocation
00:36 completionHandler method.
00:38 To test this method we will need to verify whether or not geocode
00:42 reverseGeocodeLocation gets caught.
00:44 We can use a mock object for this. We will need to create three unit tests for this.
00:49 First, verify that when geocodePending is NO, the geocode ReverseGeocodeLocation
00:55 is called with correct parameters.
00:57 Second, verify that when geocodePending is NO, the geocodePending is set to YES.
01:03 And third, verify that when geocodePending is YES, the reverseGeocodeLocation is not called.
01:10 It is tempting to combine one and two, but I recommend that tests each test
01:14 only a single thing.
01:16 So let's create three tests. Test names should read like a sentence describing
01:20 what they expect to happen.
01:22 So we will name the tests test updatePostalCode calls reverseGeocode when
01:26 Pending No, testUpdatePostalCode does not call reverseGeocode when Pending Yes,
01:32 and testUpdatePostalCode sets pending.
01:35 Let's create the first test.
01:37 Going into LocationTests.m we will want to put the tests in the order in which
01:42 they appear in the code.
01:43 So looking at our Location.m we will be testing the updatePostalCode method and
01:50 that comes right after startLocationUpdates, which is untested right after init.
01:55 So we will scan down through our init tests, and we will add that following those.
02:00 So the first test we will want to create
02:02 testUpdatePostalCodeCallsReverseGeocodeWhenPendingNo. First, we will need to
02:07 create a mock object for the CLGeocoder class.
02:11 Then we will need to assign the mock object to the test location
02:14 object's Geocoder property.
02:17 Then we'll need to tell the mock object to expect a call to
02:20 reverseGeocodeLocation.
02:23 Note that we don't care about the parameters so we'll just pass nil.
02:26 Then we will call the method being tested, and finally ask the mock to verify
02:32 that the method was called.
02:33 Now we will run the test and verify all tests passing.
02:38 For the second test, we can simply copy and edit the first.
02:41 This is a very common practice when writing unit tests.
02:45 Typically the first test takes a little while to figure out and then the rest
02:48 are easily copied and modified.
02:51 We initially get the error because the name is the same, but we are going to
02:53 change that right now.
02:55 On this test we are going to test that
02:56 PostalCodeDoesNotCallReverseGeocodePendingYes.
02:57 Now we will set the geocodePending property to Yes.
03:07 Just for fun let's run the test now expecting it to fail.
03:12 And it does. It reports that the
03:14 testUpdatePostalCodeDoesNotCallReverseGeocodeWhenPendingYes did not receive a
03:19 call to the expected method. That's as we expected.
03:23 So now let's delete the mock expect statement because we're not expecting that
03:28 method to be called, since we've set geocodePending to Yes, and now we will rerun
03:32 our tests, and in this case we executed all seven tests with no failures.
03:37 When an unexpected method on a mock object is called, meaning that we haven't
03:42 set mock expect for that method, OCMock will throw an exception causing the test to fail.
03:48 Let's see an example of this.
03:49 In the first test, let's remove the mock expect line.
03:53 I am going to comment this out and let's run our test now.
03:58 We can see that we got one failure. Scrolling up to the failing line, we see the
04:02 message that unexpected method was invoked.
04:05 This is a very powerful feature of OCMock, making sure that unexpected
04:09 things aren't happening.
04:10 It is possible to configure a mock object to ignore unexpected calls.
04:15 This is referred to as creating a nice mock. In a later video we will take a
04:19 look at why and how to do that.
04:21 Now let's remove the comment line to restore all tests passing.
04:25 Let's create the third test. We will copy and paste the first test again, and we
04:30 will rename this test testUpdatePostalCodeSetsPending, and in this case we will
04:40 replace the mock verify line with an assertion to verify that geocodePending is set.
04:52 And running our tests now, we see that we get eight tests passing now with no failures.
04:59 Note that in this last test we weren't really worried about the call to geocoder,
05:03 so this might've just been sent to nil.
05:05 But since we'd already created a mock object in a previous test, it was just as
05:09 easy to go ahead and do the same here.
05:11 Using OCMock to verify that expected method calls are made is easy and detecting
05:17 unexpected method calls is even easier.
05:20
Collapse this transcript
Exploring OCMock partial mocks
00:00 OCMock has a powerful feature allowing a Mock object to be combined with the real object.
00:07 Methods that are stubbed are handled by the mock while all other methods are
00:11 handled by the real object.
00:13 Often when unit testing a class method, the method under test will call another
00:17 method on itself, so how does a test verify that this other method gets called?
00:22 There are several albeit awkward ways to do this.
00:26 First, we could create a subclass of the object being tested and override the
00:31 method being called to set a flag or property indicates that it's been called.
00:36 This would work but it is complex.
00:38 Secondly, we could inject test code into the method doing the calling or being called.
00:43 This is a really bad idea.
00:45 Refactoring or modifying of code to support unit testing should make the code
00:49 easier to understand and maintain, this would be doing the exact opposite.
00:54 Fortunately, the first option is already supported by OCMock using what's
00:59 called a partial mock.
01:00 Let's take a look at how this is done.
01:03 A partial mock is a special type of mock object that is expected to
01:07 override some of the methods of an object, but then let the original object
01:12 handle everything else.
01:13 Let's look at an example in WhatsMySpeed code where this is useful.
01:18 Let's open the LocationTests, and we'll view the location object being tested on the right side.
01:27 At this point our LocationTests are only testing part of the locationManager,
01:33 didUpdateToLocation, fromLocation delegate method.
01:36 The current unit test only tests the speed is set; it's not testing
01:41 didUpdatePostalCode is being called.
01:44 Let's add another test.
01:46 Let's duplicate the existing test, and we'll rename this test
01:59 testLocationManagerDidUpdateUpdatesPostalCode.
02:07 We can delete the two lines at the bottom that are testing the speed is set.
02:12 We can also delete the GeocodePending line.
02:15 Now we'll create a partial mock of our test location object. We'll call this
02:22 mockSelf, and I'll make this a partial mock object, and we'll specify that it's a
02:30 partial mock object for our location object.
02:35 Now let's tell our partial mock object to expect to call to updatePostalCode.
02:42 And since we don't care about what argument is passed, we'll use the OCMArg any;
02:49 again same with the Handler, OCMArg any.
02:51 Now we'll change the call to locationManager didUpdateToLocation fromLocation
02:58 to make that call on our partial mock and instead of the location.
03:06 And finally, we'll ask our partial mock to verify that it receives the call
03:12 to updatePostalCode.
03:15 Now running our tests, we see that we have nine tests passing now with no failures.
03:24 So to review this code we've copied the previous test, which did all the hard
03:28 setup work for us, we created a partial mock and then we called the partial mock
03:36 instead of calling the location object itself, and then we asked it to verify,
03:41 to expect and verify that a call is made to updatePostalCode.
03:45 So here we've seen that partial mocks are powerful feature that support
03:49 overriding some of the methods of an object.
03:52 In this example, we saw that it made it easy to verify that the call to
03:56 updatePostalCode was made.
03:58
Collapse this transcript
Reviewing unit test coverage for an existing class
00:00 Now that we've seen how OCMock can be used to simplify unit testing, let's go
00:05 ahead and complete our unit tests for the location model class.
00:10 We'll leverage Xcode's ability to display two edit windows side by side.
00:13 This is helpful when reviewing unit test case files, to ensure that they cover
00:18 all of the methods in the class under test.
00:21 We'll display the LocationTests on the left, and then review top to bottom the
00:26 code under test at the location model.
00:29 So starting at the top, we come down and we find the init.
00:33 Within our test file we find the testInit.
00:36 Here we're testing that the object can be created.
00:39 Next we see that init sets the postalCode for a default value of unknown.
00:44 We don't have a test for that, so let's add it.
00:47 We'll keep our tests in the same order that they appear in the code.
00:53 First, we'll get the postalCode from the freshly initialized location.
01:01 Then we'll Assert that it's equal to the string unknown.
01:07 We'll display an error message if it's not, and show what the incorrect string is.
01:16 Let's run our test, and we have 10 tests, 0 failures. So let's move on.
01:26 Next we see init sets geocodePending to NO.
01:30 We'll write a test for that.
01:35 Here we'll use the STAssertFalse, as we are looking for a NO value.
01:43 Normally, I would run the tests after writing each one.
01:46 But we have a bunch to do here, so we'll go ahead and enter them all and run
01:50 the tests at the end.
01:52 Moving down next we see the geocoder is set.
01:55 We'll write a test.
01:56 I'll test for NotNil.
02:02 Next we set locationManager.
02:05 We do have a test already for that, so we'll skip over that test.
02:09 Next we see that the locationManager delegate is set. We'll test that.
02:14 We can do a better test than just checking for nil.
02:17 Here we'll check that what the delegate is set to is actually the object we
02:22 think it is, which is our location object. I'm moving down.
02:26 Finally, we see that two properties on the locationManager are set.
02:36 And again, normally I'd run test after creating each one.
02:39 We'll go ahead and finish this out, then run the test once.
02:42 Okay so at this point there are tests for everything init does.
02:47 Moving down the code we see the next method is startLocationUpdates.
02:49 All that startLocationUpdates does is call the locationManager's
02:54 startUpdatingLocation method.
02:58 So that's a great opportunity for using a mock.
03:00 So let's go ahead and create a test for this.
03:05 We'll create the mock.
03:08 We'll instruct the mock to expect a call, to its startUpdatingLocation method,
03:17 and we'll set our test objects reference to locationManager to point to the mock instead.
03:24 We'll execute the call to that code, and we'll ask the mock to verify that it
03:31 received the expected call.
03:34 And at this point all of the location model code is tested.
03:37 Let's run our test now; see if we made any mistakes.
03:45 And we get 15 tests completed, no failures.
03:48 One final note regarding OCMock. OCMock provides even more features that we
03:53 haven't had time to explore in this video.
03:55 These more obscured OCMock features could prove helpful in trickier test situations.
04:01 It's worthwhile to become familiar with them, and review the information on the
04:04 OCMock web site when you get into a tricky test situation.
04:09
Collapse this transcript
6. iOS-Specific Examples
Forming an iOS unit testing strategy
00:00 Normal iOS programming best practices present a few situations that can be
00:05 challenging when first encountering them.
00:08 These situations include ViewControllers, Notifications, Singletons, Gestures
00:14 and other input events, CoreLocation and interacting with Web services and Web APIs.
00:21 We've already looked at CoreLocation, Web services and APIs, and done some
00:26 testing of our ViewController.
00:27 We'll be looking at the rest of these issues in the remainder of this section.
00:32 But before we do let's take a step back and think about what we are trying to accomplish.
00:36 It should be possible to create meaningful tests without having to jump through
00:40 hoops or spend an unreasonable amount of time creating tests.
00:44 We need to have a clear unit testing strategy.
00:48 First of all, don't waste time testing Apple's code.
00:52 The boilerplate code created by Xcode is tested by Apple and a gazillion other
00:56 developers and testers; we don't need to test it.
01:00 Limit your test coding efforts to testing the code that you write.
01:03 Secondly, don't spend a disproportionate or unreasonable amount of time writing tests.
01:10 There may be situations that could require hours or even days to figure out
01:14 how to write tests for some small bit of code that is unlikely to ever break or change.
01:19 Remember why we are writing unit tests to help understand and document what the
01:24 code is supposed to do, verify that it does it.
01:27 Catch errors in the future if and when the code changes, catch errors if
01:31 and when outside dependencies change, reduce the time spent in the debugger, etcetera.
01:37 By keeping these in mind and understanding the development process for the
01:41 project, making trade-offs may be possible.
01:44 Another way of saying this is, think about what you were doing.
01:47 Most of the time writing unit tests means spending a few minutes or hours to
01:52 save potentially days or weeks of debugging, and reducing the odds of
01:56 delivering a bug to the field.
01:58 Don't spend days writing tests that potentially can save you minutes or hours of
02:03 time debugging and don't really reduce the odds of shipping any bugs.
02:07 I know that saying this is going to run against the normal unit testing party
02:11 line, so let me give you an example.
02:14 Suppose that I'm working on a project that will include good old-fashioned human
02:18 QA testing at the end of each sprint.
02:21 This QA testing is intended to catch UI issues, performance issues, and any
02:26 other unexpected issues that somehow slip past the automated tests.
02:29 In this environment it might make sense to spend a few minutes adding additional
02:34 items to the QA test instead of writing some really complicated test code.
02:40 Keep in mind however the manual tests need to be run repeatedly but test code
02:44 only needs to be written once.
02:46 Use your head and choose the type of testing that will yield the greatest
02:50 return on investment.
02:52 Let me make sure that I'm being clear about an important aspect of what I just said.
02:57 If we don't write a unit test for a piece of code that we have or will write,
03:01 then we need to have testing of that code documented somewhere else.
03:05 In this example, in the QA test plan, we do not want to end up shipping untested code.
03:11 iOS can present some pretty challenging unit testing issues.
03:15 Use your head and create meaningful tests that maximize the results for the
03:19 amount of effort required.
03:21
Collapse this transcript
More on OCUnit application testing
00:01 Up to this point we've been creating the infrastructure of the application,
00:05 but we're still not doing the main thing that the app is intended to do
00:09 telling the user their speed.
00:11 So let's create a view to display speed and write an application test to verify
00:15 that is connected in the nib.
00:17 Let's open our project and display the storyboard.
00:20 I can even drag a label under the top of the display and then resize it to fit
00:27 snugly on the edges, filling about the top one-fourth of the display.
00:33 Select our Properties inspector, and let's change the Text to read Calculating,
00:39 and we will center the text and let's make it larger, Minimum Size about 32.
00:46 Select the Size, and let's set it straight, and middle spring.
00:53 Now let's close the Utility view, open up the Assistant, make sure it's Auto.
01:01 That will display the header file associated with the view. We'll select the
01:06 label and I will right-click and drag or Ctrl+Drag into the header file. We'll
01:12 put it under the existing outlet and release, and we'll call this speedLabel and click Connect.
01:19 This will create a property in our header file and synthesize it in the
01:24 implementation file.
01:26 So now we have an outlet and its property.
01:29 Because we let Xcode create it for us by using the Ctrl+Drag technique, we can
01:34 be fairly sure that the label is connected to its outlet, but let's write a
01:38 test to confirm this.
01:40 This is an example of the sort of thing that OCUnit ApplicationTests are great at.
01:45 So let's add a test to our ApplicationTests test case file. Before we do though
01:50 let's run the test and verify that everything works okay before we make changes.
01:54 So we'll select WhatsMySpeed, Product > Test. Simulator come up and run.
02:08 Let's close the Simulator.
02:10 View our Console and see that we have four tests, all passing.
02:15 So now let's open the ApplicationTests implementation file, scroll to the
02:21 bottom, and let's add our test here, and we'll Assert that the SpeedLabelOutlet is NotNil.
02:35 Run the test again and observe that we should have five tests now, all passing.
02:41 And we do, close our Simulator.
02:44 Now let's run the app.
02:45 We observe that Calculating is displayed, we've created a view for displaying
02:55 speed but haven't written a calculated speed data to it.
02:59 We'll do that in a later video.
03:01 OCUnit ApplicationTests are great for verifying that our outlets are connected.
03:06 While there may not be much of chance of these connections breaking, they can be
03:10 broken and creating the test to catch this is so easy, that it just doesn't make
03:14 sense not to test them.
03:16
Collapse this transcript
Setting up a ViewController for testing
00:00 From a testing perspective, ViewControllers are just big objects.
00:04 What makes them somewhat unique is the fact that there is always at least one of
00:08 them in every MVC application, and they tend to be fairly large and overly
00:12 complex if not created with unit testing in mind to begin with.
00:15 Luckily, we're creating unit tests as we go.
00:19 Our ViewController is mostly Apple boilerplate code at this point.
00:23 Let's set it up so we can add tests as we add new code.
00:27 To test the ViewController in a logic test, we need to create an instance of it in our test.
00:32 We'll do that in the setup method.
00:34 So let's start by creating a TestCase file and using setUp and TearDown to
00:38 create a fresh instance of the ViewController for testing.
00:41 So in our Logic Test group, we'll click New File. We'll select the Objective-C
00:47 test case class, and click Next.
00:49 We're going to call this ViewControllerTests.
00:54 Notice the Logic and Application dropdown box below here, Test Type.
00:58 New to Xcode 4.2, this dropdown box provides a different set of configuration
01:04 based on whether the test case file is an Application Test or a Logic Test.
01:09 So we'll select Logic, click Next. We want this file to be associated with our Logic Test.
01:16 So we'll deselect all together, select Logic Test, select a location for it, and click Create.
01:24 Now let's merge our header file and with the implementation file, and then
01:32 delete the header file.
01:37 Within our TestCase file, we're going to need a property for the ViewController.
01:42 So first let's import the ViewController header.
01:46 While we're thinking of it, we'll need to add the ViewController itself to
01:50 the LogicTest Target.
01:52 So click ViewController and then click LogicTests target.
01:56 Back to our ViewController tests, we'll create our property, over the
02:04 ViewController instance, and we'll synthesize that in the Implementation section.
02:11 Now let's create a test to verify that the ViewController
02:16 instantiates correctly.
02:17 We're going to do this test- driven development style.
02:20 So we'll write our test first and then implement the code.
02:26 Let's run our test and watch them fail.
02:29 So we'll select LogicTests, Product > Test, and there we see the test failure.
02:41 Now to get this test to pass, we'll need to allocate a ViewController and assign
02:44 it to our ViewController property.
02:46 So let's create a setUp and a TearDown method.
02:51 Within setUp, we'll allocate a ViewController and assign it to our
02:55 self.viewController property, and we'll create a tearDown to destroy it after each test.
03:05 Now when we'll rerun our tests and it should pass.
03:10 LogicTests, Product > Test.
03:14 We see that our tests all passed; no failures.
03:17 So now we have a TestCase file, which creates a fresh instance of a
03:21 ViewController before every test. We're ready to add test to that, which I'll
03:26 show you in the next movie.
03:27
Collapse this transcript
Testing ViewControllers
00:00 So now that we've set up our ViewController, let's write some tests on
00:03 the ViewController.
00:06 We'll display our ViewController on the right and our ViewControllerTests on the left.
00:11 Looking at the viewDidLoad method, we see that it does three things.
00:15 It creates a location object and assigns it to its location property.
00:19 Then it calls startLocationUpdates on the location object, and then it sets its
00:24 mapView's UserTrackingMode property.
00:27 We'll need to test these three things.
00:29 Right off the bat though, we run into a problem with the mapView.
00:33 Normally the mapView is created and set by the nib.
00:37 Since we're running our own standalone Logic Tests, we don't have a mapView
00:41 unless we create it, so we'll need to do that.
00:44 We have a couple of options in how we create the mapView.
00:47 We can go ahead and just create a mapView object and assign it to
00:51 ViewController's mapView property or we can create a mock and assign it to
00:56 the mapView property.
00:58 As you probably guess, we'll use the mock.
01:00 So first we'll need to include the OCMock header file.
01:06 Then we'll create a test that will create the mock, set it to the mapView
01:11 property and then verify that the SetsUserTrackingMode is called correctly.
01:18 So we'll create our mock.
01:21 We'll tell the mock what to expect.
01:23 Then we'll set the mapView property on our ViewController;
01:30 then we'll execute the code calling viewDidLoad, and finally ask the mock to
01:37 verify that the expected call was made.
01:40 Before we run the test though there is one thing we need to take care of and
01:43 something to be aware of when working with, especially with ViewControllers.
01:48 In the ViewController header, outlets are typically defined with the weak reference.
01:54 We need to change this to strong, since in our tests these will be the only
01:58 reference to this object.
02:01 Now back in our ViewControllerTests, let's run our tests. We have a build
02:09 failure and that's because we've not included the mapView framework in the Logic
02:14 Tests target, so let's do that now.
02:16 Click the MapKit.framework, show the Utilities view, and we'll click LogicTests.
02:23 Now let's run our test again.
02:29 And we have all tests passing, no failures.
02:32 So moving on with our ViewControllerTests, we've confirmed this last line happens.
02:37 Now we are going to need to verify that the location object is created and that
02:41 the location objects startLocationUpdates method is called.
02:45 This is a common sequence found in code written without tests, making it tough to test.
02:51 The location object is being created and then called.
02:55 We don't really have any place in here that we can inject our mock object.
03:00 So there are a couple ways we can test this. The first way, which is a bad way,
03:05 is to use a side effect on the call to startLocationUpdates to confirm that it was called.
03:10 For example, there may be some sort of flag that's set in this method that we could check.
03:17 We are not going to do that.
03:18 The second way is to refactor this code to enable testing.
03:23 By moving this second line into its own method and then passing the location
03:28 object into it we would have the opportunity there to pass a mock instead of the
03:33 location object during our testing, so we'll do that.
03:36 So we select the second line, right-click Refactor > Extract.
03:42 This will take that line and insert it into its own method.
03:46 We'll call this method beginLocationUpdates.
03:54 Click Preview. Looks good click Save.
03:58 Now scrolling up to the new method, we can see that the call is being made up
04:03 here, but we're not passing this object, that's an important part.
04:07 So we'll need to refactor this a little bit more, passing in the location object,
04:16 and then using the passed location instead of the property on the object.
04:23 So what I've just done here is what's referred to as the "tell don't ask" principle.
04:29 Instead of having this method ask for the location from the property on the
04:35 object, we're telling it which location to use.
04:39 This will allow us to pass in a mock object when we are testing.
04:45 And let's make sure that this signature is in our header file for
04:49 the ViewController.
04:58 Now back to our tests.
05:00 First, we need to perform a simple test that the location object is allocated and
05:04 set, that's a simple assert not nil test.
05:11 We'll call our code and then we'll assert that the location object was created.
05:16 Then we'll need to test that the viewDidLoad method calls the
05:23 beginLocationUpdates method.
05:25 As we've seen before, to test that an object calls a method on itself, we can
05:29 use a partialMock, so we'll do that.
05:33 First we'll create the ViewController partialMock.
05:36 Then we'll tell the mock object what to expect.
05:44 In this case we don't care what the argument passed is.
05:50 Then we'll call viewDidLoad on the partialMock and then asked the mock object to
05:56 verify the expected call was made.
06:00 So at this point we are testing that this object is created, we already tested
06:05 that this property is set.
06:06 We've tested that the LocationUpdates is called.
06:11 The only thing we need to do now is to make sure that beginLocationUpdates calls
06:16 location start LocationUpdates.
06:18 Normally, I would go ahead and run the tests after each test is created.
06:23 But to speed things up I am going to go ahead wait until all of these tests are created.
06:32 So now that our tests are created, let's run them and see what happens.
06:39 We see that we have 20 tests passing with no failures.
06:43 ViewControllers can be tested using normal unit testing techniques.
06:47 What makes them intimidating at times can typically be resolved through
06:51 refactoring and/or the use of fakes and mock objects.
06:55
Collapse this transcript
Testing notification generation
00:00 Notifications allow broadcasting an event to multiple listeners.
00:04 Let's look at how to test that.
00:06 In our WhatsMySpeed code at this point, we have a location object that receives
00:10 location updates and calculates speed based on those updates.
00:14 We've also added a view for displaying speed.
00:17 Now we need a way to update the display whenever the location object is updated.
00:22 The most obvious way of dealing with this, would be to create a delegate
00:25 method in the Location class for notifying our ViewController when to update the speed view.
00:31 However, Delegation provides a one- to-one communication mechanism.
00:35 If more than one object needs to be notified, when the location is updated, then
00:39 we should use a notification.
00:41 Apple makes broadcasting notifications very easy, using the
00:44 NSNotificationCenter.
00:46 Let's use it for updating our speed view whenever location changes.
00:51 To update speed using a notification, we'll need to implement three things.
00:56 We'll need to generate a notification whenever location updates.
00:59 We'll need to listen for the notification, and we'll need to update the speed
01:03 view when the notification is received.
01:06 Let's use test-driven development as we make these three changes.
01:10 So first we'll write a test, testLocationManagerDidUpdateNotification.
01:16 We can copy most of the previous testLocationManagerDidUpdateSetSpeed code, and
01:22 use that for this test.
01:25 To copy that, paste that down at the bottom of our file.
01:28 We're in location tests now.
01:30 We'll change the name of this to DidUpdateNotification.
01:38 You don't need these bottom two lines that are testing the speed.
01:42 We'll be testing for the notification being issued.
01:47 OCMock provides an observer mock, for testing notifications. We'll use that.
01:54 So first we'll create a mockObserver, then we'll register the mockObserver to
02:00 receive the location change notifications, and then we'll tell the
02:05 mockObserver what to expect.
02:11 Now we'll ask our mockObserver to verify that it received a notification, and
02:18 finally this is important.
02:20 We'll need to remove our mockObserver from the NSNotificationCenter.
02:25 This isn't documented currently on the OCMock web site.
02:28 But if we don't do this, we'll end up getting crashes in our tests, if
02:32 multiple observers are run.
02:36 Now let's run our test.
02:38 In test-driven development we're expecting this test to fail.
02:41 We've hit the Xcode bug.
02:46 Where our test expected to fail, it actually passed.
02:50 So we'll do a complete clean.
02:52 Remember, when you see unexpected results, go back and do a full clean.
02:56 I'm going to hold the Option key > Clean the Build Folder, Clean.
03:01 This will cause our next build to be a little bit slower.
03:03 It should start reproducing the results we expect.
03:06 So let's run our test again.
03:09 Now we see the failure.
03:11 So we've written the test to confirm our expectations.
03:15 Seeing that they're failing now, we'll go implement the change to our code.
03:19 In this case, we'll need to generate a notification in the location objects,
03:24 locationManagerDidupdateToLocation from location method.
03:29 Be careful to get the name correct.
03:31 So it matches with our test, and let's run our test at this point.
03:36 Now we expect it to pass, and we see that it does.
03:40 Okay, so we're generating notifications, whenever location changes.
03:44 In the next video, we'll look at how to handle that notification.
03:48
Collapse this transcript
Testing notification handling
00:00 So now that we are creating a notification we need to set up to handle the notification.
00:05 The notification handler, we will need to grab the speed from the location
00:09 object, format it into text and write it out to the speed label.
00:14 We could do this in the ViewController, but we might want to support kilometers
00:19 for an example later on.
00:20 So I am going to keep that code in the formatting of that string in the
00:23 location model object.
00:25 So we will need a new method speedText in the location model.
00:29 So in our location tests we will need to create a new test for the speedText method.
00:36 We can copy the existing speed test, since all of the setup will be the same.
00:42 Copy this and paste it down to the bottom.
00:47 Let's rename this testSpeedText.
00:48 We will change out the tests that are at the bottom and replace them with tests
00:57 for the speedText string.
01:00 So let's grab the string first. Using our unit test best practices, we will
01:07 display the string if it's incorrect.
01:11 Before this code will compile, we will need to add the location speedText
01:14 property, and we'll come over to the location header.
01:22 And in the implementation, we will synthesize it.
01:30 And in init, we will set it to the value Calculating.
01:36 Now we can run our test and see the one failure; there's our expected failure.
01:43 Now we'll go implement the code to make this test pass.
01:47 Within the location object LocationManager did update to location from location
01:52 method. We will go ahead and format that string right under where we are setting
01:56 the speed value itself.
01:57 We will use the string with format method.
02:03 Now we will rerun our tests, and we should see them all passing now.
02:08 All right, 22 tests with no failures.
02:12 Now let's create the ViewController test to verify that the speed label gets
02:16 updated by the notification handler.
02:18 We will create a new test. We will call this
02:22 testLocationChangeNotificationUpdateSpeed.
02:28 First, we will create a mock of the location object;
02:31 then we will tell it to stub and return 55 mph for the speedText method.
02:41 And we will create a notification mock, and we'll tell the notification mock to
02:49 stub the object method and return our location mock.
02:54 Kind of fun; we have a mock returning a mock.
02:57 So just make sure this is clear we are telling our notification mock to stub the
03:05 object method and return the location mock when that's called.
03:10 Now we will create a mock for the label. We will tell the label mock to expect a
03:18 call to its setText method with the string 55 mph.
03:25 So that's where the work of this unit test will actually happen.
03:28 Then we will tell the ViewController to set its speed label property to our label mock.
03:38 And now we will call the handleLocationChange method to run the test.
03:43 We haven't written the handleLocationChange method yet;
03:47 it's okay we will write that in a minute.
03:48 And then we will ask the label mock to verify that it was called correctly.
03:54 Now we need to go create the handleLocationChange method before we can compile the code.
03:59 So we will open up the ViewController header on the right; define our method.
04:07 And we will come over to the implementation and implement the handler.
04:17 And now we can run our tests. It should compile okay.
04:22 Although we are not expecting them to pass; I mean we should have one failure as
04:26 a result of the label text not actually being written in here.
04:29 You can see that that's correct.
04:32 So now we will go implement writing the speed text value to the label.
04:39 We'll get the location object passed in the notification object and we'll
04:46 get our speedText from the location object and finally we will write that
04:51 to the speed label.
04:55 So now our test should pass. If we rerun it, now we have of 23 tests with zero failures.
05:03 In the next section, we will register our notification handler with the
05:08 notification center.
05:09
Collapse this transcript
Testing notification registration
00:00 Now, all we need is for the ViewController to register for speed
00:04 change notifications.
00:06 First, we will write a test.
00:07 There are a couple of ways that we can test this.
00:09 Since NSNotificationCenter is a singleton, it's a bit tougher to simply
00:14 substitute a mock object for it.
00:16 If it wasn't a singleton, we could create a mock and have it expect the call to
00:21 register for the notification.
00:23 But, since it is a singleton, we will look for an alternative to mocking
00:26 NSNotificationCenter.
00:28 Perhaps, the next best thing in this case is to use a partial mock for our
00:31 ViewController and have it expect the call to the handler when a
00:35 notification occurs.
00:36 This is similar to what we did in testViewDidLoadCallsBeginLocationUpdates.
00:42 So let's copy that test and modify it.
00:44 We'll call this test testThatNotificationHandlerCalled.
00:56 We can use most of this code to change the expects, and then we will need to
01:03 generate a notification.
01:04 Now, let's run this test and we will see one failure.
01:12 Now let's add the code to viewDidLoad to register for the location
01:16 change notification.
01:22 Now let's run the test again, and make sure everything is passing.
01:27 And we have all test passing, no failures.
01:29 Now we need to stop listening when the view unloads.
01:33 Let's write a test.
01:34 We can simply copy the last test, and modify it slightly.
01:38 In this case, we are not expecting to receive a call to the notification
01:43 handler, but specifically want to reject receiving that call.
01:47 If this were not a partial Mock, then we wouldn't need to do anything, because
01:51 any unexpected call causes verify to fail.
01:55 However, in a partial mock like this the unexpected call to the notification
02:00 handler would be passed to the objects handler.
02:03 So we need to tell the mock to reject, and thus fail if the call is made to the
02:08 notification handler.
02:09 So we change the word expect to reject.
02:13 This is another new OCMock feature.
02:16 We are going to call viewDidLoad and then viewDidUnload.
02:20 Then, we will post our notification, and verify that in fact it didn't occur.
02:28 We will call this test testThatNotificationHandlerNotCalledAfterUnload.
02:38 Now let's run our test and see one failure.
02:41 This is test-driven development.
02:43 There is our failure.
02:45 Now, we need to add the code to make this test pass.
02:48 So over in our viewDidUnload, we will call NSNotificationCenter to remove the Observer.
02:57 Now, we run our tests and we should have all passing.
03:03 At this point, our tests are telling us that speed should display correctly.
03:08 We haven't tried it in the product code yet.
03:10 Let's give that a shot.
03:11 So we will switch back to WhatsMySpeed, and we'll click Run.
03:23 There is calculating; updated to our speed.
03:26 One of the features we can use with the Simulator for testing location is the
03:31 Debug Location menu, provides different types of simulated location data.
03:37 So let's take a freeway drive.
03:38 Now, it's moving up to the freeway.
03:41 We are now clocking 4 miles an hour, 5 miles an hour, 6, etcetera.
03:46 So we've seen how to use Observer mocks and how to expect or reject calls to
03:51 notification handlers.
03:53 This has been some fairly advanced unit testing.
03:55 Take some time to let it sink in, go back over the video again if you'd like.
04:00 The approaches that we've taken here will be helpful in other situations in
04:03 addition to notifications.
04:06
Collapse this transcript
Testing singletons
00:00 Singletons present a few unique testing challenges.
00:03 Unit tests should be independent of each other.
00:06 This means that any state created by one test should be completely cleared out
00:10 before the next test runs.
00:13 Unfortunately, singletons by their very nature exist indefinitely once they are created.
00:18 If a singleton doesn't persist state information that can affect its behavior,
00:23 then this may not matter. If it does though, then testing becomes problematic.
00:28 Let's look at a technique that can be used to deal with this.
00:32 It's called singleton as a choice.
00:35 Let's start by converting our location model object to a singleton.
00:38 Singletons are used quite a bit in the Apple frameworks;
00:42 we've already worked with some of them, for example, UI Application,
00:46 NSNotification Center, and CLLocationManager.
00:50 By definition there is only one application, one default notification center and
00:55 the device can only be in one location at a time so only one GPS location.
01:00 A singleton is created once, typically the first time it is accessed.
01:05 After that references to it are passed out using a class method Shared Instance.
01:11 So let's convert location to provide a Shared Instance method, but first a test.
01:17 In our location test file we will add a new test. We'll call
01:22 this testSharedInstance.
01:26 We'll assert that shared instance return something.
01:31 Now let's add the SharedInstance method to location. In the location header,
01:36 we'll define the class method.
01:41 And in the implementation file, we will create both a static variable and the
01:48 class method to access it.
01:50 So let's run our Logic Tests; watch the test fail.
02:00 Now let's implement the code to make the test pass.
02:03 The standard implementation of a singleton uses a static variable to hold a
02:08 single instance and then in the SharedInstance method checks to see if it's
02:14 already initialized and if not initializes it at that point.
02:21 Now let's rerun our test, and the tests are passing.
02:25 Now this test only confirmed that it's returning something.
02:30 We haven't confirmed yet whether or not it's always the same instance.
02:34 So let's try the second test to confirm the other behavior of a singleton.
02:38 We will call SharedInstance twice and then we'll compare to make sure that these
02:46 objects are exactly the same.
02:51 This is no assertion AssertEqualObjects. This is basically the same as using an
02:57 == sign between these.
02:58 We are going to make sure that the address of those are the same, not that they
03:03 have the same contents.
03:05 Run our test now; all tests passing.
03:12 At this point we've implemented a class that can be either a singleton or
03:15 not. This is called singleton as a choice if shared instance is used then it is a singleton.
03:22 If alloc init are used, then it is not a singleton.
03:26 What's the drawback of this approach?
03:28 Mistakes can happen, it's possible to forget that this object is a singleton and
03:33 use alloc init by mistake.
03:35 Singletons are quite common in iOS applications. By understanding their
03:40 implications on unit testing,
03:42 you can choose whether or not to implement the singleton as a choice pattern.
03:46
Collapse this transcript
Testing gesture recognizers
00:00 Possibly the most common methods found in ViewControllers are event handlers.
00:05 Event handlers that are written with testing in mind are quite straightforward
00:09 to test using mocks. Let's look at an example.
00:12 Our simple WhatsMySpeed application is nearly done;
00:16 there is however an issue that needs to be resolved.
00:19 Let's run the application.
00:20 When the application is running, the user can scroll the displayed map by simply
00:29 dragging or flicking it. This behavior is built into MapView.
00:37 This is nice, but unfortunately there's no built-in way to restore user tracking.
00:43 Let's fix this by providing a simple tap touch event to restore user tracking.
00:48 We're not using single tap for anything else, and the default MapView behavior
00:53 uses just about every other gesture for zooming or scrolling, except tap.
00:58 To restore user tracking after the user has panned the map display, we just
01:03 need to set user tracking mode back to user tracking mode follow, whenever a tap is detected.
01:09 The way to detect a tap is to set up a TapGestureRecognizer.
01:11 The TapGestureRecognizer will then invoke a tap gesture handler.
01:18 Let's start by writing a test for the tap gesture handler.
01:21 Close our app let me have the ViewControllerTests, and let's write a test.
01:30 First, we'll create a mock of the MapView.
01:33 Then we'll tell the mock what to expect.
01:40 Then we'll tell the ViewController to use our mock for its MapView.
01:47 We'll call the code under test.
01:50 We haven't created that handler yet, so we are getting a warning. That's okay,
01:53 we'll create it in a minute, and then let's call the mock asking it to verify
01:58 that it got to expected call.
01:59 Now in order to compile and run this code, we'll need to create the handler, but
02:05 we'll leave it empty.
02:05 So we'll open the ViewController header file, and we'll create a method signature
02:10 for the tap handler.
02:11 And we'll create an empty body within the implementation.
02:25 We can run our test, select the Logic Tests, and we'll expect one failure, and we
02:31 see the one failure as expected.
02:34 Now we'll implement the functionality in the tap handler to set TrackingMode
02:40 Follow and rerun our tests and our tests are passing.
02:47 Now we need a test to confirm that viewDidLoad creates a GestureRecognizer and
02:53 adds it to the MapView.
02:55 Once again mock's to the rescue.
02:57 In our ViewController tests, we'll create a new test;
03:00 create a mock for the MapView class.
03:07 We'll tell the mock what to expect. We'll set our mock to the MapView
03:13 property of the ViewController, and we'll call ViewController, viewDidLoad to exercise the code.
03:23 And ask the mock whether it got the expected call or not.
03:27 We can run our test now, and we'll expect one failure; there is the failure.
03:33 Now let's add the code to viewDidLoad, to create and add the
03:37 GestureRecognizer to the MapView.
03:42 We'll create a TapGestureRecognizer. We'll use the initWithTarget and point to
03:51 ourselves, and then specify our handler selector.
03:58 Now we'll add this GestureRecognizer to the MapView.
04:03 And now rerunning the tests we're expecting zero failures.
04:07 However, we now get two failures.
04:14 What's happening here is that viewDidLoad is making two calls to our mock MapView.
04:20 We already have a test to verify that viewDidLoad calls the MapView
04:24 setUserTrackingModeFollow and now we've added another call to add
04:29 GestureRecognizer, which is causing the other test to fail now.
04:34 And our new test is still failing because of the unexpected other call.
04:38 Often detecting these unexpected calls is a good thing, but in this particular
04:43 case, the other calls are valid and are actually expected.
04:47 The problem here is that we don't want to start putting expectations about one
04:52 behavior -- for example, setting up the GestureRecognizer -- in a test about setting
04:57 UserTrackingMode and vice versa.
05:00 A better solution is to use a nice mock.
05:03 A nice mock is a mock that ignores unexpected calls.
05:07 This allows us to verify that our expected call happens, but not worry about
05:11 whether other calls are made.
05:13 So the change is quite simple.
05:15 In both of the tests, we need only change one line that creates the mock to have
05:20 it create a nice mock instead.
05:22 So in testDidLoadSetsMapViewGestureRecognizer, we'll change the line mockForClass
05:30 to niceMockForClass, and we'll do the same for
05:34 testViewDidLoadSetsUserTrackingMode.
05:41 Now rerunning our tests, everything should pass, and it does.
05:48 But the proof is in the pudding, running our WhatsMySpeed target to see if it works.
06:04 And we can see that we are centered. Let's go ahead and zoom it off, and let's
06:08 tap the screen and we're back centered again.
06:12 So we've seem that using unit tests we can implement new functionality in an
06:17 app, including handling of gestures, and have a high confidence that it will work
06:22 before even running it.
06:23 Unit testing gesture handlers is simplified using mock objects, and to ignore
06:28 unexpected method calls to mocks, switch to nice mocks.
06:32
Collapse this transcript
Analyzing code coverage with CoverStory
00:00 An important aspect of unit testing is knowing how much of the code in a project is tested.
00:06 This is called Code Coverage or Test Coverage and is expressed as a percentage
00:11 of all the code in a project.
00:13 Tools are available to measure this.
00:15 Let's take a look at one of them called Cover Story.
00:18 To get started with Cover Story, download a copy from the Google Code Cover
00:22 Story site. Select Downloads and download Cover Story 4.2.0 DMG.
00:33 Open the file and then drag the icon to Desktop. Enter your doc.
00:39 Now we need to configure are app to generate the input files needed by Cover Story.
00:44 Instructions are available on the web site in the Wiki, but I will walk you
00:48 through those steps.
00:51 First, we need to add a couple of settings to the other CFLAGS Build Setting.
00:55 Select our LogicTests and our Other C Flags, click plus (+), and we will add
01:04 -fprofile-arcs and -ftest-coverage. Click Done.
01:17 In addition in the build phases, we will need to add a library, click plus (+).
01:26 Add Other, Command+Shift+G. Go to Developer > user > lib and we will select the
01:35 libprofile_rt.dylib and click Open.
01:39 We will clean up our Project Navigator.
01:47 Now we've configured our LogicTests. Let's run the test again.
01:53 And this will generate the Code Coverage files needed.
01:56 We see 29 tests with no failures.
01:59 Now we need to locate the generated files.
02:02 These are located next to the Derived Data. We can find where the Derived
02:07 Data is by opening Xcode > Preferences > Locations and the Derived Data is displayed here.
02:19 To determine what the last build was, switch to List view and sort on Date Modified.
02:28 Inside the Build > Intermediates > Project name, which is
02:32 WhatsMySpeed.build > Debug- iphonesimulator > LogicTests.build > Objects-normal > i386
02:41 are all of the GCDA and GCNO files.
02:46 These will be used for calculating code coverage.
02:50 For convenience, drag the folder to Finder Favorites to simplify opening it in Cover Story.
02:56 Now launch Cover Story and select File > Open. Use the shortcut and click Open.
03:10 The first file listed is ViewController.m. It shows 69.4% coverage and is
03:17 displayed on the right.
03:19 Scrolling down through the file, we can see lines highlighted in red. Reviewing
03:24 these, we see that these are all boilerplate that we've chosen not to test.
03:30 Moving down to the next file, Location.m, we see 86.8 listed.
03:38 Scrolling down to the file, we see init return nil is red.
03:44 This is a good catch. We haven't implemented a test to verify that init returns
03:49 nil if our parents class init super init returns nil.
03:53 We will choose not to worry about this.
03:56 Scrolling down, we see a large section; the block completion handler for
04:02 updatePostal and LocationManager did update to location from location is marked
04:08 red, indicating that it wasn't tested.
04:12 Seems incorrect; I thought we were testing that.
04:14 So let's look at the testLocationManagerDidUpdateUpdatesPostalCode test.
04:20 We are creating a mock CLLocation object and a partialMock for location, which
04:28 then verifies that the call to updatePostalCode method is called, passing the
04:33 completion block code but not actually executing it.
04:37 Cover Story has caught a good-sized chunk of code that I had mistakenly
04:41 thought was already tested.
04:43 I will leave creating a test of that untested code as an advanced exercise for you.
04:48 I'll give you a hint though, copy this method and use an OCMock feature to
04:54 execute the block code.
04:56 Cover Story is a handy utility that can give you insights into how much of your
05:00 code is being tested.
05:02 While it's reported coverage percentage numbers may not be spot on. We've scene
05:06 that it can be very helpful for quickly spotting untested code.
05:10
Collapse this transcript
Conclusion
Where to go from here
00:00 So now we've seen how to use unit tests in iOS projects, let's look at what we've seen.
00:06 How to perform unit testing using the integrated OCUnit framework, how to unit
00:11 test code using GHUnit, how to use mock objects in our unit tests using the
00:16 OCMock framework, how to add unit tests to existing code as well as how to
00:22 write tests first using test-driven development, and how to approach difficult test situations.
00:29 Remember to review Apple's documentation as new versions of Xcode are released.
00:34 Unit testing is an area that seems to get updated with each new release.
00:39 My final recommendation for you is to go practice writing some unit tests.
00:44 Don't become discouraged if it seems a bit daunting at first.
00:47 Like many things worth learning in life writing good unit tests requires practice.
00:52 Give test-driven development a try with a small project.
00:56 Add unit test to an existing project smaller, simpler model classes.
01:01 As you begin to get the feel for it move up to more complex code. With
01:06 practice, you can reach a point where you can quickly write unit tests for any
01:10 code no matter how complex.
01:13 And I suspect that as a result you will find yourself writing better code overall.
01:18 I hope that you've seen some things in this course that will encourage you to
01:21 try making unit testing a favorite tool in your programmer's toolbox.
01:26 Thank you for watching this video. Now go forth and write your own unit tests.
01:31
Collapse this transcript


Suggested courses to watch next:

iPhone SDK Essential Training (2009) (6h 52m)
Simon Allardice

Objective-C Essential Training (6h 35m)
Simon Allardice


Xcode 4 New Features (1h 58m)
Bill Weinman


Are you sure you want to delete this bookmark?

cancel

Bookmark this Tutorial

Name

Description

{0} characters left

Tags

Separate tags with a space. Use quotes around multi-word tags. Suggested Tags:
loading
cancel

bookmark this course

{0} characters left Separate tags with a space. Use quotes around multi-word tags. Suggested Tags:
loading

Error:

go to playlists »

Create new playlist

name:
description:
save cancel

You must be a lynda.com member to watch this video.

Every course in the lynda.com library contains free videos that let you assess the quality of our tutorials before you subscribe—just click on the blue links to watch them. Become a member to access all 98,695 instructional videos.

start free trial learn more

If you are already an active lynda.com member, please log in to access the lynda.com library.

Get access to all lynda.com videos

You are currently signed into your admin account, which doesn't let you view lynda.com videos. For full access to the lynda.com library, log in through iplogin.lynda.com, or sign in through your organization's portal. You may also request a user account by calling 1 1 (888) 335-9632 or emailing us at cs@lynda.com.

Get access to all lynda.com videos

You are currently signed into your admin account, which doesn't let you view lynda.com videos. For full access to the lynda.com library, log in through iplogin.lynda.com, or sign in through your organization's portal. You may also request a user account by calling 1 1 (888) 335-9632 or emailing us at cs@lynda.com.

Access to lynda.com videos

Your organization has a limited access membership to the lynda.com library that allows access to only a specific, limited selection of courses.

You don't have access to this video.

You're logged in as an account administrator, but your membership is not active.

Contact a Training Solutions Advisor at 1 (888) 335-9632.

How to access this video.

If this course is one of your five classes, then your class currently isn't in session.

If you want to watch this video and it is not part of your class, upgrade your membership for unlimited access to the full library of 1,899 courses anytime, anywhere.

learn more upgrade

You can always watch the free content included in every course.

Questions? Call Customer Service at 1 1 (888) 335-9632 or email cs@lynda.com.

You don't have access to this video.

You're logged in as an account administrator, but your membership is no longer active. You can still access reports and account information.

To reactivate your account, contact a Training Solutions Advisor at 1 1 (888) 335-9632.

Need help accessing this video?

You can't access this video from your master administrator account.

Call Customer Service at 1 1 (888) 335-9632 or email cs@lynda.com for help accessing this video.


site feedback

Thanks for signing up.

We’ll send you a confirmation email shortly.


By signing up, you’ll receive about four emails per month, including

We’ll only use your email address to send you these mailings.

Here’s our privacy policy with more details about how we handle your information.

Keep up with news, tips, and latest courses with emails from lynda.com.

By signing up, you’ll receive about four emails per month, including

We’ll only use your email address to send you these mailings.

Here’s our privacy policy with more details about how we handle your information.

   
submit Lightbox submit clicked