1. Trang chủ
  2. » Công Nghệ Thông Tin

Lập trình Wrox Professional Xcode 3 cho Mac OS part 71 ppsx

13 182 0
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 13
Dung lượng 3,86 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Create a unit test class and add its source fi le to the unit test target.. Unit test class fi les can go anywhere in your project, but I suggest, at the very least, creating a group for

Trang 1

560 ❘CHAPTER 20 UNIT TESTING

CREATING 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

LISTING 20 - 2: Sample C++ unit test

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);

for (size_t i=0; i < sizeof(knownPrimes)/sizeof(int); i++)

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

to testSieve.isPrime() returns false , the CPTAssert macro signals to the testing framework

that the test failed The testing macros are described in the “ Objective - C Test Macros ” and “ C++

Test Macros ” sections

Download at getcoolebook.com

Trang 2

Common Test Initialization

If a group of tests — defi ned as all of the tests in a TestCase class — deal with a similar set of data

or environment, the construction and destruction of that data can be placed in two special methods:

setUp and tearDown The setUp method is called before each test is started, and the tearDown

method is called after each test is fi nished Override these methods if you want to initialize values or create common data structures that all, or at least two, of the tests will use The typical use for setUp

and tearDown is to create a working instance of an object that the tests will exercise, as illustrated in Listing 20 - 3 The test class defi nes a single instance variable that is initialized by setUp and destroyed

by tearDown Each test is free to use the object in the instance variable as the subject of its tests

For Objective - C tests, the methods your test class should override are - (void)setUp and

- (void)teardown For C/C++ tests, the functions to override are void TestCase::setup() and

void TestCase::tearDown()

LISTING 20 - 3: Objective - C unit test using setUp and tearDown

SieveOfEratosthenesTests.h

#define UNIT_TEST_MAX_PRIMES 100000

@interface SieveOfEratosthenesTests : SenTestCase {

SieveOfEratosthenes* testSieve;

}

SieveOfEratosthenesTests.m

@implementation SieveOfEratosthenesTests

- (void)setUp {

testSieve = [[SieveOfEratosthenes alloc]

init:UNIT_TEST_MAX_PRIMES];

}

- (void)tearDown {

[testSieve release];

testSieve = nil;

}

- (void)testInvalidNumbers {

// These should all return NO STAssertFalse([testSieve isPrimeInMap:-1], @"-1 is not a prime number");

STAssertFalse([testSieve isPrimeInMap:0], @"0 is not a prime number");

STAssertFalse([testSieve isPrimeInMap:1], @"1 is not a prime number");

}

Creating a Unit Test❘ 561

Trang 3

562 ❘CHAPTER 20 UNIT TESTING

The setUp and tearDown methods are called before and after every test This allows tests to

perform destructive tests on the object — that is, tests that alter the object ’ s state — because the

object will be destroyed at the end of the test and re - created anew before the next test is run

Unit test classes have standard constructor and destructor methods Do not use the constructor to

create test data If your test structures are expensive to create and destroy, you may be tempted

to create them in the constructor and let them persist for the duration of the test class Don ’ t do

this Your next approach might be to turn the setUp method into a factory that creates a singleton

object when called the fi rst time Sorry, but that probably won ’ t work either Some testing

frameworks create a separate instance of the test class for each test

Instead, make a single test that creates the expensive object and then calls a series of subtests

itself Remember not to name your Objective - C subtests “ test … ” or the testing framework will run

them again

Because so many of the minor details of creating tests for Objective - C and C/C++ differ, the steps

for creating your own tests have been separated into the following two sections, “ Objective - C

Tests ” and “ C++ Tests ”

Objective - C Tests

To create an Objective - C test class and add it to a unit test target, start by selecting the File ➪ New

File command The new fi le assistant presents a list of new fi le templates Choose the Objective - C

Test Case Class, as shown in Figure 20 - 3, from either the Cocoa Class or Cocoa Touch Class group,

as appropriate

FIGURE 20 - 3

Download at getcoolebook.com

Trang 4

Click the Next button and give the test case class and fi le a name The name should be descriptive of its purposes, such as StudentTests for a set of tests that validate the Student class Make sure you create a matching .h fi le Select the working project and add the test to the desired unit test target,

as shown in Figure 20 - 4, making sure you don ’ t include the test class fi le in any other target Click the Finish button

FIGURE 20 - 4

Xcode creates a skeletal test class defi nition and implementation, similar to the one shown in Listing 20 - 4 All of your test classes for Objective - C must be direct subclasses of the

SenTestCase class

LISTING 20 - 4: Example Objective - C test case

#import < SenTestingKit/SenTestingKit.h >

@interface StudentTests : SenTestCase {

}

@end

Xcode has created the framework for your class and has already added it to your unit test target The only thing you need to do now is to write one or more test methods Each test method:

Creating a Unit Test ❘ 563

Trang 5

564 ❘CHAPTER 20 UNIT TESTING

Must begin with “ test ” in lowercase, as in - testNegativeCoordinates

Must return void Must not take any parameters

An example of three such tests is shown in Listing 20 - 5

