IntroductionWelcome| 00:04 | Hi! I am Ron Lisle and welcome to Unit
Testing iOS Applications with Xcode 4.
| | 00:10 | In this course, we will look at unit
testing and how to unit test iOS applications.
| | 00:15 | We will use the support built into
Xcode 4 called OCUnit and also a couple of
| | 00:20 | open-source unit test
frameworks called GHUnit and OCMock.
| | 00:25 | I will start off easy by explaining what
unit testing is and how it differs from
| | 00:30 | other types of testing.
| | 00:31 | I'll show you how to install and use
the OCUnit support built into Xcode 4,
| | 00:37 | and then we'll see how to add and use
the additional unit testing capabilities
| | 00:41 | provided by GHUnit.
| | 00:43 | Then we will move up to some more
advanced techniques including the use of mock
| | 00:48 | objects using OCMock.
| | 00:51 | Finally, we'll look at some of the
problem areas when unit testing iOS
| | 00:55 | applications and how these
tools can be used to solve difficult
| | 00:59 | testing situations.
| | 01:00 | So let's get started with Unit
Testing iOS Applications with Xcode 4.
| | Collapse this transcript |
| What you should know| 00:00 | This course assumes that you are already
familiar with creating iOS applications.
| | 00:05 | You should already be
familiar with using Xcode 4.
| | 00:09 | I recommend that you have a copy of
Xcode 4 and the iOS Developer toolset
| | 00:13 | installed on your system so you
can work along with the videos.
| | 00:17 | If you need some brushing up on your
iOS development knowledge, I recommend
| | 00:21 | watching one of the lynda.com iOS
SDK training videos, such as iOS SDK
| | 00:28 | Essential Training.
| | 00:29 | So let's get started.
| | Collapse this transcript |
| Using the exercise files| 00:00 | If you are a Premium member of the
lynda.com online training library or
| | 00:05 | purchased this title on a disk, then you have
access to the exercise files for this course.
| | 00:10 | Exercise files are organized
by chapter and movie number.
| | 00:14 | So for example, 0403 contains the Xcode
project files for the third movie in Chapter 4.
| | 00:21 | Note that I use a slightly
different numbering for 0209.
| | 00:25 | This movie uses two different projects in the
same movie; hence are named 0209a and 0209b.
| | 00:32 | You can open the project in Xcode
by double-clicking the project file.
| | 00:37 | If you are a Monthly or an Annual
member, then you don't have access to
| | 00:41 | the exercise files.
| | 00:42 | You can still follow along.
| | 00:44 | Throughout the course, I'll explain
how you can set up your project files the
| | 00:48 | same way so that you can follow along and
build a similar application and test on your own.
| | 00:53 | In the beginning, we will use Xcode to
create a new project and add additional
| | 00:58 | functions and testing as we go.
| | 01:00 | This project in code will then
be used by the subsequent videos.
| | 01:03 | Let's get started then, and create an
Xcode project and run some unit tests.
| | Collapse this transcript |
|
|
1. Unit Testing ConceptsWhat is unit testing?| 00:00 | What is in unit testing?
| | 00:02 | Unit testing is the low-level testing of each
class method and function in an application.
| | 00:09 | Every developer does it; if
not automatically then manually.
| | 00:13 | Perhaps, stepping through code using the
debugger to verify that it works correctly.
| | 00:19 | Unit tests verify that each method of
each class produces the expected result.
| | 00:25 | This may include return values, side
effects, such as setting the value of a
| | 00:29 | property or throwing an
exception under some circumstances.
| | 00:34 | Unit testing is a key element in modern
agile software development methodologies
| | 00:40 | and code refactoring best practices.
| | 00:43 | Unit tests validate the low-level
specific behavior of the code that we write.
| | Collapse this transcript |
| Understanding different types of testing| 00:00 | There are various types of software tests.
| | 00:03 | Whereas testing covers the low-level
implementation of classes other types of
| | 00:08 | software testing include integration tests.
| | 00:12 | These tests that the separate software
components work correctly when combined
| | 00:18 | or integrated together.
| | 00:20 | Acceptance tests -- these verify that
software as a whole performs the required
| | 00:26 | and expected functionality and a whole
slew of other specialized tests to ensure
| | 00:33 | that software meets other
requirements, such as usability, performance,
| | 00:39 | scalability, security, and so forth.
| | 00:42 | In this video course, we will be
focusing on automated unit tests.
| | 00:47 | These test the low-level behavior of our code.
| | Collapse this transcript |
| Why unit test?| 00:00 | So why should we write unit tests?
| | 00:02 | There are a lot of good reasons -- they
facilitate change, simplify integration,
| | 00:08 | and provide living documentation.
| | 00:10 | Writing unit tests improve the
overall design, which can reduce App Store
| | 00:15 | rejection and result in easier to maintain code.
| | 00:18 | And it reduces the time a developer
spends in the debugger, which improves
| | 00:23 | developer productivity.
| | 00:25 | Most reasons for not writing unit tests
don't really hold up under honest scrutiny.
| | 00:30 | Perhaps, the most common excuse for not
unit testing is that there isn't time.
| | 00:35 | The reality confirmed over and over
by developers experienced with unit
| | 00:40 | testing, is that projects with unit
tests take about the same amount of time as
| | 00:45 | a project without unit tests.
| | 00:48 | The difference is that projects
without unit tests require a lot more time
| | 00:53 | debugging and fixing bugs after
the code is supposed to be done.
| | 00:58 | It's a lot easier to estimate and
schedule the writing of unit tests as the code
| | 01:03 | is being written than it is to
anticipate how much time will be needed to debug
| | 01:08 | and fix bugs after the code is written.
| | 01:10 | The really big advantage of code with
Unit Tests occurs later on when the code
| | 01:15 | needs to be changed or extended.
| | 01:18 | Having unit Tests in place makes it
much easier and quicker and safer to
| | 01:23 | make those changes.
| | 01:25 | There are a lot of good reasons for
using automated unit testing. My favorite
| | 01:29 | reason, all that quality and
productivity stuff aside, is that it allows me to
| | 01:35 | spend more time doing what I'd
really like to do, which is writing good
| | 01:39 | robust bug free code.
| | Collapse this transcript |
| Understanding how to unit test| 00:00 | So how is unit testing performed?
| | 00:03 | As I mentioned in an earlier video,
the developer can do this manually, using
| | 00:08 | the debugger to step through the
code and observing the results.
| | 00:12 | This approach is very time consuming,
repetitive, meaning boring, and can
| | 00:17 | easily be ineffective.
| | 00:19 | To be consistent and reproducible
there needs to be a written list of
| | 00:23 | steps called a test plan.
| | 00:26 | Alternatively, testing can be performed
automatically using a testing framework.
| | 00:31 | Instead of creating a test plan that
lists all the things that should be tested
| | 00:36 | and performing each step manually, a
test script or unit tests are created to
| | 00:42 | test each of these same things automatically.
| | 00:45 | These tests can even be set up to be
run automatically each time code changes
| | 00:50 | are checked into the code repository.
| | 00:53 | This is a key piece of what is
referred to as continuous integration.
| | 00:57 | We will talk about this more in another video.
| | 01:00 | Unit testing can be done
manually or automatically.
| | 01:04 | We will focus on learning how to
automate unit testing in this course.
| | Collapse this transcript |
| Working with unit testing frameworks for iOS| 00:00 | Writing automated unit tests is
simplified through the use of unit test frameworks.
| | 00:06 | Unit testing frameworks are available
for most programming environments. Most
| | 00:11 | arrive from a framework created for
Smalltalk then becoming mainstream for Java.
| | 00:16 | These have names like JUnit for Java,
NUnit for .net, PHPUnit for PHP, and
| | 00:24 | OCUnit for Objective-C.
| | 00:26 | These all use a common structure.
| | 00:29 | A test class is created to
contain individual tests.
| | 00:33 | Optional setup and teardown
methods in each file perform common test
| | 00:38 | initialization and cleanup
before and after each test is run.
| | 00:42 | Multiple independent test methods
perform tests on each method of the class
| | 00:48 | under test to ensure correct operation.
| | 00:50 | There is typically one test
case file per class being tested.
| | 00:55 | Typically there are three or four
tests for each method being tested.
| | 01:00 | Assertions are used to verify correct
return values and other behavior from the
| | 01:05 | method being tested.
| | 01:06 | There are three frameworks that have
become the workhorses for unit testing in
| | 01:11 | iOS. OCUnit, previously known as the
SenTestingKit, is integrated into Xcode.
| | 01:20 | GHUnit is a separate framework, but has
some features that make it attractive to
| | 01:25 | use in some circumstances.
| | 01:28 | OCMock is not a test framework like the
others, but it provides the ability to
| | 01:33 | create mock objects, which
simplify writing unit tests.
| | 01:37 | Unit testing frameworks are available
that will simplify using automated unit
| | 01:42 | testing in our iOS projects.
| | 01:44 | We will take a close look at
those frameworks in other videos.
| | Collapse this transcript |
| Understanding test-driven development| 00:00 | What is test-driven development?
| | 00:03 | Test-driven development, TDD, is
a key component of today's agile
| | 00:08 | development methodologies.
| | 00:10 | In this approach unit tests are
written before product code is written.
| | 00:15 | The test help to clarify what each small
piece of product code is expected to do.
| | 00:20 | Each input and output is carefully considered.
| | 00:23 | Tests are written to verify that the
code behaves as expected given various
| | 00:28 | inputs, boundary conditions,
and even erroneous inputs.
| | 00:32 | Only after the tests have been
written is the product code written.
| | 00:36 | One of the principles of test-driven
development is that once unit tests are
| | 00:41 | written only the minimal code
necessary to pass the test is written.
| | 00:46 | This tends to keep code small and simple
as additional features or behaviors are
| | 00:51 | identified, new tests are written, and
code created to make the new tests pass.
| | 00:57 | Since the minimal amount of code is
written for each test, the test themselves
| | 01:02 | provide clear documentation of
what the code is supposed to do.
| | 01:07 | Unit test frameworks often display
the results of tests in a bar that grows
| | 01:12 | as each test is run.
| | 01:14 | As long as each test is
passing the bar remains green.
| | 01:17 | If any test fails, the bar turns red.
| | 01:20 | You may hear references to green bar or
red bar regarding test passing or failing.
| | 01:27 | Writing tests first can help clarify
as well as document exactly what the
| | 01:32 | code is expected to do.
| | Collapse this transcript |
| Understanding the use of unit tests in refactoring| 00:00 | Unit tests are an essential piece of
modern software refactoring best practices.
| | 00:06 | Refactoring means modifying or
rewriting existing software in order to make it
| | 00:11 | more readable, perform better,
or easier to maintain and modify.
| | 00:15 | During refactoring there should be
absolutely no change to what the code does,
| | 00:20 | only how it does it.
| | 00:22 | Understandably, refactoring depends on
having good unit tests to ensure that
| | 00:27 | there is no change in what the code does.
| | 00:30 | Without unit tests, refactoring
can be a dangerous operation.
| | 00:34 | Bugs can appear in the code,
perhaps caused by unknown side effects.
| | 00:38 | This becomes somewhat of a catch-22 situation.
| | 00:42 | We are reluctant to refactor
code that is complex and hard to
| | 00:45 | understand, because we might break
it and yet that is the very code that
| | 00:50 | should be refactored.
| | 00:52 | On the positive side refactoring of code
that doesn't have unit tests provides a
| | 00:57 | great opportunity to add
those tests to the code.
| | 01:01 | Since you're going to need to carefully
review the code line-by-line in order to
| | 01:05 | understand exactly what the code does,
you should then understand what the unit
| | 01:10 | tests should be testing.
| | 01:12 | It maybe just as quick to create unit
tests as it would be to keep a carefully
| | 01:16 | written set of notes.
| | 01:18 | Because refactoring is dangerous
without unit tests if the code doesn't already
| | 01:23 | have unit tests, then
that's a good time to add them.
| | Collapse this transcript |
| A summary of unit testing concepts| 00:00 | Let's summarize what
we've covered in this section.
| | 00:03 | We've covered what unit testing is, how
it improves our design and keeps us out
| | 00:08 | of the debugger, and how it differs
from other software testing such as
| | 00:13 | integration and acceptance tests.
| | 00:15 | We've seen what unit testing
frameworks are available for iOS and what
| | 00:20 | test-driven development means, and
we heard about unit tests critical
| | 00:24 | roles during refactoring.
| | 00:26 | Now let's move on to actually using
these frameworks to perform unit testing in
| | 00:31 | an Xcode iOS project.
| | Collapse this transcript |
|
|
2. Getting Started with OCUnit TestsWhat is OCUnit?| 00:00 | What is OCUnit? OCUnit is the unit testing
framework integrated into Xcode 4.
| | 00:07 | OCUnit was previously called SenTestingKit.
| | 00:11 | Apple renamed it OCUnit, when they
integrated it into Xcode, but you'll still
| | 00:16 | see references to SenTestingKit
headers and libraries within the code.
| | 00:21 | The important thing to remember
is that these are the same things.
| | 00:25 | Since OCUnit is integrated into Xcode,
including it in a project is as easy as
| | 00:31 | setting a check box, during project creation.
| | 00:34 | Likewise adding it to an existing
project is as easy as adding a new project
| | 00:39 | target, and selecting the
Cocoa Touch Unit Testing Bundle.
| | 00:43 | OCUnit documentation has
been pretty sparse in the past.
| | 00:47 | As of the time of this recording, the
best OCUnit documentation is in the Apple
| | 00:53 | iOS App Development Workflow Guide,
| | 00:56 | in the Unit Testing Application
section and in Appendix A, Unit-Test
| | 01:00 | Result Macro Reference.
| | 01:02 | I recommend that you bookmark these,
since you'll need to reference them when
| | 01:06 | you're unit testing using OCUnit.
| | 01:09 | Apple has been steadily improving
its support for OCUnit in Xcode.
| | 01:13 | Xcode 4.2 added additional support,
and I would expect to see additional
| | 01:19 | improvements in future releases of Xcode also.
| | 01:22 | OCUnit uses introspection to
locate unit tests within a project.
| | 01:27 | This eliminates the need to keep any
sort of list of tests within your code.
| | 01:32 | OCUnit will locate within the
project's unit test target, all classes
| | 01:37 | derived from SenTest case.
| | 01:39 | It will then execute all methods of those
classes that begin with the letters, test.
| | 01:44 | This makes it very easy to add unit
tests along with the production code.
| | 01:49 | A good workflow that I use is to create
a unit test case file, to coincide with
| | 01:55 | each product class file.
| | 01:56 | Then create a test method, to coincide
with each method added to the product class.
| | 02:02 | OCUnit makes it easy to add unit test
case files and unit tests for each class
| | 02:07 | and method in your product code.
| | 02:09 | Don't be confused by
references to SenTestingKit though.
| | 02:13 | OCUnit and SenTestingKit are the same thing.
| | Collapse this transcript |
| Using OCUnit with Xcode 4 vs. Xcode 3| 00:00 | OCUnit has undergone
improvements with each new release of Xcode.
| | 00:04 | Most significant, have been the
changes from Xcode 3 to Xcode 4.
| | 00:09 | Prior to Xcode 4, debugging
unit test cases was difficult.
| | 00:14 | Often developers would get
frustrated with OCUnit, and resort to using a
| | 00:18 | third-party unit test framework, such as GHUnit.
| | 00:21 | Once Xcode 4 was released, however, unit
test debugging became easy and the tight
| | 00:27 | integration of OCUnit within
Xcode made it a snap to use.
| | 00:32 | One problem that remained for OCUnit, even
in Xcode 4 was the scarcity of documentation.
| | 00:39 | Since much had changed between Xcode
3 and Xcode 4, much of the existing
| | 00:43 | documentation was incorrect.
| | 00:46 | Even today you will need to be careful
when reading about OCUnit that it isn't
| | 00:50 | referring to the older version in Xcode 3.
| | 00:54 | More recently, Apple has updated the
Unit Testing Applications section in the
| | 00:59 | iOS App Development Workflow Guide.
| | 01:02 | This page provides really important
information on using both logic and
| | 01:06 | application tests in OCUnit.
| | 01:08 | We'll talk some more
about those in a later video.
| | 01:12 | One final point about changes in
Xcode versions. Xcode 4.2 added additional
| | 01:18 | improvements and I expect that we'll see
more improvements with future versions.
| | 01:21 | Review the official Apple documentation
to become aware of improvements as new
| | 01:27 | versions of Xcode are released.
| | 01:28 | Xcode 4 brought with it some
great improvements for unit testing.
| | 01:33 | Be careful though, when reading blog
posts and other documentation about OCUnit,
| | 01:38 | to be sure that the information is current.
| | 01:40 | There is a lot of obsolete Xcode 3
based information still floating around
| | 01:45 | the 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:00 | Let's look at how to add unit
tests to an existing project.
| | 00:04 | We'll start by creating a simple
iPhone project without unit tests.
| | 00:09 | Launch Xcode, and select File > New Project.
| | 00:15 | Select Single View Application and click Next.
| | 00:19 | We'll call this WhatsMySpeed.
| | 00:20 | I'll use com.lynda for the Company Identifier.
| | 00:26 | To keep things simpler we'll create
just an iPhone app, but understand that
| | 00:30 | everything that we'll be doing
works the same on both iPhone and iPad.
| | 00:33 | Make sure that Include Unit Tests
check box is not set. Click Next.
| | 00:40 | Select a location and click Create.
| | 00:45 | With the project selected in the
Project Navigator, we can see that the project
| | 00:49 | only contains one target.
| | 00:52 | It does not include unit tests.
| | 00:53 | We'll need to add them now.
| | 00:55 | Select the project, and click Add Target.
| | 01:00 | Select Other, and then Cocoa Touch
Unit Testing Bundle. Click Next.
| | 01:07 | Let's call this LogicTests. Click Finish.
| | 01:14 | Note the LogicTests target has been created.
| | 01:17 | A new group, LogicTests, has been
created that contains the unit test code.
| | 01:24 | There is also a new schema named LogicTests.
| | 01:29 | We'll select the Simulator.
| | 01:32 | We can now run the unit tests.
| | 01:34 | Select LogicTests scheme with Sim.
| | 01:37 | Product > Test or Command+U,
and this will run the tests.
| | 01:43 | One error is reported, as expected.
| | 01:46 | Issue Navigator shows the failing test,
and clicking on that opens up the Code
| | 01:52 | Editor with the failing test code.
| | 01:55 | Here we can see that a test example was
created, with a single line of code that
| | 02:00 | forces the test to fail and display a message.
| | 02:03 | This is just a placeholder.
| | 02:05 | We'll replace it with code to
actually test something in a later video.
| | 02:09 | So we've seen how to add unit
tests to an existing project.
| | 02:14 | Later 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:00 | OCUnit supports two different types of
tests: Logic Tests and Application Tests.
| | 00:06 | Currently these tests look very
similar, but behave quite differently.
| | 00:10 | It's also not very easy to
distinguish one from the other.
| | 00:13 | I'll show you how to do that in just a moment.
| | 00:16 | Logic tests are what I would
consider to be traditional unit tests.
| | 00:20 | They load and run quickly.
| | 00:22 | You include all of the code to be tested.
| | 00:26 | Any other objects or environment needed by
the test must be created by the test itself.
| | 00:31 | Logic tests run on the Simulator only.
| | 00:34 | Support for logic test is added when
you use the Add Target, to add a unit test
| | 00:40 | as we saw previously.
| | 00:41 | Application tests on the other
hand, are run in the context of the
| | 00:46 | loaded application.
| | 00:48 | The application is built and loaded
into either the Simulator or device and
| | 00:53 | the test code then run.
| | 00:55 | The tests are said to be
injected into the application bundle.
| | 00:59 | Application test can do things that
logic test cannot, such as verifying that an
| | 01:03 | IP outlet has been
connected in the nib correctly.
| | 01:07 | This comes at a price though.
| | 01:09 | Loading the entire application bundle
before each test is run is time consuming.
| | 01:14 | One powerful capability of OCUnit in
Xcode is that you can have both logic and
| | 01:20 | application tests in a single project.
| | 01:22 | Let's look at how to do that now.
| | 01:25 | Open Xcode and select New
Project, so File > New > New Project.
| | 01:32 | We'll select the Single View
Application and click Next.
| | 01:37 | We'll call this WhatsMySpeed. Be sure
the check boxes are all set, including
| | 01:43 | the Include Unit Tests.
| | 01:45 | This will cause us to include our
application tests in this project, and click Next.
| | 01:52 | We'll select a location, and click Create.
| | 01:56 | As of Xcode 4.2, selecting the Include Unit
Tests causes application tests to be created.
| | 02:04 | This behavior may change in the future.
| | 02:07 | I expect that Apple will eventually
give you a choice as to which type of
| | 02:11 | unit tests are created.
| | 02:13 | But as of Xcode 4.2 or 4.3, this
always creates application unit tests.
| | 02:21 | Xcode has created a project with
application tests. See that here.
| | 02:27 | Let's rename our
application test to keep things clear.
| | 02:30 | We'll click twice on the group
name and change WhatsMySpeedTests to
| | 02:36 | ApplicationTests, and let's
open the implementation file.
| | 02:42 | We'll double-click on the name
of the object, right-click, select
| | 02:48 | Refactor > Rename, and we'll
rename this ApplicationTests also.
| | 02:56 | Make sure the check box is set to Rename the
related files and click Preview, and then Save.
| | 03:05 | I'm going to ignore the snapshot popup for now.
| | 03:10 | Now our ApplicationTests file
have been renamed ApplicationTests.
| | 03:15 | Refactor however doesn't change our comments.
| | 03:20 | So to clean this up we'll
want to do that ourselves.
| | 03:23 | We'll double-click and then copy the new name.
| | 03:28 | Double-click and Paste. Double-click and Paste.
| | 03:35 | Command+S to save this file, and we'll
do the same thing in the implementation.
| | 03:41 | Double-click, Paste and double-click, Paste.
| | 03:47 | Let's run the test now; verify
that things build okay and run okay.
| | 03:51 | I'll select Product > Test.
| | 03:56 | There should be one failure caused by
the default STFail, sometimes when code
| | 04:04 | behaves unexpectedly.
| | 04:06 | In this case we expected
one failure and received none.
| | 04:10 | The problem is that Xcode has cached some
files and has not rebuilt them correctly.
| | 04:17 | When this happens, choose Product, hold the
Option button, and click Clean Build Folder.
| | 04:24 | This will cause previously built
files to be deleted and force a complete
| | 04:30 | rebuild of all the code.
| | 04:32 | The test it started, or tried
to start and didn't complete.
| | 04:35 | So we'll stop them now, and
we've cleaned the project.
| | 04:40 | So now we'll try running Product > Test again,
and this time we should see one failure.
| | 04:49 | Simulator pops up, and there's our failure.
| | 04:52 | So I'm going to close the Simulator.
| | 04:55 | So now let's add LogicTests.
| | 04:59 | We'll add a new target, Add Target button.
| | 05:05 | We'll select from the other category, the
Cocoa Touch Unit Testing Bundle, and click Next.
| | 05:12 | We'll call this LogicTests. Click Finish.
| | 05:17 | Now we have a project that
supports both application and logic tests.
| | 05:22 | To run the logic tests, we change the
scheme to LogicTests > Simulator, and we can
| | 05:30 | select Product > Test to run the LogicTests now.
| | 05:34 | We'll expect the same one failure from
the default STFail statement, which we
| | 05:40 | can see here, and if we look we can see
that we have two different sets of test
| | 05:46 | files: ApplicationTests, with its
default STFail test, and LogicTests, with its
| | 05:54 | default test example STFail.
| | 05:57 | Tests can now be created and added
to either Application or Logic Tests.
| | 06:02 | Since Logic Tests run faster, I
recommend creating Logic Tests where possible,
| | 06:08 | and using Application Tests for those
things that cannot or are very difficult
| | 06:13 | to be tested as Logic Tests.
| | 06:15 | The combination of Application
and Logic Tests, provide a powerful
| | 06:18 | testing capability.
| | 06:20 | You can use both in a project,
and I recommend that you do.
| | 06:24 | Then you can choose the
type best suited for each test.
| | 06:28 |
| | Collapse this transcript |
| Writing a logic unit test| 00:00 | Previously, we saw how to enable unit
testing in both new and existing projects.
| | 00:05 | When you add unit testing support to a project,
Xcode creates a placeholder test case file.
| | 00:11 | We're going to look at modifying
that placeholder test to actually test
| | 00:14 | something in our code.
| | 00:16 | I've added a Location model class.
| | 00:19 | This class encapsulates the core location
information used to calculate the user speed.
| | 00:24 | It isn't absolutely required that
you understand how core location works.
| | 00:29 | But if you'd like to brush up on core
location before proceeding, you can stop
| | 00:33 | the video at this point and go review
the core location documentation on the
| | 00:38 | Apple developer web site.
| | 00:40 | Let's look at the Location model class
code and write some unit tests for it.
| | 00:45 | I've created a new group called Models,
and created the Location class within it.
| | 00:50 | The Location class imports the
CoreLocation framework and implements the
| | 00:57 | CLLocationManagerDelegate.
| | 01:00 | That means we need to include the
CoreLocation framework in our project, in both
| | 01:07 | the Project and the LogicTests target.
| | 01:11 | The Location.m file is
included in both targets also.
| | 01:15 | The Location class exposes two properties.
| | 01:20 | On lines 14 and 15,
locationManager contains a reference to the
| | 01:26 | CLLocationManager object.
| | 01:29 | The speed property will
persist our last speed calculation.
| | 01:33 | The Location class also exposes two
methods, startLocationUpdates will be called
| | 01:40 | to start CLLocationManager.
| | 01:43 | The reason this isn't done in
init is just to simplify testing.
| | 01:47 | We'll explore this concept more in later videos.
| | 01:50 | calculateSpeedInMPH, CoreLocation
provides speed information in MetersPerSecond.
| | 01:57 | This method will convert
that data to miles per hour.
| | 02:01 | In the implementation file, I
synthesize both properties on line 17 and 18.
| | 02:08 | In the init method, I perform the standard
call to super, returning if nil is returned.
| | 02:15 | I create an instance of the
locationManager, and assign it to the
| | 02:20 | locationManager property in line 26.
| | 02:24 | I set the Delegate to self in
line 27, and lines 28 and 29, I set the
| | 02:31 | DesiredAccuracy and DistanceFilter.
| | 02:34 | The startLocationUpdates method
simply calls the locationManager
| | 02:41 | StartUpdatingLocation method.
| | 02:42 | calculateSpeedInMPH we already
mentioned; proceeds speedInMetersPerSecond.
| | 02:50 | Come returns, speedInMilesPerHour,
and finally there is the required
| | 02:56 | LocationManager Delegate method.
| | 02:58 | This method is called with
location and speed information by the
| | 03:03 | CLLocationManager instance, because we have
said its delegate to point to this object.
| | 03:08 | The method will extract the speed
information, from the newLocation argument
| | 03:13 | passed, and save the converted
MPH value in the speed property.
| | 03:20 | Meanwhile, back in the ViewController,
I've defined a new location property at
| | 03:26 | line 14, and synthesized it
in the implementation file.
| | 03:30 | Then I create an instance of the
location object in the viewDidLoad method and
| | 03:37 | free it by setting the property to
nil, and the viewDidUnload method.
| | 03:42 | So let's write some tests.
| | 03:46 | First off, let's rename the default
LogicTests class to accurately reflect that
| | 03:51 | we will be using this test case
file to test the Location model object.
| | 03:57 | We'll use Refactor on the
object name, Refactor > Rename.
| | 04:03 | Let's call this LocationTests.
| | 04:06 | We'll leave the Rename related files
check box set, and click Preview, and Save.
| | 04:17 | As before we'll clean up our
comments. I'll double-click Copy,
| | 04:22 | double-click Paste, double-click
Paste, and in the LocationTests header
| | 04:30 | file also, double-click Paste.
| | 04:34 | Next let's create an instance of the
location object to perform our tests on.
| | 04:40 | The setup and tear-down methods are
intended to be used for this sort of test
| | 04:43 | preparation and cleanup.
| | 04:45 | In the Tests case header file, we'll
create a property, contain the location
| | 04:51 | object, and we'll synthesize
that in the implementation file.
| | 05:01 | In the setUp method, we'll allocate
an instance of the location object, and
| | 05:06 | assign it to the location property, and
in tearDown we'll free it by setting the
| | 05:15 | reference to nil, saving our files.
| | 05:20 | We'll need to import the location header file.
| | 05:23 | So now let's write some tests.
| | 05:27 | So at this point our test will create
an instance of the location class before
| | 05:34 | each test and remove it after each test.
| | 05:38 | Now let's create a test method.
| | 05:40 | Remember that all test methods
must begin with the text string test.
| | 05:44 | We'll reuse test example for this next test.
| | 05:48 | Let's start with an easy test.
| | 05:50 | Methods that perform a calculation on an
input argument are the easiest to test.
| | 05:55 | So let's create a test for
the CalculateSpeedInMPH method.
| | 06:00 | For convenience, we'll display the code
that we'll be testing on the right side
| | 06:05 | by holding Option and clicking Location
.m. Then close the Utilities View, and
| | 06:12 | we'll scroll down to where we
can see the code will be tested.
| | 06:16 | To test this method, we'll pass it a
value in meters per second and compare the
| | 06:23 | return miles per hour value.
| | 06:25 | So let's name our test beginning with test.
| | 06:28 | Then we'll use the name of
the method that we're testing.
| | 06:32 | So CalculateSpeedInMPH, and
delete the old failing statement.
| | 06:43 | Now we'll create some Constance,
just to my test a little more readable.
| | 06:48 | Now we're going to call
CalculateSpeedInMPH and save the value that's returned.
| | 06:56 | We're going to pass it 55 miles per
hour, convert it to meters per second, and
| | 07:03 | now we'll compare or assert that the
value that's returned is equal to 55, and
| | 07:13 | display an error message
if the value is incorrect.
| | 07:19 | We can use normal formatting commands
within the error message text string.
| | 07:26 | So here we'll return the returned mph
value, which will be displayed if it's not 55.
| | 07:33 | Now let's run the tests
by selecting the LogicTests
| | 07:37 | scheme > Simulator > Product > Test.
| | 07:42 | Our test run -- we see no issues.
| | 07:45 | We'll open the Console View, and here we can
see the one test was executed with no failures.
| | 07:52 | There's something I'd like to
point out about Xcode at this point.
| | 07:55 | Xcode in the Simulator appear to
have issues with caching files.
| | 08:01 | Sometimes I'll make a change to a file,
including test files, and then run the
| | 08:05 | code and test, and not see the change.
| | 08:08 | It appears that things get
cached and are not always updated.
| | 08:12 | We saw that earlier.
| | 08:14 | Be alert to this behavior, and if
things don't appear to have been updated as
| | 08:19 | expected, try cleaning before running.
| | 08:22 | This can be done from the Product
menu, hold the Option key and then
| | 08:27 | select Clean Build Folder.
| | 08:31 | Select Clean, and all of the
previously built files should be erased.
| | 08:36 | On the next run of Test or go, all of
the code should be compiled fresh again,
| | 08:43 | and here we see executed
one test with no failures.
| | 08:46 | So we've seen how to use the OCUnit
test case file structure to instantiate an
| | 08:51 | object to test before each test.
| | 08:54 | Then we call a method to test it, and
then the Tear-down is called to destroy
| | 09:00 | that object before the next test.
| | 09:02 | We passed the test code in own
quantity, and verify that it returned the
| | 09:07 | expected value using an Assert.
| | 09:09 | This is the most basic type of unit test.
| | 09:12 | We'll look at progressively more
complex test scenarios, as we go through
| | 09:16 | this course.
| | 09:17 |
| | Collapse this transcript |
| Understanding the rules for writing good unit tests| 00:00 | Now we are going to locate examples
of a few unit testing best practices.
| | 00:05 | We will create some additional unit tests and
location tests to demonstrate these concepts.
| | 00:11 | The first unit testing best practice is to
keep tests small and independent of each other.
| | 00:17 | No should depend on the
effects of any other test.
| | 00:21 | Let's open LocationTests implementation file.
| | 00:24 | Here we've already set up our tests to
be independent of each other using the
| | 00:29 | setUp and tearDown methods to create a fresh
instance of a location object for each test.
| | 00:35 | Let's create a new test to simply verify
that init method instantiates a location object.
| | 00:42 | To do this we will just check that
the object created in setup isn't nil.
| | 00:47 | We will AssertNotNil and
display a message if it is.
| | 00:58 | We'll save; run this test.
| | 01:10 | Opening up the console log, we can
see that two test ran with no failures.
| | 01:16 | Note also that because the tests are
independent of each other it doesn't matter
| | 01:20 | in which order the tests run.
| | 01:22 | That's good because OCUnit doesn't
make any guarantees about the order in
| | 01:27 | which it runs the tests.
| | 01:28 | In fact, looking at our test run here it
appears the OCUnit has run the tests in
| | 01:35 | the opposite order they appear in the file.
| | 01:37 | So testCalculateSpeedInMPH, which is
the second test, was run before testInit,
| | 01:44 | which appears above it.
| | 01:46 | Another unit testing best practice is
to test just one thing with each test.
| | 01:51 | Often this means making just a
single assertion, but sometimes multiple
| | 01:56 | assertions can be used to verify a single thing.
| | 01:59 | For example, let's create a new test
to verify that the location class init
| | 02:04 | method correctly instantiates an
instance of the CLLocationManager class in its
| | 02:10 | location manager property.
| | 02:11 | We will create one test for this, but
we will choose to assert first that the
| | 02:16 | reference is not nil.
| | 02:18 | Then that the object that the
reference points to is actually of the correct
| | 02:23 | CLLocationManager class.
| | 02:26 | Close the Console view.
| | 02:27 | The right view shows the init method
that we will be testing, and we will create
| | 02:32 | a new test, testThatInitSetsLocationManager.
| | 02:41 | Now we will assert that the
LocationManager property is not nil.
| | 02:49 | Also, we will assert that the
LocationManager's class is CLLocationManager.
| | 02:58 | Now we will run our test, Command+U,
open the Console log, see that we now have
| | 03:06 | three tests with no failures.
| | 03:09 | Another best practice is to create a
separate test case file to test each
| | 03:14 | class in the application.
| | 03:16 | If you are using test-driven
development, then you will create each test case
| | 03:21 | file prior to creating each new class file.
| | 03:24 | In this example we created a
LocationTests test case file to contain our
| | 03:31 | LocationClass unit tests.
| | 03:34 | A best practice is to create one
or more tests for each method in the
| | 03:38 | class being tested.
| | 03:40 | Test each of the possible behaviors
of the method with separate tests.
| | 03:45 | There we created two tests for the init method.
| | 03:48 | For example, test what the method is
expected to do if an argument passed to
| | 03:54 | it is nil or invalid.
| | 03:56 | If an exception is expected to be thrown,
write a test to verify that the correct
| | 04:00 | exception is thrown.
| | 04:02 | And finally, don't test Apple's code.
| | 04:06 | By this I mean don't test the
boilerplate code that Xcode generates
| | 04:09 | automatically for you or
calls made to Apple's frameworks.
| | 04:13 | Let Apple test their own code.
| | 04:16 | Just worry about testing the
code that you write or modify.
| | 04:20 | Writing good unit tests isn't difficult.
| | 04:23 | Remain focused on small sections
of code and write tests to cover all
| | 04:27 | possible expected behaviors.
| | Collapse this transcript |
| Writing an application unit test| 00:00 | Now I am going to show you how to
create OCUnit application tests that will
| | 00:04 | verify that the connections between
outlets in our ViewController and the
| | 00:08 | nib file have been made.
| | 00:09 | The connections are done in Interface
Builder and so aren't very easy to test
| | 00:14 | using logic tests, but they are quite
simple to verify using application tests.
| | 00:20 | In application tests, the application
bundle is loaded before the tests are run.
| | 00:24 | So we don't need to create instances
of objects that exist in the application
| | 00:28 | like we do in logic tests.
| | 00:31 | Instead, we can get references to
the loaded applications instances.
| | 00:35 | In this example, we will get a
reference to the ViewController and then test
| | 00:39 | that the outlets have been set.
| | 00:40 | We will be working with
just the applications tests.
| | 00:44 | So be careful that the LogicTests
scheme isn't selected when running tests.
| | 00:48 | Let's start by getting a
reference to the ViewController.
| | 00:52 | We will need to import the header files
for the ViewController and the AppDelegate.
| | 01:01 | We will create a weak reference
to the ViewController. Save it.
| | 01:11 | We will synthesize that property.
| | 01:18 | We'll reuse the test example,
testThatViewControllerIsntNil.
| | 01:20 | Delete the STFail code, and we will
replace that with a STAssertNotNil.
| | 01:36 | Saving and run this test.
| | 01:38 | In this case, I've forgotten
and left a device selected.
| | 01:41 | I am going to switch to the Simulator
since I don't have the device plugged in,
| | 01:46 | and we will test, test run,
and the test failed as expected.
| | 01:54 | This approach is test-driven development.
| | 01:56 | We've created the test and watched it fail
before writing code to make the test pass.
| | 02:02 | Now let's write the code.
| | 02:04 | In setUp we'll set our ViewController's
reference from the AppDelegate, which we
| | 02:10 | can get from the UIApplication.
| | 02:11 | Then we are going to reference to the Delegate.
| | 02:19 | Now we will give a reference to the
Window, and finally, set our ViewController
| | 02:25 | property from the Windows rootViewController.
| | 02:33 | In tearDown, we will go ahead and
remove nil out that reference.
| | 02:41 | Now saving and running our test.
| | 02:42 | We should see them passing now.
| | 02:44 | We will close the Simulator, expand our
view, and we can see that All tests is
| | 02:58 | indicated so we know that we are
running ApplicationTests, and we see that the
| | 03:03 | test started and ran.
| | 03:05 | I am not seeing the number of tests run.
| | 03:08 | So let's run the tests again.
| | 03:10 | You can see that the
Simulator comes up, the test run.
| | 03:16 | Close the Simulator, and we get the
Executed 1 test with 0 failures.
| | 03:21 | So our tests have passed.
| | 03:23 | Let's extend our test
application now to display a MapKit view.
| | 03:26 | We will add a test to verify
that its IBOutlet has been set.
| | 03:31 | We could try to use TDD and write a test
first, but without an IBOutlet property
| | 03:36 | defined yet the test won't compile.
| | 03:40 | Xcode provides a convenient drag-and-
drop method for creating outlets.
| | 03:44 | So we are going to use that
first, then create the tests.
| | 03:48 | Let's close the Console view, and we
will select the Storyboard in our left
| | 03:55 | pane. Select Automatic.
| | 03:59 | We will open the Utilities view,
scroll, and go over here, and we will drag a
| | 04:11 | Map view into our view.
| | 04:11 | Now we will open both the storyboard,
we will close the Utility view, and
| | 04:20 | open the header file, and we will Ctrl
+Drag from the view into the header
| | 04:26 | file to create the outlet.
| | 04:29 | We will call this outlet mapView.
| | 04:31 | Adjust the spacing here a little bit.
| | 04:38 | Now we need to add MapKit to the project.
| | 04:40 | That's why we are seeing this error here.
| | 04:44 | So come up to the Project > Build Phases >
Link Binary With Libraries and select MapKit.
| | 04:56 | We will drag it down into our
Frameworks to clean things up.
| | 05:06 | Now in our ViewController header file
we will need to import the MapKit also.
| | 05:12 | We save our ViewController, come down to
our ApplicationTests, and we will add a
| | 05:24 | test to verify that the
mapView outlet gets connected.
| | 05:28 | So here we can copy this test.
| | 05:32 | So it already does most of what we want.
| | 05:37 | Change the name of the test
to testThatMapViewIsntNil.
| | 05:39 | Then we will change our object from
ViewController to self.mapView, and then
| | 05:51 | change your message.
| | 05:54 | And here we will check that the
ViewController's MapView property is set.
| | 06:03 | Now if we run our test, Test
Build and Run and All Tests Succeeded.
| | 06:11 | Close Simulator, double check the log
to be sure, and we see that in this case
| | 06:17 | it's still not saying they passed.
| | 06:20 | So let's run test again. Here we go.
| | 06:25 | Now we see that two tests were run,
testThatViewControllerIsntNil and
| | 06:28 | testThatMapViewIsntNil, both completed.
| | 06:36 | We will want to track the user's location.
| | 06:39 | So let's add an application test to
verify that show user location is set.
| | 06:44 | We will use test-driven
development and write the test first.
| | 06:49 | Close the Console log.
| | 06:50 | Let's create a test for vc
mapView ShowsUserLocation is yes.
| | 06:58 | Then we will Assert that our
ViewController's mapView showsUserLocation property
| | 07:10 | is true and display an
error message if it doesn't.
| | 07:14 | Save and run our test.
| | 07:25 | And here we have the expected failure,
because we are not setting this property yet.
| | 07:29 | Close the Simulator.
| | 07:30 | Now in this case, we can set this
property using Interface Builder.
| | 07:37 | So let's display the MainStoryboard,
select the Map View, and we will show the
| | 07:44 | Properties, and here this
check box at the top Shows User Location.
| | 07:51 | If we set that, that will set that property.
| | 07:54 | Now if we save and run our tests again,
tests are running, and all tests pass.
| | 08:06 | So let's close our Simulator;
double-check our log to be sure.
| | 08:13 | See that's all passing.
| | 08:15 | Let's run that again, Test, and here
we can see that all three tests passed.
| | 08:24 | Now according to our tests, the
user location should be shown.
| | 08:29 | Let's run it and see if the
code is actually doing that.
| | 08:34 | Getting a prompt, allow it to
access our location. We will say OK.
| | 08:39 | Now the Map view is displayed and our
position is over Cupertino, which is the default.
| | 08:48 | Since the Simulator doesn't have an
actual GPS it's simulating that location.
| | 08:54 | By default it's Apple's headquarters.
| | 08:57 | We still need one more change
to make this view look good.
| | 08:59 | Well, we want the map centered on the display.
| | 09:04 | So we will need to set the
userTrackingMode to MKUserTrackingModeFollow.
| | 09:09 | So let's do that now.
| | 09:12 | We will quit out of the Simulator.
| | 09:16 | In running we saw two MapViews;
| | 09:18 | somehow I dragged out a second
MapView here, so we will delete this one.
| | 09:22 | So I am clicking Delete, make
sure that this one is centered.
| | 09:27 | Okay, now we have just one.
| | 09:33 | So now we are going to create a test
that the TrackingModeFollow is set.
| | 09:39 | Going back to ApplicationTests.
| | 09:44 | Once again, we will copy this previous test.
| | 09:48 | This is a pattern that you will see
quite often when you are unit testing.
| | 09:54 | That's a bit daunting at first to
think about having several tests for every
| | 09:58 | method that you test until you realize
the most of the time you just write a
| | 10:02 | single test, and then copy that
with minor changes multiple times.
| | 10:06 | So we are going to change the
testThatShowsUserLocation, and we are going to
| | 10:12 | change that to
testThatUserTrackingFollow, and we are going to change this
| | 10:20 | property from showsUserLocation to
userTracking = MKUserTrackingModeFollow
| | 10:32 | selected of our list here, and
then change our error message to
| | 10:37 | UserTrackingMode is not follow. Save that.
| | 10:42 | We can run our tests, test-driven
development, and expect this test to fail
| | 10:47 | since we've got implemented this code yet.
| | 10:50 | Tests are running and indeed
we get the expected failure.
| | 10:54 | Close the Simulator.
| | 10:55 | There is our failing test.
| | 10:58 | Now we'll go implement that code.
| | 11:02 | Now we if we go look at Interface
Builder, select the Map, we see that we don't
| | 11:10 | have a property exposed here for
setting UserTrackingModeFollow.
| | 11:15 | So we will need to do that with code.
| | 11:16 | So 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:28 | Then we will set this to
MKUserTrackingModeFollow.
| | 11:33 | Save, rerun our tests, layer console.
| | 11:41 | Now we have four tests and all tests passing.
| | 11:45 | So as we have just seen
application tests can be quite useful for testing things
| | 11:50 | like IBOutlets, checking those
connections, while not the best choice for
| | 11:56 | general unit testing
application tests can be quite powerful.
| | Collapse this transcript |
| Exploring Xcode dependencies and schemes| 00:00 | Xcode has a couple features that can be
helpful to understand and use when unit
| | 00:05 | testing, target dependencies and schemes.
| | 00:09 | Target dependencies allow you to
specify that whenever one target is built or
| | 00:14 | run, other targets need to be built first.
| | 00:18 | This can be useful when
unit testing in a couple ways.
| | 00:21 | First, when a separate target is used
for application unit tests, the product
| | 00:27 | code being tested needs to be
built prior to building and running the
| | 00:30 | application unit tests.
| | 00:32 | To ensure that this happens, set the
product target as a dependency of the
| | 00:37 | application unit test target.
| | 00:39 | Conveniently, the dependency build will
only occur if needed; if the dependency
| | 00:45 | has changed and not already been built.
| | 00:48 | Here we see that Xcode created this
dependency when it created the unit tests.
| | 00:54 | This was done as a result of setting
the Include Unit Test check box when we
| | 00:59 | created this project.
| | 01:00 | Another use for Target Dependencies in
unit testing is to cause logic unit tests
| | 01:06 | to be run whenever the
product target is built or run.
| | 01:10 | To do this, add the logic unit test
target as a dependency of the product target.
| | 01:20 | This is helpful for use in automated
continuous integration build systems.
| | 01:25 | CIBuild typically hook into the source
code control system to automatically kick
| | 01:30 | off a build whenever new or
modified code is checked in.
| | 01:35 | When this happens unit tests should be
run as a part of that build process.
| | 01:40 | In this way, the build breaks as a
result of any unit test failures, in addition
| | 01:45 | to any compiler or linker errors
encountered with the updated code.
| | 01:49 | Xcode schemes can be used to
configure which tests are run for each scheme.
| | 01:55 | To edit any of the schemes in a project,
select Schemes > Edit Schemes. Select the
| | 02:01 | scheme to edit. I am selecting
LogicTests. Select the Test tab and the tests
| | 02:10 | that are run are displayed in the right.
| | 02:12 | So here we can see a set of tests.
| | 02:15 | The check box is on the right under the
Test column to allow you to selectively
| | 02:19 | enable or disable any or all of the tests.
| | 02:22 | This can be handy when working on
tests or using tests to debug code.
| | 02:27 | You can disable all of the tests
except the ones you're working with.
| | 02:33 | One other important reason in unit
testing for editing schemes is to select the
| | 02:38 | test target the runs when the scheme's
Test Action is selected. For example,
| | 02:44 | if you have an existing project that
was selected without unit tests then the
| | 02:49 | product scheme's Test Action will be grayed out.
| | 02:53 | This is the case in this example.
| | 02:55 | This is because no test target has
been assigned to the scheme's Test Action.
| | 03:00 | Let's edit that scheme.
| | 03:04 | When you create a new unit test
target. Xcode creates a new scheme for it.
| | 03:10 | It doesn't automatically add the new test
target to any existing targets' test action.
| | 03:17 | This forces you to switch
schemes between running and testing.
| | 03:25 | This is inconvenient because the
product target can only be built and run while
| | 03:31 | the test target cannot be run.
| | 03:36 | This is easily fixed by adding the
test target to the project's test action.
| | 03:40 | To do that select Edit Scheme, select
the product target, select Test, click
| | 03:47 | plus (+), and add the
LogicTests to the test action.
| | 03:54 | Now we can run tests from the product scheme.
| | 03:59 | Since there isn't any need to select
the test target scheme anymore, we can use
| | 04:05 | Manage Schemes to hide the
test target scheme altogether.
| | 04:14 | Now there is only one scheme,
and it supports both run and test.
| | 04:20 | Xcode provides some convenient
features for running unit tests using target
| | 04:24 | dependencies and schemes.
| | 04:27 | Understanding and using these can
make unit testing quicker and easier.
| | Collapse this transcript |
|
|
3. Getting Started with GHUnit TestsWhat is GHUnit?| 00:00 | Now let's take a look at another unit
testing framework for iOS and how it
| | 00:04 | differs from OCUnit.
| | 00:06 | GHUnit is a third-party unit testing
framework for iOS written by Gabriel
| | 00:12 | Hanford, hence the GH.
| | 00:14 | It's similar to OCUnit,
with the few key differences.
| | 00:17 | GHUnit isn't integrated into
Xcode but it isn't tough to add.
| | 00:22 | Tests are run as a separate executable target.
| | 00:26 | Tests can be run and debugged on
both the Simulator and a device.
| | 00:31 | GHUnit supports its own unique test
format. in addition to being able to
| | 00:36 | run OCUnit Logic Tests.
| | 00:39 | GHUnit supports asynchronous tests; for
example, you can create tests that send a
| | 00:44 | request to a Web service, and then verify
the results returned by the Web service
| | 00:49 | when the response is received.
| | 00:51 | The combined use of OCUnit and GHUnit
makes for a powerful unit testing workflow.
| | 00:58 | At the start of a project, include both
OCUnit and GHUnit as described in other videos.
| | 01:04 | Create OCUnit Logic Tests
when and where possible.
| | 01:08 | Create GHUnit asynchronous tests and
those specific test situations that require
| | 01:14 | asynchronous testing.
| | 01:15 | And debug tests in OCUnit on the
Simulator, and if you have to use a hardware
| | 01:21 | device debug on GHUnit.
| | 01:24 | GHUnit isn't as convenient to use as
OCUnit, but for asynchronous tasks such as
| | 01:29 | API testing, it's a great choice.
| | 01:31 | Used in combination with OCUnit,
it makes for a powerful workflow.
| | Collapse this transcript |
| Adding GHUnit to a project| 00:00 | Let's look at how you add
GHUnit to an Xcode project.
| | 00:04 | The project we've been using contains
both OCUnit application and LogicTests,
| | 00:10 | and a few tests of both type.
| | 00:12 | We'll see how GHUnit works hand-in-
hand with these to provide additional
| | 00:16 | unit test capability.
| | 00:18 | First, we'll need to get the
documentation and add a copy of GHUnit.
| | 00:24 | The code is currently hosted on github.
| | 00:27 | Open a browser, search to gh-unit, and
select the github repository or use the URL here.
| | 00:34 | Scroll down a ways to the Docset
section and follow these instructions.
| | 00:41 | We'll copy this atom feed address,
switch to Xcode, and in Xcode Preferences,
| | 00:52 | select the Downloads tab, Documentation > Add (+),
and Paste the atom feed address we copied.
| | 01:02 | If the Install button appears, click it. You
may need to enter your password at that point.
| | 01:11 | At this point the documentation for
GHUnit has been added to Xcode, and since
| | 01:16 | it's an atom feed, it will
be updated automatically.
| | 01:19 | Now download the GHUnit framework.
| | 01:22 | Select Downloads. GHUnit is
available for both Mac and iOS.
| | 01:31 | Be sure to select the
latest iOS version to download.
| | 01:36 | Once it is downloaded, open the
file to unzip it. We'll be using this
| | 01:43 | framework in just a moment.
| | 01:45 | Now let's add the GHUnit
framework to our project.
| | 01:49 | Back in Xcode, select Add Target.
| | 01:56 | Select iOS Empty Application. Click
Next. Name the target GHUnitTests.
| | 02:05 | Turn off the Include Unit
Tests check box. Click Finish.
| | 02:11 | Add the GHUnit framework to this
target by right clicking on Frameworks and
| | 02:16 | selecting Add Files.
| | 02:21 | Select the GHUnit framework;
| | 02:24 | ensure that only the
GHUnitTests check box is set.
| | 02:29 | Set the Copy items check box and then click Add.
| | 02:35 | We can see that the framework has
been added to our Frameworks group.
| | 02:39 | Now edit the Build Settings
for the GHUnit test target.
| | 02:45 | Let's close the Console view.
| | 02:47 | In the Other Linker Flags field, double-
click to add, and we'll add the ObjC flag
| | 03:01 | and the all_load flags.
| | 03:05 | Over in the Project Navigator, open up
the GHUnitTests group. Delete all of the
| | 03:11 | files except the Supporting Files
group. Select. Right-click. Delete.
| | 03:19 | Open Supporting Files. Open
the GHUnitTests-Info.plist.
| | 03:26 | Verify that main nib file
base name is not present;
| | 03:30 | if it is, delete it.
| | 03:32 | Edit main.m and in main.m replace
NSStringFromClass ([AppDelegate class]) with
| | 03:41 | the string GHUnitIPhoneAppDelegate.
Save the file, Command+S. Now run the GHUnit
| | 03:55 | scheme, not test but run.
| | 04:02 | The Simulator is loading and GHUnit is running.
| | 04:06 | GHUnit displays a list of tests since
we've not added any tests to the GHUnit
| | 04:12 | target, the list is empty at this point.
| | 04:15 | So let's add the existing OCUnit LogicTests.
| | 04:19 | Close Simulator. Select LogicTests.m
from the LogicTests group,
| | 04:27 | and we'll add it to the GHUnitTests target.
| | 04:32 | We'll need to add to SenTestingKit
framework to the GHUnit target also, so
| | 04:38 | select the framework and click the GHUnitTests.
| | 04:42 | We'll also need to add CoreLocation
since that's used in some of our tests;
| | 04:48 | select that and click GHUnitTests also.
| | 04:51 | Location.m will need to be added to
GHUnit also, since it's being tested, and we
| | 04:58 | should be able to run our tests at this point.
| | 05:02 | GHUnitTests scheme is selected. Click Run.
| | 05:08 | Now we see three tests that we just
added. These are the previously the OCUnit
| | 05:13 | Logic Tests. GHUnit is picking them up.
| | 05:17 | Now we can tap the Run
button to run these three tests.
| | 05:21 | We see that all three tests have run.
| | 05:25 | GHUnit will display any failing tests in red.
| | 05:29 | Since these are all black,
they've completed successfully.
| | 05:32 | We can see the status at the bottom --
tests completed, three out of three tests
| | 05:37 | passed, no failures.
| | 05:39 | If an error does fail and is displayed
in red, you can click on it and it will
| | 05:44 | display diagnostic information and any
messages generated by that test to help
| | 05:51 | explain why the test failed.
| | 05:54 | Note that you can display just the
failing tests by clicking the Failed
| | 05:58 | button at the bottom.
| | 06:00 | Tapping Run while Failed is
displayed will run just the failing test.
| | 06:05 | This is great when you're debugging a
particular test or a particular set of
| | 06:10 | tests that are failing; you
don't have to run all of them again.
| | 06:13 | As we've just seen GHUnit can run our
OCUnit LogicTests, but it cannot run
| | 06:19 | application tests if they depend on the
application bundle having been loaded,
| | 06:24 | which is after all why we
would use application tests.
| | 06:27 | My recommendation for which test
framework to use in a new project is to create
| | 06:32 | targets for all three.
| | 06:34 | Favor adding OCUnit LogicTests
and use the other two as needed.
| | 06:39 | GHUnit is fairly easy to add to a
project and it can be added to a project that
| | 06:44 | also uses OCUnit application and LogicTests.
| | 06:47 | Be sure to check the GHUnit
web site though for latest instructions.
| | Collapse this transcript |
| Writing GHUnit unit tests| 00:00 | So now let's take a look
at writing a GHUnit test.
| | 00:03 | GHUnit has its own unique format, which
provides some features not available in OCUnit.
| | 00:09 | Using the GHUnit format you
can do some additional things.
| | 00:13 | So now let's look at writing some GHUnit tests.
| | 00:16 | In addition to running OCUnit test,
GHUnit provides its own test format.
| | 00:22 | This format provides some additional
features not available in the OCUnit format.
| | 00:26 | So it's worthwhile to
become familiar with these.
| | 00:28 | We will be looking later on at
using those advanced GHUnit features for
| | 00:33 | writing asynchronous tests.
| | 00:35 | But let's just start with the
basic GHUnit test at this point.
| | 00:39 | To create a GHUnit test, I will need to
create a new file. We will put it into
| | 00:43 | the GHUnit test group that we created.
| | 00:47 | So right-click, New file.
| | 00:49 | We will select from the Cocoa
Touch section the Objective-C class
| | 00:55 | template. Click Next.
| | 00:56 | We'll call this LocationGHTests and click Next.
| | 01:02 | We will make sure that the test is
associated with the GHUnitTests target; it's
| | 01:08 | going into the GHUnitTests folder. Click Create.
| | 01:13 | And here we see that it's created
the normal two files, a header and an
| | 01:17 | implementation file.
| | 01:19 | It's common with unit tests to do away
with the header files, so we will delete
| | 01:24 | that and we will put everything
into the implementation file itself.
| | 01:29 | To create the test file, we will use
the template provided by GHUnit on the
| | 01:34 | web site, so we will delete this.
| | 01:37 | Now we will switch over to the GHUnit
web site, and in their documentation we will
| | 01:42 | copy the first test template they provide.
| | 01:47 | Select and copy, switch back to Xcode,
and we will paste it into our test.
| | 01:53 | The test template
imports the GHUnit header file.
| | 01:57 | We will rename the test to
LocationGHTests for both the interface definition and
| | 02:06 | the implementation, and now we
will try building and running this.
| | 02:12 | Remember that a GHUnit test doesn't
use the test action; it is its own
| | 02:17 | test runner application.
| | 02:18 | So we will click Run and
we see that the build fails.
| | 02:22 | What's happened is that this test
template was created before ARC and as such
| | 02:28 | doesn't quite support it.
| | 02:29 | They do provide assertions that do
work though. We'll change from a NULL
| | 02:35 | testing for the NotNil.
| | 02:37 | Now we will rebuild and rerun.
| | 02:39 | We see that the build succeeds okay,
and we will run those tests, and we see
| | 02:45 | that they all pass.
| | 02:47 | If we click on a test's name, it will
show us the console log for that test.
| | 02:52 | In addition we see this extra Log
statement, "I can log to the GHUnit test console:
| | 02:58 | a string."
| | 02:59 | If we look over at our test, we see
that that test message is coming from this
| | 03:04 | GHTestLog statement.
| | 03:06 | This is a very cool feature of GHUnit.
| | 03:08 | We are able within our tests to output
formatted string information, and it'll be
| | 03:15 | available on the device or the
Simulator when you run the tests.
| | 03:18 | So let's cancel out of the
Simulator, and let's add an additional text.
| | 03:24 | One of the features I really like about
GHUnit is its assertion for equal strings.
| | 03:30 | So let's create a simple
test to demonstrate that.
| | 03:33 | So we will test EqualStrings. I
am going to create a GHAssertion,
| | 03:38 | GHAssertEqualStrings. We will select
that off the list, and I am going to create
| | 03:45 | a couple strings that are
the same with different case.
| | 03:50 | Now for the description if this were
OCUnit, I would need to provide a formatted
| | 03:55 | string that would display
the strings that were compared.
| | 03:59 | OCUnit would normally only
provide a message like not true.
| | 04:03 | GHUnit itself will go a step further
though and without providing a formatted
| | 04:08 | string will provide that
information itself. So let's see that.
| | 04:12 | This test should fail, we are
not providing a message for it.
| | 04:15 | So we will save and run this.
| | 04:20 | We see our new test here, let's go
ahead and run the test. The test fails.
| | 04:25 | We look at the test log, because this
test failed GHUnit is providing a full
| | 04:32 | stack trace, I don't find that really
helpful myself, but in some circumstances
| | 04:38 | I could see how that would be.
| | 04:39 | But here it's given us a reason, and not
only has it just said that the strings
| | 04:44 | weren't equal, but it's
showing us what the strings are.
| | 04:47 | This is a very nice feature.
| | 04:48 | So as we have just seen, GHUnit can
run both OCUnit format tests and its own
| | 04:55 | GHUnit format tests, which
provide additional features.
| | 05:00 | So it's worthwhile to get
familiar with the GHUnit format.
| | 05:04 | Review the GHUnit documentation on the
web site for more information about the
| | 05:08 | features available with GHUnit.
| | Collapse this transcript |
| Looking at asynchronous tests| 00:00 | One of the advantages of GHUnit
over OCUnit is its built-in support
| | 00:04 | for asynchronous tests.
| | 00:07 | To demonstrate the type of code where
asynchronous testing can be useful, I've
| | 00:11 | extended the Location model class.
| | 00:14 | I've added a few lines of code that
will use the iOS 5 CLGeocoder class to
| | 00:20 | perform reverse geocoding.
| | 00:21 | This means that will pass it a
longitude and latitude, and it will return
| | 00:26 | address information for that location.
| | 00:28 | In this case, all we're really
interested in is the postal code.
| | 00:33 | Later on, we could use that postal
code to get weather information for
| | 00:36 | our current location.
| | 00:38 | But, for now, we'll get the postal code
and save it to a new postalCode property
| | 00:43 | in the Location model object.
| | 00:46 | If you're not familiar with the
CoreLocation.framework or CLGeocoder
| | 00:50 | specifically, refer to the
Apple Developer Documentation.
| | 00:55 | Looking at Location.h, we can see that two
new properties have been created in an ivar.
| | 01:01 | CLGeocoder *geocoder is our reference to
the geocoder, postalCode is an NSString
| | 01:08 | that will contain the returned postal
code, and geocodePending is an ivar used
| | 01:14 | to serialize access to the geocoder.
| | 01:17 | In the implementation file, you will see
that I have synthesized the two properties.
| | 01:22 | Down in init, I initialized
the two properties in the ivar;
| | 01:27 | one of them being an instance of the CLGeocoder.
| | 01:31 | I've created a new method,
updatePostalCode, and down in the LocationManager,
| | 01:36 | didUpdateToLocation from
Location method and make a call to the
| | 01:42 | updatePostalCode, passing in an event handler.
| | 01:46 | Let's look at how to test
the updatePostalCode method.
| | 01:49 | When this method is called, it
in turn calls the geocoder, its
| | 01:54 | reverseGeocodeLocation method,
passing it a completionHandler.
| | 01:59 | This method is described
in the Apple documentation.
| | 02:03 | The documentation explains that this
call is asynchronous, invoking the past
| | 02:08 | block when the location data is available.
| | 02:12 | It also states not to call it
again after initiating a request.
| | 02:16 | So I've created the geocodePending
ivar to serialize access to the geocoder.
| | 02:22 | So to test this method, we'll need to
verify that first the geocodePending is
| | 02:28 | initially NO; that was set up in init up here.
| | 02:32 | Secondly, that after calling
updatePostalCode, geocodePending becomes YES,
| | 02:39 | and thirdly, that the call to geocoder
results in the completionHandler being called.
| | 02:46 | The first two items can be done
using standard OCUnit synchronous tests.
| | 02:51 | The third item, however,
requires that we wait for the call to
| | 02:55 | reverseGeocodeLocation to complete and
ensuring that the completionHandler is called.
| | 03:02 | Right off the bat, we see a problem
with the new changes to the Location class.
| | 03:07 | We'll need to access the
geocodePending ivar from within our tests.
| | 03:12 | Standard object-oriented best practices
recommend that our implementation detail
| | 03:17 | should remain hidden.
| | 03:18 | This is referred to as encapsulation.
| | 03:21 | This is good, but it is a hindrance to our
testing efforts, and one will often run into.
| | 03:27 | Objective-C provides a
fairly simple solution to this;
| | 03:31 | the class extension.
| | 03:32 | A class extension is a category on a
class defined without a name; perhaps the
| | 03:38 | best way to understand this is to see it used.
| | 03:40 | So to enable our test to access
geocodePending, let's first convert it from
| | 03:47 | an ivar to a property.
| | 03:50 | In Location.h, we'll remove the ivar,
and we'll create a property instead.
| | 04:00 | Then in the implementation file,
we'll synthesize that property.
| | 04:09 | Now we can access geocodePending, but
unfortunately, so can everyone using this class.
| | 04:15 | This breaks our encapsulation.
| | 04:17 | So we need to do something else.
| | 04:20 | A good approach to use when unit
testing is to move the things that you want
| | 04:24 | to remain hidden into the class extension,
and put the class extension in a separate file.
| | 04:29 | We will give the special file
a name to indicate its purpose.
| | 04:35 | In this case, let's call it
LocationForTesting.h. You could use other names like
| | 04:40 | LocationInternal.h or LocationPrivate.h.
I prefer LocationForTesting because it
| | 04:47 | reminds me of why I created the file.
| | 04:50 | Note also that we can and should put
methods that we want to keep private in
| | 04:55 | addition to properties into
the class extension also.
| | 05:00 | So first, let's run our test and make
sure everything builds before making changes.
| | 05:06 | GHUnit, we'll run that.
| | 05:07 | It builds, loads, and runs okay,
goes in the Simulator. We'll select our
| | 05:17 | LogicTests and run those.
| | 05:20 | Those all pass, and
finally we'll build our code.
| | 05:26 | Make sure we haven't broken the
compile, and that all runs okay.
| | 05:32 | So it's looking all right.
| | 05:34 | Let's split the Location Header
file into separate Location.h and
| | 05:38 | LocationForTesting.h. Let's right-click
on the file, show in Finder, and we'll
| | 05:45 | use the right-click, Duplicate function.
| | 05:49 | Let's rename LocationCopy to
LocationForTesting, and back into Xcode, and let's
| | 05:59 | add that file to the project.
| | 06:01 | LocationForTesting, we don't
need to copy it; it's already there.
| | 06:08 | We're going to add it to all of the
targets since these are either tests or the
| | 06:13 | product code itself. Click Add.
| | 06:17 | Now, we can see that the new file is up here.
| | 06:20 | Let's open the new file,
LocationForTesting; update our comments.
| | 06:27 | We'll change the class definition to a
class extension, which means putting the
| | 06:33 | open and close parentheses, deleting all that.
| | 06:37 | We'll delete everything except the two
things we want kept secret except for our tests.
| | 06:43 | In this case, it's the property BOOL
geocodePending, and the calculateSpeedInMPH
| | 06:50 | method. We'll keep that
internal also; save that.
| | 06:53 | Let's go over to our Location.h file,
the original header, and we'll remove the
| | 06:59 | things that we just moved
into the class extension.
| | 07:02 | So we will delete geocodePending,
and we'll delete calculateSpeedInMPH.
| | 07:08 | We can remove the ivar, don't
need it anymore, and we'll save that.
| | 07:14 | Now we'll need to import the new
LocationForTesting header file to the two
| | 07:19 | other files that need the location information.
| | 07:22 | So we'll import it into LocationTests.h
and we'll import it into GHUnitTests, and
| | 07:37 | make sure that our
location object itself has that.
| | 07:43 | At this point, our code should build okay.
| | 07:46 | Let's run our LogicTests; make sure they pass.
| | 07:48 | LogicTests are good. We build our code,
Command+B to build, and it builds okay
| | 07:58 | with no compiler errors.
| | 07:59 | Now we're ready to create an
asynchronous test for the updatePostalCode method.
| | 08:06 | I've created a new GHUnit
asynchronous test case file.
| | 08:11 | We'll add that to our project.
| | 08:16 | It's in the GHUnitTests group, and
we'll add it only to the GHUnitTests target.
| | 08:23 | Since it's already there, I don't
need to copy it, and it appears now.
| | 08:29 | Looking at this code, I've
already added the Location and the
| | 08:32 | LocationForTesting headers.
| | 08:34 | It includes the GHUnit.h file.
| | 08:38 | It defines the
LocationGHAsyncTests class file and implements it;
| | 08:44 | it has a single method
to testUpdatePostalCode.
| | 08:49 | The way that GHAsyncTests cases work, as we
can see here, is first you call self prepare.
| | 08:56 | GH asynchronous test cases need to
be told when the test is starting.
| | 09:02 | It does some initialization.
| | 09:05 | Secondly, we perform whatever
asynchronous tasks we need to. In this case,
| | 09:09 | we're creating a location object,
then calling the updatePostalCode method
| | 09:14 | of that, passing an event handler,
and in an event handler, we've put some
| | 09:19 | test code in there.
| | 09:20 | We'll come back to this.
| | 09:21 | Now we can perform whatever GHAsserts we need.
| | 09:26 | In this case, we're verifying that
geocodePending is set to YES right after the
| | 09:31 | call to updatePostalCode, and then we
wait for the asynchronous event to occur.
| | 09:38 | This is a GHUnit asynchronous test
mechanism used to tell the GHUnit
| | 09:43 | asynchronous framework to wait the specified
timeout for a successful completion status.
| | 09:52 | Up in our event handler, we're
passing that successful status through the
| | 09:57 | self-notify, forSelector, and we
pass in the selector to the test that
| | 10:03 | initiated the request.
| | 10:04 | Since this is asynchronous, and GHUnit
might be running multiple requests at
| | 10:10 | the same time, this tells it which one to
associate the successful completion status with.
| | 10:17 | When that happens, this will
complete and the test will pass.
| | 10:21 | So let's run this test and see what happens.
| | 10:23 | I will select the GHUnitTests, run them,
we build okay, we'll run our tests, and
| | 10:33 | everything passes okay.
| | 10:34 | Look at the log, nothing of interest there.
| | 10:37 | Let's look briefly at what happens if
the asynchronous event doesn't happen.
| | 10:43 | Close our Simulator;
| | 10:45 | let's comment out the
call to update postal code.
| | 10:49 | This should prevent our status from ever
being posted or notified, and we'll run this.
| | 11:01 | Rerun our test. We can see that the
test failed, but the reason for the
| | 11:07 | failure is not because of the
lacking notification, but because our
| | 11:11 | geocodePending was not set to YES.
| | 11:14 | So that's as expected.
| | 11:18 | That's this assertion failing.
| | 11:20 | So let's comment this out,
and we'll run our test again.
| | 11:24 | Now the only thing that should fail
would be the lacking notification.
| | 11:30 | Let's rerun our test. It's running,
and after five seconds, the request
| | 11:36 | fails. It timed out.
| | 11:37 | Let's go back up here.
| | 11:39 | Let me show you how it looks on this screen.
| | 11:42 | When we run an asynchronous test, if it's
waiting, an asterisk appears next to the text.
| | 11:46 | So this isn't telling you it's done,
it's telling you that it's currently
| | 11:50 | waiting, and then once it completed, of
course, because it failed it turned red.
| | 11:55 | So we've seen that ivars can be
exposed to our tests by using properties
| | 12:00 | instead of ivars, and placing them into a
special header file containing a class extension.
| | 12:06 | That's a great technique for dealing with
this situation that comes up quite often.
| | 12:10 | And we've also seen how to create a
GHUnit asynchronous test to test methods
| | 12:15 | that execute asynchronously.
| | Collapse this transcript |
| Testing an API with GHUnit asynchronous tests| 00:00 | A powerful application of GHUnit's
native asynchronous testing capability is the
| | 00:05 | testing of host-based APIs.
| | 00:08 | Let's take a look at how to
test a web service API using GHUnit
| | 00:13 | asynchronous tests.
| | 00:15 | From my personal experience,
problems with host-based APIs are the most
| | 00:20 | problematic area of iOS development.
| | 00:23 | Most applications interact
with at least one web service.
| | 00:27 | Web service API problems
can occur from many reasons.
| | 00:30 | Sometimes the API isn't understood
correctly or the API server goes down or
| | 00:35 | isn't implemented fully, or networking
problems prevent accessing the server,
| | 00:40 | among other problems.
| | 00:42 | Something that can greatly improve
the odds of success for a project is to
| | 00:46 | create a set of tests that call the API and
verify that the data returned looks as expected.
| | 00:53 | This is especially true and
helpful when the API is being developed
| | 00:57 | simultaneously with the iOS client application.
| | 01:02 | Let's look at how to create API tests.
| | 01:05 | One of the features that we could add
to WhatsMySpeed would be a display of
| | 01:10 | the weather forecast.
| | 01:11 | We'll query the weather web service
to obtain the local forecast data.
| | 01:16 | Google has an unofficial weather
web service that we can use for this.
| | 01:20 | Before we try to use this API, we'll
write some GHUnit asynchronous tests to
| | 01:25 | verify that the API works and also to
confirm exactly what the data returned
| | 01:31 | by the API looks like.
| | 01:33 | To start off, we'll create a new
TestCase file using the standard Async example
| | 01:38 | provided with GHUnit.
| | 01:40 | Come over to GHUnitTests. We'll right-
click. Select New File. we'll use the
| | 01:48 | Objective-C class. Click Next. We'll
call this WeatherAPITests. Click Next.
| | 01:52 | I'm going to assign this just to
GHUnitTests, put it in the GHUnitTests
| | 02:03 | folder, click Create.
| | 02:07 | As with the other tests,
we'll delete the header file.
| | 02:14 | Now we'll go, copy the sample code
provided with GHUnit and this is on the
| | 02:19 | Documentation page under the ExampleAsyncTest.
| | 02:21 | We need to make sure we pick up the
event handlers, also not just the test.
| | 02:33 | Come back to Xcode now, then
we'll paste that here. Okay.
| | 02:40 | Let's clean up these comments.
| | 02:46 | Now the example as posted on
GHUnit currently has some typos on it.
| | 02:53 | So here we have this extraneous string
the compiler is warning us this is wrong.
| | 03:00 | Also, this code is not ARC enabled.
| | 03:03 | So we'll need to get rid of
any releases or autorelease.
| | 03:07 | Here's another autorelease.
| | 03:17 | Now the code looks fairly clean.
| | 03:19 | We have a warning here because connection is
not being used that was used for the release.
| | 03:24 | We'll ignore that.
| | 03:27 | Save this, and let's run this
test, GHUnit in the Simulator.
| | 03:34 | The build succeeded and we see our new
testURLConnection test that was added.
| | 03:40 | Let's run it, and it passes okay.
| | 03:45 | Looking at the log, we see that the log is
having the full response from the API written out.
| | 03:52 | That's real handy.
| | 03:53 | This is a good way to look at data
release link and see what's being returned.
| | 03:57 | Okay, so let's quit out of the
Simulator. Let's go modify our test to test
| | 04:03 | the API we care about.
| | 04:06 | We'll rename our class,
WeatherAPITests, and we'll change the name of our
| | 04:13 | test to testWeatherAPI.
| | 04:17 | One of the things we're going to need
to do with this test, this sample code
| | 04:22 | strictly tests for a response.
| | 04:25 | So after the connection did finish
loading is when the notify is sent.
| | 04:32 | In the connection didReceiveData, the
data is being written to the log but it's
| | 04:37 | not being saved anywhere where we can test it.
| | 04:40 | So we'll need to add a property to
this. We'll call that our response and
| | 04:46 | we'll synthesize it here.
| | 04:53 | Now we'll need down in the connection
didReceiveData instead of just writing up
| | 04:59 | the string, we want to save it.
| | 05:01 | So we'll create an NSString and we'll take
the data where it's getting it right here.
| | 05:07 | Okay, so we're saving the response. Go
ahead and write that out to the test
| | 05:16 | log like it had been.
| | 05:18 | I'll also set that to our ApiResponse property.
| | 05:25 | Now up in our test, following the
Wait, we'll put a STAssertion in here.
| | 05:31 | But right now, we don't know what to assert for.
| | 05:34 | So let's go look that.
| | 05:36 | First, let's change our vanilla google.
com URL that was provided with the test,
| | 05:41 | and let's change that to the weather API.
| | 05:45 | I'm going to use an Austin postal code,
and now we'll go look at the data and see
| | 05:51 | what kind of data is returned.
| | 05:52 | We're still writing it out to the log.
| | 05:55 | So let's run GHUnit.
| | 05:56 | We are going to ignore that unused variable.
| | 06:00 | That was there because we deleted the
variable that we saved in order to release it.
| | 06:06 | Let's run our tests.
| | 06:10 | Okay, we missed one other line of
code. I'm going to quit out of this.
| | 06:14 | We can see that there was a failed
test there and the problem we have is that
| | 06:20 | because we changed the name of the
method up here, we need to remember to
| | 06:26 | associate the selector
with the response down here.
| | 06:30 | So where we're issuing the notify
with the selector, we have to give it the
| | 06:34 | correct selector name.
| | 06:36 | So I'm copying our method name
up here, we'll try this again now.
| | 06:42 | So running, saving and running,
and here's our WeatherAPITests.
| | 06:50 | Select that and we'll rerun it.
| | 06:54 | Here we see that the test passed
and the check mark indicates that.
| | 06:59 | We can also tell this hasn't been updated.
| | 07:04 | Let's run it again; let it update the display.
| | 07:07 | Your testWeatherAPI is black with a check mark.
| | 07:10 | Again, the check mark is shown in the log also.
| | 07:16 | Now we're looking at the data
that's coming back from the WeatherAPI.
| | 07:19 | We'll need to be careful that we don't pick
a string that will change with the weather.
| | 07:25 | Looking at the response, we see this string here,
forecast_information city data=Austin, TX.
| | 07:32 | That's probably going to be good
no matter what the weather does.
| | 07:35 | So we'll use that in our test to confirm
that this data is coming back from the API.
| | 07:39 | So quitting out of Simulator, go back to Xcode.
| | 07:43 | We'll want to insert the test
after the wait for the response.
| | 07:49 | So here we're going to add
another GHAssert; True in this case.
| | 07:56 | When performing unit tests, I
recommend keeping them as simple as possible.
| | 08:02 | So instead of trying to parse the full
XML or anything like that, we're just
| | 08:06 | going to do a simple substring check.
| | 08:10 | So something that'll work well for
this sort of test is to use the NSString
| | 08:14 | rangeOfString method.
| | 08:16 | We don't care about all the rest of that data
in response, just that Austin, TX response.
| | 08:21 | So we're going to GHAssertTrue,
compare with the response,
| | 08:27 | apiResponse rangeOfString.
| | 08:35 | So the NSString rangeOfString will
return NSNotFound if it's not there in
| | 08:42 | the location property.
| | 08:43 | So we'll compare against that and then
for our description, we'll just say that
| | 08:48 | the response didn't contain
the string we're looking for.
| | 08:52 | Okay, now let's run the tests.
| | 08:55 | Down here in our WeatherAPI it
failed, which says that we probably typed
| | 09:08 | this information in incorrectly, and let's quit
out of here and let's go see what we've done.
| | 09:16 | So we have this string here.
| | 09:17 | Well that's our problem.
| | 09:20 | We're missing that trailing, closing slash.
| | 09:26 | Try again and run our tests, and we'll
rerun this failing test and now it passes;
| | 09:36 | we have a check box there. Good to go!
| | 09:40 | Using this sort of approach, we can
add additional tests for however many API
| | 09:44 | calls we need to verify.
| | 09:46 | Typically, we will want to provide
other arguments to the request to select and
| | 09:51 | verify each of the APIs to be verified.
| | 09:54 | So we've seen how easy it is to
test web service APIs using GHUnit's
| | 09:59 | asynchronous 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 TestsAn 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 ObjectsWhat 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 ExamplesForming 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 |
|
|
ConclusionWhere 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 |
|
|