By
Reg. CharneyI am defining a unit as a program or function that uses some objects to do a task. These objects are non-trivial and require preparation in terms of setting up their constructor arguments before they can be instantiated. Further, we know that individual classes have been tested and we know they work. What we are trying to do is put them all together into meaningful whole. We are also trying to avoid embedding the unit into a massive system. There are several reasons for doing this, including the main system does not work yet, or it takes up a lot of resources, etc. We also want the testing to be as quick and simple as possible while giving meaningful results.
As part of our music site, our unit test logs in users, loads their preferences and extracts matching music from a database. The objects come from an authentication class, a user class, a preference class, and a database class.
These classes have different characteristics. If the Database can not be found or accessed, the unit fails catastrophically (I.e., an assert fails.) Similarly for the Auth class. If no preferences can be found for a user, a default Pref instance is acceptable. If a User fails, only an indicator is needed because the user can try up to n times, depending on parameter.
In pseudo-code, here is the unit:
|
string dbname, uid; cout << "Enter db name:"; cin >> dbname; Database db(dbname);
cout << "Enter
your id:"; Selection s(db, p); |
In this example, Database db needs a string for its name/path. Auth a needs a string, Preference p needs a user, and Selection s needs both a database object and a preference set. There is a lot of setup needed to test the viability of selection s. Wouldn’t it be simpler to write something like this that would do the same thing?
|
TDatabase db; TAuth a; TPreference p(a); TSelection s(db, p); |
The sample above shows what you can do with test classes. These are classes that you design when you design the real classes. Notice that the default test class constructors do what you want and expect. This article discusses how you can do this.
There are two techniques used to construct an object: the normal constructor method; and the static create method. The normal constructor method usually appears in one of two ways:
|
class X { /* . . . */ }; X x(a1, a2); X px = new X(a1, a2, a3); |
The number of constructor arguments vary by class and application use. With this method, failure to construct an object can only be detected by: (i) throwing an exception; (ii) terminating the process; or (iii) using a status variable. (Note: failure may be independent of the allocation of object storage.)
The second technique uses a static create function for the class.
|
class X { public: static X* create(int a) { return a ? new X : 0; } // . . . private: X() { cout << "In X\n"; } }; // . . . A a; X px = X::create(a); |
An instance of the class X is created only under some precondition, like when a evaluates to non-zero . Otherwise, the create function returns null.
We use derivation to create test classes because they satisfy the “is-a” criteria. Any derivative is an instance of a base class. Thus, anything that you do to a base class instance, you can also do to a derived class instance. For example,
|
class B { /* . . . */ }; class TB : public B { }; void foo(B& b); void bar(B* pb); B b, bp = &b; TB tb, ptb = &tb; foo(tb); bar(ptb); |
Both foo() and bar() work as well with a TB as with a B. Thus, we can use a TB as easily as a B. This is the basis of our testing technique. This article shows how we can easily create derived test classes from normal class.
Going back to the music example, we can see that a Pref class can return a default preference object if the user has no preferences.
|
class Pref { public: typedef list<Music*> LM; Pref() { /* . . . */ }; Pref(Auth& a) { lm = rdPref(a.path); } private: LM lm; LM rdPref(const string &s); }; |
This is the simplest case for creating a test class. In fact, a test class is not really needed—we can just use the actual class. However, a normal default preference may not be very interesting. For example, it may be the same as no preference, which means that it looks like it does not even exist. Thus, tests based on such a preference would not be very useful. Better would be a dummy set of preferences that would be non-empty and valid.
In this simple case, Step 1 would be to introduce a protected constructor that does nothing.. It needs a signature that distinguishes it from any other constructor. Step 2 would be to define a derived class that loads a sample preference list.
|
class Pref { public: typedef list<Music*> LM; Pref() { /* . . . */ }; Pref(Auth& a) { lm = rdPref(a.pathm); } private: LM lm; LM rdPref(const string &s); protected: // test only Pref(int) { /* empty */ } }; #include <new> class TPref : public Pref { public: TPref() : Pref(0) { Auth a("validAuth"); ((Pref*)this)->~Pref(); new(this) Pref(a); } }; |
Here, TPref invokes the constructor that does nothing, defines an authorized user using a known valid test user, destroys the empty object part of itself, and then creates a valid Pref instance on top of itself. Now we can use TPref anywhere that we could use Pref — even the address has remainder the same. Please note the #include <new>. It is needed for the placement new.
Recall that the Auth class needs to get a valid user. If an invalid user is entered, another n-1 tries are made. Thus, we want a way of signally that the given string does not represent a valid User. In such a case, the static create technique can come in handy.
|
class User { public: static User* create(string& s) { bool b = valid(s); return b?new User(s):0; } void doSomething(); private: User(string& s) { uid=s; } bool valid(string s); string uid; }; cout << "Enter id:"; cin >> s; User *pU = User::create(s); if (pU) { // valid user pU->doSomething(); } |
Since the test class should support the same constructor interface as the base class, we need a static class create function. We are also going to use the placement new trick that we used earlier, only its going to be a little more complicated—but just a little. As before, we added a do-nothing base class constructor different from any potential default base constructor.
|
class User { public: static User* create(string& s) { bool b = valid(s); return b?new User(s):0; } void doSomething(); private: User(string& s) { uid=s; } static bool valid(char*s); string uid; protected: // test only User(int) { /* empty */ } }; #include <new> |
This is simple and works fine. However, it does not support a default constructor for the test class. First, notice that class User already has a constructor, but its private. To make the User constructors we need available to the test class TUser, the easiest solution is to declare TUser a friend class. This makes them available to any derived test class. Also, lets make all validation functions static. (Note, we have already separated data validation from the allocation of the memory in the create function.)
|
class User { friend class TUser; public: static User* create(string& s) { bool b = valid(s); return b?new User(s):0; } void doSomething(); private: string uid; User(string& s) { uid=s; } static bool valid(char*s); User(int) { /* empty */ } }; #include <new> class TUser : public User { public: // Assume Ed always valid TUser(char* s=0):User(0) { string ss; ss = (s) ? s : ""; if (ss == "") ss = "Ed"; ((User*)this)->~User(); assert(valid(ss)); new(this) User(ss); } static TUser* create(string& s) { return (TUser*)User::create(s); } }; TUser tu; // valid user Ed tu.doSomething();
cout << "Enter
id:"; |
Note the default TUser constructor invokes the do-nothing User constructor explicitly. The empty User that is initially constructed as part of TUser can also be destroyed. Since this part of the code is a constructor, if valid() returns a false, the assertion fails. Else, we can construct the new instance on top of ourselves. Again, remember to #include <new> to use the placement new operator. I can also safely cast up from a User to a TUser since TUser has no member data.
To summarize this technique, I defined a derived test class with a default constructor and a public static create function. I also made the test class a friend of the base class. As part of the test class, a valid user value was inserted if no user id was supplied. As a result of all this is that the test class can be used wherever the original base class can be used. It can also be used with a default constructor.
The music example was inspired by my neighbor Marietta and her kids, Kelly and Robert, and a delightful Sunday Peninsula Youth Orchestra concert. ( www.peninsulasym.org ) Thanks.
By Reg. Charney
Early this month I attended the C++ Standards Working Group (WG21) in Copenhagen. It was held at the Danish Standards (DS) locale on the outskirts of the city. In getting to the meeting place by train, two things were noticeable: the trains ran on time and the ticketing system they used was very neat. Ask if you are interested.
I attended the Library Working Group for the week, although I normally attend the Core group. Most of the recent “exciting work” has been done in the Library Working Group (lwg) and I wanted to get a better feel for what has been happening there.
In summary three things have come out of this meeting:
1) We are within one meeting of issuing the next Technical Report on the C++ Standard containing corrections and clarifications. As voting members of WG21, we will have access to the Working Draft of the TR that should be ready within 60 days.
2) Bjarne gave a presentation on his vision of the Future Directions of C++. Bjarne may have posted his slides on his web site by the time you read this.
3) Based on a number of Bjarne's comments and other issues that have come up over the last couple of years, we are going to resurrect the Extensions Working Group to consider things like his eXtensions Language Interface (XLI).
By Reg. Charney
This is another month of declining job openings. There was a 15.8% decline in Silicon Valley IT job openings since last month. Nationwide, the decline was 16.9%. The only positive trends were in the Windows 2000 arena.


From the charts above, you can see that the growth in demand for Windows 2000 skills are increasing at an accelerating rate. The most dramatic falloff is in the Linux space. While demand is still strong for Linux, the continued demand is falling off. It is my contention that Linux is a “early adopter” phenomena. As such, people take a chance on things like Linux while times are good and retreat from chancy ventures when things are tough.
Since last month the number of projects has increate 5.8% and the number of users has increased by 9.2%. The most popular projects downloaded this month are the CDex project, a CD-Ripper that extracts digital audio data from an Audio CD. The GPL Windows application supports many Audio encoders, like MPEG (MP2,MP3), VQF, AAC encoders. The second most popular download is MiKTeX, an up-to-date implementation of TeX & Friends for Windows (all current variants). The third most popular project is MyNapster, a Win32 client using Gnutella and IRC for chat. It is based on Gnucleus and utilizes MFC (works with WINE).
Note that these projects are meant to run under Windows, not Linux.
By project type, the projects on SourceForge are:
| Communications | 2564 projects |
| Database | 984 projects |
| Desktop Environ | 678 projects |
| Education | 465 projects |
| Games/Entertainment | 2339 projects |
| Internet | 3675 projects |
| Multimedia | 1998 projects |
| Office/Business | 683 projects |
| Other/Non-listed | 461 projects |
| Printing | 77 projects |
| Religion | 48 projects |
| Scientific/Engineering | 1220 projects |
| Security | 406 projects |
| Sociology | 27 projects |
| Software Development | 2566 projects |
| System | 3068 projects |
| Terminals | 106 projects |
| Text Editors | 386 projects |
As an aside, the story I heard about CDex is that they wrote to the RIAA (Record Industry Association of America) asking what CDs they could index and the RIAA replied by suing them. Ah, thank God for lawyers.
In the last month’s article, I changed the fourth example to say: ^[^\s]*
Jesse corrected me. It should have been: ^[^ ]+. He said, “I did not use the '\s' because that is not in all regex engines. It should be '+' rather than '*' because we want a positive grab.