LISTING 20 - 5: Example Objective - C tests

#import "StudentTests.h"

@implementation StudentTests

- (void)setUp

{

student = [[Student alloc] initWithName:@"Jane Doe"];

STAssertNotNil(student,@"Unable to create Student");

}

- (void)tearDown

{

[student release];

student = nil;

}

- (void)testNameConstructor;

{

STAssertTrue([[student name] isEqualToString:@"Jane Doe"],

@"Student.name property incorrect");

}

- (void)testNamePartsParsing;

{

STAssertTrue([[student firstName] isEqualToString:@"Jane"],

@"Student.firstName parsing incorrect");

STAssertTrue([[student lastName] isEqualToString:@"Doe"],

@"Student.lastName parsing incorrect");

}

- (void)testNamePartsReplacement;

{

STAssertTrue([[student name] isEqualToString:@"Jane Doe"],

@"Student.name property incorrect");

[student setFirstName:@"John"];

STAssertTrue([[student name] isEqualToString:@"John Doe"],

@"Student.name first name replacement incorrect");

[student setLastName:@"Smith"];

STAssertTrue([[student name] isEqualToString:@"John Smith"],

@"Student.name last name replacement incorrect");

}

@end

Download at getcoolebook.com

Trang 6

Amazingly, you are all done The introspective nature of Objective - C allows the unit test framework

to discover automatically all classes that are subclasses of SenTestCase in the bundle, and then fi nd all void methods that begin with the name “ test ” The unit test framework creates your test object and then executes each test method one at a time

Objective - C Test Macros

When you write your test, you will employ a set of macros to evaluate the success of each test If the assertion in the macro fails to meet the expectation, the test fails and a signal with a description

of the failure is passed back to the unit test framework Test failures appear as an error in the build log

Each macro accepts a description of the failure The description argument is a Core Foundation format string that may be followed by a variable number of arguments, à la NSLog(description, )

or - [NSString stringWithFormat:format, ] The unit test macros available are listed in the following table The STFail macro unconditionally records a failed test Use it in a block of code where the program fl ow has already determined that a failure has occurred All of the other macros are assertion macros The test is successful if the parameters meet the expectations of the assertion

If they do not, a failure is recorded using the description constructed using the format string ( @ ”

in the table) and the remaining arguments

STFail(@ ” ” , ) This is the basic macro for unconditionally

recording a test failure This macro always causes the described failure to be recorded

STAssertTrue(expression,@ ” ” , ) The test is successful if the statement

expression evaluates to YES Otherwise,

a description of the failed test is logged

STAssertFalse(expression,@ ” ” , ) The test is successful if the statement

expression evaluates to NO

STAssertNil(reference,@ ” ” , ) The test is successful if the statement

reference evaluates to nil

STAssertNotNil(reference,@ ” ” , ) The test is successful if the statement

reference evaluates to something other than nil

STAssertEquals(left,right,@ ” ” , ) The test is successful if the numeric value of

the statement left equals the numeric value

of the statement right Both statements must evaluate to the same primitive type That is, they must both be long int, fl oat, and so on

You may cast them if necessary If the values are not the same type or value, the test fails

continues

Creating a Unit Test ❘ 565

Trang 7

566 ❘CHAPTER 20 UNIT TESTING

STAssertEqualsWithAccuracy(left,right,

accuracy,@ ” ” , )

The test is successful if the absolute diff erence between the numeric value of the left statement and the numeric value

of the right statement is equal to or less than the value of accuracy Both left and right

must evaluate to the same primitive type

If the values diff er by more than accuracy

or are not the same type, the test fails

STAssertEqualObjects(left,right,@ ” ” ,

)

The test is successful if the object reference

in the left statement is equal to the object reference in the right statement, according

to the [left isEqual:right] method Both object references must be of the same type and the isEqual method must return normally with a Boolean result If the object references are not the same type, the isEqual method returns NO , or the isEqual method throws an exception, the test fails

STAssertThrows(statement,@ ” ” , ) The test is successful if statement causes an

exception to be thrown

STAssertThrowsSpecific(statement,class,

@ ” , )

The test is successful if statement causes an exception of the class class to be thrown

STAssertThrowsSpecificNamed(statement,

class,name,@ ” ” , )

The test is successful if the statement

causes an exception of class with the name

exception name to be thrown

STAssertNoThrow(statement,@ ” ” , ) The test is successful if statement does not

cause an exception to be thrown

STAssertNoThrowSpecific(statement,

class,@ ” ” , )

The test is successful if statement does not cause an exception of class to be thrown Note that the test is still successful

if the statement causes some other class of exception to be thrown

STAssertThrowsSpecificNamed(statement,

class,name,@ ” ” , )

The test is successful if statement does not cause an exception of class with the name

exception name to be thrown

After you have added your tests, you are ready to build the unit test target

(continued)

Download at getcoolebook.com

Trang 8

C++ Tests

To create a C++ test class, follow the instructions for adding an Objective - C Test Case Class to your project, with the one exception that you ’ ll start by choosing the C++ Test Case Class template from the Carbon group Once you ’ ve selected the correct targets and added the class fi les to the project, return here

