The key thing about this port is that it has very high "allegiance." Whenever there was choice, I leaned towards doing it the way that it was done in the Java version. No doubt there are more idiomatic ways. The write up on the code generation approach that you and Paul have on the wiki is very intriguing. I suspect that the real answer over time may be something like that, more idiomatic.
The directory structure I sent you and Paul has the following:
1. Take a look at Parse.h and Parse.cpp in the Core directory. Parse has very high allegiance. In most cases the code is exactly the same. I added some unit tests as I went to make sure that I was on the right path. The main deviation is the use of a template I named HurlingPointer. It throws an exception when someone attempts to dereference a null Parse pointer. Most C++ compilers will give you that behavior, but the exception it throws varies. I've typedef'ed the name ParsePtr as a HurlingPointer wrapper on Parse pointers. Fixture uses ParsePtrs so that it can catch and display any dereference errors in the cells they are detected in.
2. Fixture has very high allegiance also. There are only two deviations. Each fixture now holds onto a collection of TypeAdapters that provide access to fields and methods on the fixture. The method adapterFor in Fixture is used frequently to get ahold of type adapters when fixtures are run. The PUBLISH macro in Fixture.h is used by fixtures to provide access to their parts. If you want to see this macro in use take a look at TestFixture.h in the Core directory. In general, I suspect that the macro will be used primarily in the constructors of Fixture classes.
The other deviation is the addition of a FixtureMaker class. In Java you can go to the Class class and create a class given a name. In this port, you create a FixtureMaker and pass it to the root fixture. It gets passed to all of the fixtures that it creates. It could have been a global.
3. TypeAdapter is a little weird. It is an abstract class in this port. The primary subclass that is created is MemberTypeAdapter. MemberTypeAdapter is a template that uses the type of the fixture class and the type (or return type) of a field/method as the other parameter. This issue, that we need a subclass here that varies with the type of the class we are reflecting on, is the source of most deviates from the JUnit scheme. The reason we need the subclass is because I am using pointers to member functions and pointers to members. In C++, you need a pointer whose static type is of the type of the class to using a pointer to a member function of that class. To see the pointers to members in use, take a look at 'set' and 'invoke' in MemberTypeAdapter.
One we get inside MemberTypeAdapter, things are a little more straightforward. I use the stream operators to polymorphically move back and forth between values to strings. Take a look at valueAsString and 'set' in MemberTypeAdapter.
This scheme, dividing the Java TypeAdapter's role between TypeAdapter and MemberTypeAdapter, is the reason why TypeAdapters are served up by Fixtures. Since we need not just a pointer to a class, but a statically typed pointer to an object of that class for a type adapter to be valid it makes sense for the Fixture to create and manage its typeadapters.
4. ColumnFixture and RowFixture are pretty much the same as the Java versions'. The only difference is that RowFixture has a utility class called RowMap that it uses to make the code a little clearer. The maps in C++'s STL library often make code hard to read. The only substantial deviation is the use of the getTargetClass method from ColumnFixture. C++ doesn't have anything equivalent to a class object, so I've overridden getTargetClass to return an "example" object derived from Fixture. Right now, all callers of getTargetClass immediately call adapterFor on the return value. I'm thinking I should rename getTargetClass to something more appropriate.
One thing occurs to me. If 'bind' in ColumnFixture only recorded string names of the columnheadings rather than type adapters, fixtures could lookup typeadapters on demand. The only difference would be each cell would show an exception if a name couldn't be found.
5. ActionFixtures are pretty straightforward. The key difference is that another macro is used, PUBLISH_COMMAND. This macro is in the CommandAdapter.h file. It is different from PUBLISH in that it creates a TypeAdapter for a method that does not have a return value.
Right now, the port has been run under Microsoft Visual C++. Brian Button is working on packaging it up for Linux. The dynamic lookup I have for fixtures loads DLLs. To see an example of it, look at the DynamicMaker class in the MSDriver directory. The way it interprets the syntax of a fixture name from a table is kind of interesting. If the name in a table does not contain a dot, it is taken as the name of a DLL. The DLL can provide a default fixture when it is loaded. If there is a dot, the part of the name before the dot is the name of the DLL and the part after the dot is the name of the fixture from that DLL.
|Last edited April 28, 2005
Return to WelcomeVisitors