CUT DOCUMENTATION RELEASE 2.4 Samuel A. Falvo II OSI Certified Open Source Software -------- 1. What is CUT 2.4? CUT 2.4 is intended to provide a framework with which you write "unit tests", or as they're now called in the extreme programming community, "programmer tests". Unlike other unit test packages available for C or C++, CUT doesn't strive to be an SUnit clone. Instead, it starts from first principles, solving the problem of establishing a flexible, accurate, and easy to use unit test environment for a statically compiled language that doesn't support run-time reflection. It does this by pre-parsing a set of test modules that you write, and then automatically builds a custom test runner to invoke them. You are then responsible for compiling the test runner, linking, and executing it. See section 3 for how this is typically configured in project Makefiles. 1.1 What is a test runner? A test runner is a stand-alone program that must satisfy the following three goals: 1. It must locate the unit tests that the programmer has written. This can be done automatically (e.g., via reflection or "a priori" knowledge) or manually (e.g., the user is responsible for invoking some function in the program to register test suites with it). 2. It must execute the tests in a predefined, repeatable order. 3. It must report, at a minimum, success or failure. In the event of a failure, a diagnostic message must be displayed to give the programmer enough context to locate the error. Other C/C++ unit test packages already have a test runner created for you, and you are responsible for registering your tests with it. This is a very unnatural, labor-intensive, and error-prone method of satisfying the first condition. In contrast, CUT relies on source code pre-parsing to build a list of what functions exist, and when to call them, based on the order of the test modules given on the command-line, and in the order they're found in the test module. Because this is all done automatically, it is much more accurate, and it's faster. The test runner also consumes fewer system resources, which is a critical consideration for embedded systems development. 1.2 What is cutgen? Because CUT in and of itself isn't a test runner, it has a tool called cutgen that is responsible for generating the runner for you, based on what it sees in your test modules. cutgen also performs some BASIC error detection, but for the most part, it assumes you know what you are doing. In the event that you make a mistake, no worries -- the worse that can happen is you'll get compiler errors. 2. Invoking cutgen cutgen takes as input a series of source files, and produces another C source program as its output. The output can either be stored to a file, or be emitted to standard output. cutgen -o output-file input-file [input-file [...]] cutgen input-file [input-file [...]] > output-file Note that the latter method is included only as a concession for CUT 2.0 compatibility, and is not recommended syntax for new projects. Since you'll likely be needing to update your Makefiles to remove the references to python (which CUT 2.0 and earlier required), you probably should move towards the -o method, since all future CUT versions will support that option. Using CUT in file redirection mode is deprecated, and will be removed starting with CUT 3.0. 3. Generally Accepted Makefile Conventions Most projects that utilize CUT for unit testing will want a regimented project layout. The basic components of a project will consist of three things: a. The application's core logic, called the application library, b. The shipped production code -- this is what your customers will receive when they acquire your software, and finally, c. The test runner code -- this is what CUT is responsible for producing. To facilitate the automatic and error-free generation of these products, you'll probably want to use a project build system, such as Make or Ant. I'll use Make in this section, because that's what I'm most familiar with. However, CUT should be relatively easy to integrate into most other project building tools. 3.1 Application Library The Application Library is core business logic of the application. It typically does not have any inherent dependency on user interface, since automated unit tests will rarely invoke functionality directly via the user interface channels. The application library need not be built as a dynamically linked library, though in my experience, it is most convenient if it is. 3.2 Production Code Usually, the production code target is the top-most item in the build system, so as to make it the default production when building the software. It will, by definition, rely largely on the application library, and will provide the binding between the application library and the intended user interface. The simplest example of a production code implementation would be as follows: int main( int argc, char *argv[] ) { Application *app = NewApplication( argc, argv ); return( AppMain( app ) ); } Here, NewApplication() and AppMain() would be defined in your application library. Placing them in your application library does two basic things. It splits the initialization and execution of your program apart, so that each can be independently unit tested. For example, a whole battery of unit tests can be used against NewApplication() to verify the global effects of command-line parameters are within expectations, without actually having to invoke the core functionality of the software every time. Note also that this kind of modularity is convenient to have for another important reason: it enables multiple instances of the program to be running without having to reload the application, basically for free. Thus, if you're working on a word processor or spreadsheet, for example, each document can be represented by its own "Application" object. The application's logic doesn't have to explicitly handle the case of multiple documents within a single application instance. Therefore, the whole Microsoft Windows concept of MDI goes away (except perhaps as a user interface issue), thus making the entire logic of the program simpler, and substantially easier to test. A typical main function for this type of program would utilize a dynamic list of application objects, and wouldn't quit until the list was empty. 3.3 Test Runner The test runner is generated dynamically using the cutgen tool. The Makefile production to generate this is most often called "check", and the traditional name for the test runner is "cutcheck". Most Makefiles also have a production or macro called TESTS which lists the source files of the unit tests in the project. 3.4 Sample Makefile MODULES = keyboard window mouse tables OBJS = $(MODULES:%=%.o) TESTS = $(MODULES:%=test_%.c) LIBS = ...etc... .c.o: $(CC) $(CCOPTS) $(DEBUG) -o $@ $< # help (DEFAULT PRODUCTION) help: echo "Type 'make application' to build the application." echo "Type 'make check' to run unit/programmer tests." # Production code generation application: $(OBJS) main.o $(LD) $(LDOPTS) $(MODULES:%=%.o) main.o $(LIBS) -o TableView # Unit Test Production check: $(OBJS) $(TESTS) cutcheck.c cutgen -o cutcheck.c $(TESTS) $(CC) $(CCOPTS) $(DEBUG) -o cutcheck cutcheck.c $(OBJS) $(TESTS) $(LIBS) ./cutcheck 4. What Does a Test Module Look Like? Eventually, a more realistic example will appear here. For the time being, however, the following (contrived) example will show you the basic methods of using bring-ups, tear-downs, and test functions. #include "cut.h" #include void __CUT_BRINGUP__Explode( void ) { printf( "__CUT_BRINGUP__Explode() called!\n" ); ASSERT( 2 == 2, "Making sure environment is correct." ); } void __CUT__TestA( void ) { ASSERT( 1 == 1, "One should always be equal to one." ); } void __CUT__TestB( void ) { ASSERT( 1 != 0, "One is never zero." ); } void __CUT__TestC( void ) { ASSERT( 1 > 2, "One can never be greater than two." ); } void __CUT_TAKEDOWN__Explode( void ) { printf( "__CUT_TAKEDOWN__Explode() called!\n" ); }