Even if your application is written in pure C, the C/C++ testing framework still requires C++ objects to defi ne and drive the test process Write your tests by creating the appropriate C++ class The test member functions you add can then call your application’s C functions.

Xcode creates a skeletal test class defi nition and implementation, as shown in Listing 20 - 6 All of your test classes for C++ must be direct subclasses of the TestCase class

LISTING 20 - 6: Example C++ test case

#include < CPlusTest/CPlusTest.h >

class StudentTests : public TestCase { public:

StudentTests(TestInvocation* invocation);

virtual ~StudentTests();

};

Xcode has created the framework for your class and has already added it to your unit test target

The only thing you need to do now is to write one or more test methods Each test method:

Must return void Must not take any parameters Unlike Objective - C, C++ test method names do not have to conform to any naming convention, but

it is more readable if you retain the habit of starting each method name with “ test ” An example of two such tests is shown in Listing 20 - 7

LISTING 20 - 7: Example C++ tests

#include "StudentTests.h"

StudentTests::StudentTests(TestInvocation *invocation) : TestCase(invocation)

{ }

continues

Creating a Unit Test❘ 567

Trang 9

568 ❘CHAPTER 20 UNIT TESTING

LISTING 20-7 (continued)

StudentTests::~StudentTests()

{

}

void StudentTests::testNameConstructor( )

{

Student student("Jane Doe");

CPTAssert(strcmp(student.getName(),"Jane Doe")==0);

}

void StudentTests::testNameProperty( )

{

Student student();

CPTAssert(student.getName()==NULL)

student.setName("Jane Doe");

CPTAssert(strcmp(student.getName(),"Jane Doe")==0);

}

C++ Test Registration

C++ does not include the kind of introspection that Objective - C uses to discover the test classes and

methods that you ’ ve defi ned Consequently, you must tell the C++ unit test framework exactly what

tests you ’ ve defi ned You accomplish this by registering the tests using static constructors, as shown

in Listing 20 - 8

LISTING 20 - 8: C++ test registration

StudentTests studentTestsNameConstructor(TEST_INVOCATION(StudentTests,

testNameConstructor));

StudentTests studentTestsNameProperty(TEST_INVOCATION(StudentTests,

testNameProperty));

For every test you want run, you must create an instance of your test class, passing a

TestInvocation object to its constructor To make this easier to code, the unit test framework

provides a TEST_INVOCATION macro, which creates a confi gured instance of the TestInvocation class

for you The macro parameters are the name of your TestCase subclass and the test function You

can give the static variable any name you want, but it is more readable if you give it a name that

describes the test Remember that these object names are public, so generic names like test1 are

likely to collide with similar names from other TestCase classes

Each invocation object is constructed when the application starts up The constructor for the

TestCase class registers the test with the unit test framework Thus, as soon as the application is

ready to run, the testing framework has a complete list of the tests to be executed

C++ Test Macros

When you write your test, you will employ the CPTAssert macro to evaluate the success of each

test If the argument to the macro evaluates to a non - zero value, the test was successful If not, the test

and a signal with a description of the failure is passed back to the unit test framework Test failures

appear as an error in the build log Examples of using CPTAssert were shown in Listing 20 - 7

Download at getcoolebook.com

Trang 10

C++ Test Execution

In addition to registering the tests, a C/C++ application being tested by a dependent unit test needs

to invoke the unit tests at the appropriate time Unlike an Objective - C application, C applications can ’ t be automatically intercepted to prevent their normal execution You must add code to your application to run the tests and exit — but only when your application is being run for the purposes

of unit testing This section describes the code you need to add to non - Carbon applications — that

is, any kind of process that doesn ’ t use a Carbon run loop — and a less invasive method you can use with Carbon (run loop) applications

For command - line applications, this is simply a matter of inserting some code into your main()

function You insert this code after the point in your application where your unit tests can be run — typically after any required initialization — but before the application actually starts running

Assuming your application has no special initialization, the example in Listing 20 - 9 shows what you need

LISTING 20 - 9: Unit test hook for main()

// Conditional support for C/C++ unit tests

#ifndef UNIT_TEST_SUPPORT

#define UNIT_TEST_SUPPORT 1

#endif

#if UNIT_TEST_SUPPORT

#include < CPlusTest/CPlusTest.h >

#endif int main (int argc, char * const argv[]) {

//Perform any required initialization here

#if UNIT_TEST_SUPPORT TestRun run;

// Create a log for writing out test results TestLog log(std::cerr);

run.addObserver( & log);

// Get all registered tests and run them.

TestSuite & allTests = TestSuite::allTests();

allTests.run(run);

// If the TestSuite ran any tests, log the results and exit.

if (run.runCount()) {

// Log a final message.

std::cerr < < " Ran " < < run.runCount() < < " tests, " < < run.failureCount() < < " failed."

< < std::endl;

return (0);

} // Otherwise, run the application normally.

#endif

Creating a Unit Test❘ 569

Ngày đăng: 04/07/2014, 06:20