2.5 Test the Data Model

This section explains the third step in building the data model: testing it. This process will be described in several steps:

1.Create a file called DMTEST.CPP.

2.Create a person database.

3.Serialize the list (write it to a file).

4.Deserialize the stored data.

5.Test the FindPerson function.

6.Clean up and quit.

7.Add the supporting functions.

·To create a file called DMTEST.CPP:

1.Add the following #include directive:

#include "person.h"

2.Add the following function prototypes below the #include line:

// Function prototypes.

CPersonList* MakeDataBase();

CFile* OpenForReading( const CString& rFileName );

CFile* OpenForWriting( const CString& rFileName );

CPersonList* ReadDataBase( CFile* pFile );

BOOL WriteDataBase( CFile* pFile, CPersonList* pDataBase );

void TestFindPerson( CPersonList* pDataBase );

void ListDataBase( CPersonList* db );

3.Add the beginnings of your program's main function:

void main()

{

const char szFileName[] = "tutorial.dat";

#ifdef _DEBUG

// Prepare for display of search results

const int nDumpChildren = 1;

afxDump.SetDepth( nDumpChildren );

#endif

The first line in the function declares a filename. The other lines prepare the afxDump “dump context,” predefined by the Microsoft Foundation Class Library. When results of the database search test are displayed later, all objects in the list will dump their contents. The default “depth” of 0 dumps only information about the list, not its contents. For more information about setting the depth, see “How FindPerson Is Tested” on page 58.

·To create a person database:

Add the following lines to the main function:

printf( "Create a person list and fill it with persons\n" );

CPersonList* pDataBase = MakeDataBase();

These lines call a function to create and return a database with several person objects in it. You'll add the MakeDataBase function later.

·To serialize the list (write it to a file):

Add the following lines of code to the main function below the lines added in the previous step:

printf( "Serialize the person list\n" );

CFile* pFile; // Declare a file object

TRY

{

// Could throw a file exception if can't open file

pFile = OpenForWriting( szFileName );

// Could throw an archive exception if can't create

WriteDataBase( pFile, pDataBase );

}

CATCH( CFileException, theException )

{

printf( "Unable to open file for writing\n" );

exit( -1 );

}

AND_CATCH( CArchiveException, theException )

{

printf( "Unable to save the database\n" );

pFile -> Close(); // Close up

delete pFile;

exit( -1 );

}

END_CATCH

// No exceptions, so close up

pFile -> Close();

delete pFile;

ListDataBase( pDataBase );

printf( "Delete the list and all its elements\n" );

pDataBase -> DeleteAll();

ListDataBase( pDataBase );

delete pDataBase;

These lines create a file object, use it to create an archive object, use the archive object to serialize the list, and clean up afterward. The same process was covered briefly earlier in “How to Serialize the List” on page 42. For more information about the roles of the CFile and CArchive objects, see “More on Serialization” on page 60. Once the list has been serialized to disk, the list can be deleted.

Additionally, the code handles exceptions that may occur if a file can't be opened or an archive can't be created successfully or fails while writing. The use of the TRY, CATCH, AND_CATCH, and END_CATCH macros for exception handling is discussed in “Exception Handling” on page 61.

·To deserialize the stored data:

Add the following lines to the main function below those added in the previous step:

printf( "Deserialize the data from disk into a new list\n" );

CPersonList* pDataBase2; // Create a new, empty list

TRY

{

// Could throw a file exception if can't open file

pFile = OpenForReading( szFileName );

// Could throw an archive exception if can't create

pDataBase2 = ReadDataBase( pFile );

}

CATCH( CFileException, theException )

{

printf( "Unable to open file for reading database\n" );

exit( -1 );

}

AND_CATCH( CArchiveException, theException )

{

printf( "Unable to read the database\n" );

pFile -> Close(); // Close up before exiting

delete pFile;

exit( -1 );

}

END_CATCH

// No exceptions, so close up

pFile -> Close();

delete pFile;

ListDataBase( pDataBase2 );

As in the previous step, these lines create a file object, then an archive object, and use the archive object to deserialize the list. After it's read in, the list is printed to demonstrate success. Most of the real work of file object creation, file opening, archive creation, and serialization takes place in the supporting functions OpenForReading and ReadDataBase. You'll add these functions in the last step.

It's particularly interesting that deserialization recreates the person objects as it reads in their data. As objects are created and reinitialized with the data from disk, they are stored in the new, empty list, pDataBase2. Thus, the objects formerly serialized are fully reconstructed by the process of deserialization. For more information about deserialization, see “More on Serialization” on page 60.

·To test the FindPerson function:

Add the following lines to the main function below the lines added in the previous step:

printf( "Test the FindPerson function\n" );

if ( pDataBase2 != NULL )

TestFindPerson( pDataBase2 );

If anything has been read into the new list, these lines call the TestFindPerson function, passing it a pointer to the list. You'll add TestFindPerson in the last step. For more information about this testing, see “How FindPerson Is Tested” on page 58.

·To clean up and quit:

Add the following lines to the main function after the lines added in the previous step:

printf( "Delete the list and all its elements\n" );

pDataBase2 -> DeleteAll();

delete pDataBase2;

TRACE( "End of program\n" );

}

