Instead, the unit test bundle ’ s code links directly to the functions defi ned in the application and executes all of the tests.. Except for those confi guration differences, you can fo
Trang 1at run time The settings instruct the loader to fi rst load a special unit test framework into the
application ’ s address space This process is known as bundle injection The testing framework
causes your unit test bundle to also be loaded into memory Initialization code in your unit test bundle intercepts the execution of your application, preventing it from running normally Instead, the unit test bundle ’ s code links directly to the functions defi ned in the application and executes all
of the tests It then forces the application to terminate
However convoluted, the beauty of this process is that your unit tests will test the actual, binary code of your application; the same code that will run when your application launches normally
The disadvantage is that this process is complex and requires a number of concessions from your application Mostly these are restrictions on how your application is built In the case of some C/C++ applications, you are also required to add code to your application to support dependent unit testing
iPhone Unit Tests
The iPhone SDK supports unit testing too The techniques are very similar to the Objective - C unit testing under Mac OS X — in fact, they both use the same testing framework — but with the following differences:
Independent unit tests are called logic tests in iPhone parlance, and are executed using the
iPhone simulator
Dependent unit tests are called application tests in iPhone parlance, and are preformed on
an actual iPhone or iPod Touch
Setting up an application test suite for the iPhone is signifi cantly different than setting up a dependent test suite for a Mac OS X application
Except for those confi guration differences, you can follow the guidelines and instructions for writing Objective - C unit tests when developing for the iPhone, substituting the terms “ independent test ” and “ dependent test ” with “ logic test ” and “ application test ”
iPhone unit testing requires iPhone OS 3.0 or later.
ADDING A UNIT TEST TARGET
The fi rst step in adding unit testing to a project is to create a unit test target Choose Project ➪ New Target and choose a Unit Test Bundle template Choose the Unit Test Bundle template from the Carbon group to test a C/C++ product, from the Cocoa group to test a Mac OS X Objective
-C product, or from the -Cocoa Touch group to create an iPhone unit test An example is shown in Figure 20 - 2
➤
➤
➤
Download at getcoolebook.com
Trang 2Some releases of the Xcode Development Tools, particularly those intended for iPhone development, do not include the older Carbon and C++ target templates,
so your installation might not have a Carbon Unit Test Bundle template
You can “borrow” one from an older Xcode installation or try installing the Mac OS X Xcode package.
Give the target a name and select the project it will be added to Choose a name that refl ects the
subject of the test For example, if you were writing tests for a target named HelperTool , you might
name the unit test target HelperToolTests
FIGURE 20 - 2
Xcode creates a new unit test target and adds it to your project You now need to confi gure it
properly and populate it with tests How you confi gure your unit test target depends on what kind
of unit test it is and what kind of product it tests
You might be anxious to try out your new unit test target, but you can ’ t until
it is confi gured and you have added at least one test; a unit test bundle will fail
if it doesn ’ t contain any tests The “ Creating a Unit Test ” section, later in this chapter, tells you how to add tests to your unit test bundle.
Trang 3Unit Test Target Dependencies
Unit tests are part of the build process Target dependencies are used to integrate unit tests into your build What target dependencies you create (if any) will depend on the kind of unit test you are creating
Independent Unit Test Dependencies
Because independent/logic unit tests are self - contained, they do not (technically) need to be dependent on any other targets All of the code that needs to be tested will be compiled when the target is built Whenever you want to run your unit tests, simply build your unit test target
One of the main tenets of test driven development is that your unit tests should be performed automatically every time you build your project To do that, follow these steps:
1. Set the active target to your application target
2. Make your application target dependent on your unit test target
Now every time you build your application, Xcode will fi rst build and run all of the unit tests
Alternatively, you could make the unit test target dependent on your application target; then you have the choice of just building your application or building your application and running all
of your unit tests You could also leave your application and unit test targets independent of each other and create an aggregate target that builds both As you can see, independent unit test targets are pretty fl exible
Dependent Unit Test Dependencies
Dependent Mac OS X (but not iPhone) unit test targets must depend on the target, or targets, that produce the products they test Otherwise, there is no guarantee that the tests will be performed
on up - to - date code If you want unit tests run every time you build your product, follow these steps:
1. Set the active target to the unit test target
2. Set the active executable to the results of the product target
Now every time you build, the application is built followed by a run of all of the unit tests The build will only be successful if both the build and the unit tests pass muster
Using this arrangement, you can easily ignore unit tests by building just the product target, or making another target dependent on the product target directly In a project with many product and unit test targets you could, for example, create two aggregate targets: one that depends on all
of the product targets for “ quick ” builds and a second that depends on all of their respective unit test targets for “ full ” builds
An iPhone unit test target ’ s dependencies are inverted from those used by dependent unit test targets The section “ Confi guring an iPhone Application Test ” shows you both how to confi gure the iPhone application unit test target and set up its dependencies
Download at getcoolebook.com
Trang 4Confi guring an Independent/Logic Unit Test
Independent unit tests require no special confi guration All you need to do is make the source
code for both the tests and the code to be tested members of the target The compiler build settings
for the target should match those of your product target as closely as possible, so that the code
produced when you ’ re compiling the unit test target is as close as possible to the code that will be
compiled into your fi nal product
Add the source fi les to the target by dragging them into the Compile Sources phase of the unit test
target, or by opening their Info window and adding them to the unit test target in the Targets tab
You can add the source for the actual tests in a similar manner (if the tests already exist), or by
adding them to the unit test target when you create them The section “ Creating a Unit Test ” shows
how to write and add a new test
The target SDK for an iPhone logic test (independent unit test) must be set to the iPhone Simulator
Confi guring a Mac OS X Dependent Unit Test
A dependent unit test needs to know where to load the application or libraries to be tested
Testing an Application
For applications, you accomplish this by setting the Bundle Loader ( BUNDLE_LOADER ) and Test Host
these steps to quickly set both values:
1. In the Info window of the unit test target, select the Build tab Choose All Confi gurations
Arrange the windows so that Groups & Files list in the project window and the Info window are both visible Expand the Products group in the project source group
2. In the target ’ s Info window, fi nd the Bundle Loader setting — you ’ ll fi nd it in the Linking
group — and click in its value fi eld to edit it In the Products smart group, locate the executable product to be tested and drag it into the value fi eld of the Bundle Loader setting
Xcode inserts the full path to the executable For application bundles, you need to locate the application ’ s binary executable — the folder with the extension .app is not an executable
You can manually supply the path, or follow these steps:
a. Right/Control+click the product and choose Reveal in Finder
b. In the Finder, Right/Control+click the application and choose Open Package Contents
c. Open the Contents folder
d. Open the MacOS folder
e. Drag the application ’ s executable into the Bundle Loader setting ’ s value cell in the target ’ s Info window
3. Select the beginning of the path that represents the build location for the current build
confi guration Typically this is / path/to/project - folder /build/ build - configuration
Trang 5this portion of the path with the $(CONFIGURATION_BUILD_DIR) macro In a project that produces a simple command - line executable, the fi nal Test Host path will look like
the Bundle Loader path will look something like $(CONFIGURATION_BUILD _DIR)/ AppName
4. Locate the Test Host setting — you ’ ll fi nd it in the Unit Testing group — and double - click its value fi eld to edit it Enter $(BUNDLE_LOADER) as its value This sets the TEST_HOST build setting to the same value as the BUNDLE_LOADER setting
The Bundle Loader setting tells the linker to treat the executable as is if were a dynamic library
This allows the tests in the unit test bundle to load and link to the classes and functions defi ned in your application
The Test Host setting tells the unit test target ’ s script phase the executable that will initiate testing
When testing an application, it is the application that gets loaded and launched The injected testing framework and bundle intercepts the application ’ s normal execution to perform the tests
Preparing Your Application
A few concessions are required of applications being tested by dependent unit test bundles You must make these changes in the target that produces your application, not the unit test target These requirements do not apply to independent unit tests or when you ’ re testing dynamic libraries or frameworks
Open the Info window for the application target and choose the Build tab Choose All Confi gurations and set the following:
Set ZeroLink to NO (uncheck the box)
If your project is a C++ program, fi nd the Symbols Hidden By Default setting and turn it off (uncheck the box)
ZeroLink must be turned off for your application The ZeroLink technology is incompatible with the techniques used to intercept the application at run time ZeroLink has been deprecated in Xcode 3, so you may not even see it in your build settings, but projects from prior versions of Xcode may still have it set
The Symbols Hidden By Default option must be disabled for C++ applications so that all of the classes and functions defi ned by your application appear as external symbols The unit test target must link to the symbols in your application, so these symbols must all be public Objective - C tests are all resolved at run time by introspection, so they don ’ t require any public symbols at link time
Testing Libraries and Frameworks
When you ’ re constructing a unit test to test a dynamic library or framework, leave the Bundle Loader and Test Host settings empty This is because the “ program ” to be loaded for testing will
be the unit test bundle itself If the Test Host setting is blank, the script launches the otest (for Objective - C) or CPlusTestRig (for C/C++) tool instead The testing tool loads the unit test bundle and runs the tests it fi nds there, with the assumption that the unit test bundle either contains (in the
➤
➤
Download at getcoolebook.com
Trang 6case of independent tests) or will load (in the case of dependent tests for libraries and frameworks)
the code to be tested
For dependent unit tests that test libraries or frameworks, the unit test bundle is the client
application Confi gure your unit test bundle exactly as you would an application that uses those
libraries or frameworks, adding the frameworks to the target and including whatever headers are
appropriate to interface to them The dynamic library loader takes care of resolving the references
and loading the libraries at run time
Confi guring an iPhone Application Test
Testing an iPhone application is different from testing a Mac OS X application, and requires a
different organization in Xcode In Mac OS X development (described in the previous section), you
tell the unit test bundle what product you want tested It takes on the responsibility of loading that
target, injecting itself into the application, and performing its tests
In iPhone development, the roles of the application and unit test bundle are reversed You create a
custom version of your application that includes the unit test bundle product You load and run your
test app on your iPhone or iPod Touch device like any other app Once started, your app loads the
unit test bundle, which takes over and performs its tests
To confi gure an iPhone app for unit testing, follow these steps:
1. Add a Unit Test Bundle target, using the Cocoa Touch Unit Test Bundle template, as
described in the beginning of this section This is your unit test target
2. Duplicate the target that builds your app Give it a descriptive name like MyAppTesting
This is your test app target
3. Make your test app target dependent on your unit test bundle target
4. Add the product of the unit test target (the MyTests.octest bundle) to the Copy Bundle
Resources phase of your test app target This will include the compiled suite of unit tests in your app ’ s resource bundle
5. Set the active target to the test app target
6. Set the Target SDK to iPhone Device 3.0 or later
7. Build and run your test app target The test results will appear in your console window
Unlike all other kinds of unit tests, iPhone application tests aren ’ t run during the build phase You
must build and run your test application, which downloads both it and the unit tests to your iPhone
for execution This introduces a number of limitations to using iPhone application tests:
Application tests can ’ t be made an automatic part of your build process
The application test bundle must also be provisioned to run on your iPhone The “ correct ” way to do this is to create a provisioning profi le that includes both the application and the application test bundle (see Chapter 22) I admit that I ’ ll often simply set the Bundle
➤
➤
Trang 7Identifi er build setting in the unit test bundle to the same ID as the application It seems sleazy, but it works
The unit test bundle will take over your app, run its tests, and exit You can ’ t use your application interactively during testing
The code in the unit test can ’ t link directly to the application This is because the unit test target builds before the application, so it can ’ t link directly to the application ’ s code
You might be scratching your head about the last one You ’ re probably asking “ If the unit test code can ’ t link to the code in the application, what use is it? ”
One solution is to include the code in both targets At run time only one implementation of the class will be used — most likely the one in the application (because it loaded fi rst), but the Objective - C run time doesn ’ t specifi cally guarantee this Regardless, this is an acceptable solution
in most cases and gives your unit tests direct access to iPhone hardware and its application environment
Another solution is introspection Instead of referring to application classes directly, do it indirectly
in the case where the test will be running on an actual iPhone Listing 20 - 1 shows an example This code will compile, link, and run — as long as something in the same process actually implements the SieveOfEratosthenes class, which our application does
- (void)setUp {
#if TARGET_OS_IPHONE testSieve = [[NSClassFromString(@"SieveOfEratosthenes") alloc]
initWithMaxPrime:UNIT_TEST_MAX_PRIMES];
#else testSieve = [[SieveOfEratosthenes alloc]
initWithMaxPrime:UNIT_TEST_MAX_PRIMES];
#endif STAssertNotNil(testSieve,@"Unable to create SieveOfEratosthenes");
}
The most signifi cant pitfall in iPhone application testing is the same problem inherent in logic tests (independent unit tests) Namely, that you run the risk
of testing code that’s different from the code in your fi nal product You must remember to update your test app target scrupulously so that it has the same build confi guration as your primary app target If you have any doubts, simply discard the test app target and reproduce it using the steps listed previously This will guarantee that all of your test app target settings are identical to those in your production app target
➤
➤
Download at getcoolebook.com
Trang 8CREATING A UNIT TEST
Once you have your unit test target created and confi gured, adding unit tests is simple Here are the
basic steps:
1. Create a unit test class and add its source fi le to the unit test target
2. Add test methods to the class
3. Register the tests with the unit testing framework
Unit test class fi les can go anywhere in your project, but I suggest, at the very least, creating a group
for them named “ Tests ” or “ Unit Tests ” In a larger project you might organize your unit test fi les in
a folder, or you might group them together with the code that they test The choice is yours
Each class that you create defi nes a group of tests Each test is defi ned by a test method added to
the class A class can contain as many different tests as you desire, but must contain at least one
How you organize your tests is entirely up to you, but good practices dictate that a test class should
limit itself to testing some functional unit of your code It could test a single class or a set of related
functions in your application
Once defi ned, your tests must be registered with the unit testing framework so that it knows what tests
to run For Objective - C tests this happens automatically Objective - C test methods must adhere to a
simple naming scheme — basically they must all begin with the name “ test ” Objective - C introspection
is then used to locate and run all of the tests you defi ned For C++ unit tests, you add a declaration for
each test you ’ ve written The exact requirements for each are described in the “ Objective - C Tests ” and
“ C++ Test Registration ” sections, respectively
Each test method should perform its test and return Macros are provided for checking the
expectations of each test and reporting failures A test is successful if it completes all of its tests and
returns normally An example test is shown in Listing 20 - 2
void SieveOfEratosthenesTests::testPrimes( )
{
// Test a number of known primes
static int knownPrimes[] =
{ 2, 3, 5, 11, 503, 977, 12347, 439357, 101631947 };
SieveOfEratosthenes testSieve(UNIT_TEST_MAX_PRIMES);
CPTAssert(testSieve.isPrime(knownPrimes[i]));
}
In this example, the testPrime function defi nes one test in the SieveOfEratosthenesTests class The
test creates an instance of the SieveOfEratosthenes class, and then checks to see that it correctly
identifi es a series of numbers known to be prime If all of the calls to testSieve.isPrime() return
true , the test is successful; the testPrimes object is destroyed and the function returns If any call
that the test failed The testing macros are described in the “ Objective - C Test Macros ” and “ C++