These lines first delete all elements of the list, then delete the list object. Deleting dynamically constructed objects such as the list and file objects prevents memory leaks. These lines conclude the code for the main function. Your main function should now match the one in Listing 3 on page 73.

·To add the supporting functions:

1.Add the MakeDataBase function, which creates a new list object, fills it with person objects, and returns the list:

// MakeDataBase - Create a database and add some persons

//

CPersonList* MakeDataBase()

{

TRACE( " Make a new person list on the heap\n" );

CPersonList* pDataBase = new CPersonList;

TRACE( " Add several new persons to the list\n" );

CPerson* pNewPerson1 = new CPerson( "Smith", "Mary", "435-8159" );

pDataBase -> AddHead( pNewPerson1 );

CPerson* pNewPerson2 = new CPerson( "Smith", "Al", "435-4505" );

pDataBase -> AddHead( pNewPerson2 );

CPerson* pNewPerson3 = new CPerson( "Jones", "Steve",

"344-9865" );

pDataBase -> AddHead( pNewPerson3 );

CPerson* pNewPerson4 = new CPerson( "Hart", "Mary", "287-0987" );

pDataBase -> AddHead( pNewPerson4 );

CPerson* pNewPerson5 = new CPerson( "Meyers", "Brian",

"236-1234" );

pDataBase -> AddHead( pNewPerson5 );

TRACE( " Return the completed database to main\n" );

return pDataBase;

}

2.Add the OpenForReading function, which creates a file object and uses it to open the file specified by rFileName for reading:

// OpenForReading - open a file for reading

//

CFile* OpenForReading( const CString& rFileName )

{

CFile* pFile = new CFile;

CFileException* theException = new CFileException;

if ( !pFile -> Open( rFileName, CFile::modeRead, theException ) )

{

delete pFile;

TRACE( " Threw file exception in OpenForReading\n" );

THROW( theException );

}

// Exit here if no exceptions

return pFile;

}

3.Add the OpenForWriting function, which creates a file object and uses it to open the file specified by rFileName for writing:

// OpenForWriting - open a file for writing

//

CFile* OpenForWriting( const CString& rFileName )

{

CFile* pFile = new CFile;

CFileStatus status;

UINT nAccess = CFile::modeWrite;

// GetStatus will return TRUE if file exists,

// or FALSE if it doesn't exist

if ( !CFile::GetStatus( rFileName, status ) )

nAccess |= CFile::modeCreate;

CFileException* theException = new CFileException;

if ( !pFile -> Open( rFileName, nAccess, theException ) )

{

delete pFile;

TRACE( " Threw a file exception in OpenForWriting\n" );

THROW( theException );

}

// Exit here if no errors or exceptions

TRACE( " Opened file for writing OK\n" );

return pFile;

}

4.Add the ReadDataBase function, which creates an archive object and uses it to deserialize data from the disk, creating a new list:

// ReadDataBase - read data into a person list

//

CPersonList* ReadDataBase( CFile* pFile )

{

CPersonList* pNewDataBase = NULL;

// Create an archive from pFile for reading

CArchive archive( pFile, CArchive::load );

TRY

{

// and deserialize the new database from the archive

archive >> pNewDataBase;

}

CATCH( CArchiveException, theException )

{

TRACE( " Caught an archive exception in ReadDataBase\n" );

#ifdef _DEBUG

theException -> Dump( afxDump );

#endif

archive.Close();

// If we got part of the database then delete it so we don't

// have any Memory leaks

if ( pNewDataBase != NULL )

{

pNewDataBase -> DeleteAll();

delete pNewDataBase;

}

THROW_LAST();

}

END_CATCH

// Exit here if no errors or exceptions

archive.Close();

return pNewDataBase;

}

5.Add the WriteDataBase function, which creates an archive object and uses it to serialize the list to disk:

// WriteDataBase - write data from a person list to disk

//

BOOL WriteDataBase( CFile* pFile, CPersonList* pDataBase )

{

// Create an archive from pFile for writing

CArchive archive( pFile, CArchive::store );

TRY

{

// and serialize the data base to the archive

archive << pDataBase;

}

CATCH( CArchiveException, theException )

{

TRACE( " Caught an archive exception in WriteDataBase\n" );

#ifdef _DEBUG

theException -> Dump( afxDump );

#endif

archive.Close();

THROW_LAST();

}

END_CATCH

// Exit here if no errors or exceptions

archive.Close();

return TRUE;

}

6.Add the TestFindPerson function, which demonstrates successful and unsuccessful searches of the list, using the list's FindPerson function:

// TestFindPerson - test CPersonList::FindPerson

//

void TestFindPerson( CPersonList* pDataBase )

{

printf( " Looking for the name Banipuli\n" );

CPersonList* pFound = pDataBase -> FindPerson( "Banipuli" );

if ( pFound -> IsEmpty() )

{

printf( " No matching persons\n" );

}

else

{

printf( " Found matching persons\n" );

#ifdef _DEBUG

pFound -> Dump( afxDump );

#endif

}

delete pFound;

printf( " Looking for the name Smith\n" );

pFound = pDataBase -> FindPerson( "Smith" );

if ( pFound -> IsEmpty() )

{

printf( " No matching persons\n" );

}

else

{

printf( " Found matching persons\n" );

#ifdef _DEBUG

pFound -> Dump( afxDump );

#endif

}

// Don't DeleteAll the found list since it

// shares CPerson objects with database

delete pFound; // Deletes only the list object

}

7.Add the ListDataBase member function, which writes out the contents of the database:

void ListDataBase( CPersonList* db )

{

CPerson* pCurrent;

POSITION pos;

if ( db -> GetCount() == 0 )

printf( " List is Empty\n" );

else

{

printf( " List contains:\n" );

pos = db -> GetHeadPosition();

while ( pos != NULL )

{

pCurrent = (CPerson*)db -> GetNext( pos );

printf( "\t%s, %s\t%s\n", (const char*)pCurrent ->

GetLastName(),

(const char*)pCurrent -> GetFirstName(),

(const char*)pCurrent -> GetPhoneNumber() );

}

}

}

Your code for the supporting functions should match that given in Listing 3 on page 73.

Your DMTEST.CPP file is now complete. It contains one #include directive, six function prototypes, the main function, and seven supporting functions.

To continue the tutorial and compile the program, see “Build the Program” on page 65. For more information about the steps you just completed, see “Discussion: Testing the Data Model,” which follows.