ColdFrame: Stubs



Most systems are composed of numerous domains, some of which provide resources and capabilities to others. In the diagram, the application-level House Management domain uses the services of the device-level Digital IO domain.

While testing House Management, it's a Bad Idea to use the real Digital IO, for various reasons:

What is needed is a version of Digital IO which provides the interfaces of the real thing but with an alternative implementation, more amenable to testing.

Separating out the interface

The first thing to do is, within Digital IO, to make a child package, Digital IO Interface, stereotyped «domain-interface».

In this package put all the «public» classes and «callback» types, any «type» types used by them, and any «exception»s.

Complete package
Interface package

In the Interface package, include the tag {name = Digital IO}. This means that when generating code from the Digital IO Interface, it will be generated as if it were the package Digital IO (though the code will still be placed in the directory derived from the actual package name, Digital_IO_Interface.gen/).

Implementing stub functionality

If you want, you can provide implementations in the parallel Digital_IO_Interface.impl/ directory, as normal. You'll want to provide some means of storing in parameters and providing values for out parameters and function results; you'll want to provide queries to find out what values were passed in. Remember that you can write package specs in the .impl directory, if needed.

You should generate Digital_IO_Interface.gen/ in your test directory. The stubs you want probably won't be the same as other developers also testing code that uses Digital IO.

Generating stub functionality

Normally, ColdFrame generates the separate bodies of operations to raise Unimplemented, and you get to fill in the real code.

If you generate with GENERATE_STUBS set to yes

$ make Digital_IO_Interface.gen GENERATE_STUBS=yes

the separate bodies will contain stub code which

  1. stores in or in out parameters' values for retrieval
  2. raises an exception, if specified
  3. sets out or in out parameters or a function's return value as specified.

The stub code uses ColdFrame.Stubs.

No stub code is generated for protected types or tasks, but since the intention of the stub support is to implement domain interfaces this should not be a problem.

Using generated stub functionality

Test Case support

Your test case Set_Up must call ColdFrame.Stubs.Set_Up, and your Tear_Down must call ColdFrame.Stubs.Tear_Down.

If the stubbed code has output parameters or returns values of type T, instantiate ColdFrame.Stubs.Set_Output_Value with T.

--  Specify an output from a call to a stubbed operation for the
--  type T.
--
--  For_Subprogram_Named is the case-insensitive fully-qualified
--  name of the subprogram (eg, if dealing with procedure
--  Domain.Class.Operation, "Domain.Class.Operation").
--
--  Normally the named parameter will be an "out" (perhaps "in
--  out") parameter. The specified "To" value will be returned on
--  the "For_Call"th call, and all subsequent calls until another
--  "Set_Output_Value" call for the same parameter; if you want to
--  have the first 4 calls to Domain.Class.Operation to set Output
--  to 4, and any later ones to set it to 42, you'd say
--
--     Set_Integer_Output_Value ("Domain.Class.Operation", "Output", 4, 1);
--     Set_Integer_Output_Value ("Domain.Class.Operation", "Output", 42, 5);
--
--  A special parameter name is "return". For "return", the To
--  value will be the function result.
--
--  A previously stored value for a particular call can only be
--  overridden if Override is True (when it must be).
--
--  Overhead_Bytes is the additional space reserved for the
--  streamed representation.
generic
   type T (<>) is private;
procedure Set_Output_Value (For_Subprogram_Named : String;
                            For_Parameter_Named : String;
                            To : T;
                            For_Call : Positive := 1;
                            Override : Boolean := False;
                            Overhead_Bytes : Natural := Storage_Overhead);

If the stubbed code has input parameters of type T and you want to check that your code has supplied the right values, instantiate ColdFrame.Stubs.Get_Input_Value with T.

--  Retrieve values passed to stubbed operations for the type T.
--
--  For_Subprogram_Named is the case-insensitive fully-qualified
--  name of the subprogram (eg, if dealing with procedure
--  Domain.Class.Operation, "Domain.Class.Operation").
--
--  The named parameter will be an "in" (perhaps "in out")
--  parameter. To retrieve the result of the second call, you'd say
--
--     Result := Get_Integer_Operation_Input_Value
--       ("Domain.Class.Operation", "Input", 2);
--
--  To retrieve the result of the last call, say
--
--     Result := Get_Integer
--       ("Domain.Class.Operation", "Input", 0);
--
--  To retrieve the value passed at the last call but one, say
--
--     Result := Get_Integer
--       ("Domain.Class.Operation", "Input", -1);
generic
   type T (<>) is private;
function Get_Input_Value (For_Subprogram_Named : String;
                          For_Parameter_Named : String;
                          For_Call : Integer := 0) return T;

Test Procedure support

Call your instantiations of Set_Output_Value to set up the results of calls to stubbed subprograms. Often, and particularly for "environmental" operations, this will be in the AUnit test case's Set_Up procedure; in that case, you might want to use the Override parameter to alter your standard setup for a specific test procedure.

Call ColdFrame.Stubs.Set_Exception if you want a stubbed subprogram to raise an exception (to get an Exception_Id, use exception-name'Identity).

--  Specify that a call to a stubbed operation is to raise an
--  exception.
--
--  For_Subprogram_Named is the case-insensitive fully-qualified
--  name of the subprogram (eg, if dealing with procedure
--  Domain.Class.Operation, "Domain.Class.Operation").
--
--  Normally the exception will be raised for the specified call
--  and all later calls; to stop this, use Ada.Exceptions.Null_Id.
--
--  A previously stored exception for a particular call can only be
--  overridden if Override is True (when it must be).
procedure Set_Exception (For_Subprogram_Named : String;
                         E : Ada.Exceptions.Exception_Id;
                         For_Call : Positive := 1;
                         Override : Boolean := False);

Call your code as required.

Call Number_Of_Calls to find out how often a subprogram has been called.

--  Retrieve the number of calls made to the named subprogram.
function Number_Of_Calls (For_Subprogram_Named : String) return Natural;

In some cases, you may find it convenient to check the number of new calls since a certain point in the test suite; Save_Number_Of_Calls and Number_Of_New_Calls support this.

--  Saves the count of calls made to the named subprogram.
procedure Save_Number_Of_Calls (For_Subprogram_Named : String);

--  Retrieve the number of calls made to the named subprogram since
--  saved (if saved; if not, since program start)
function Number_Of_New_Calls (For_Subprogram_Named : String) return Natural;

Call your instantiations of Get_Input_Value to retrieve any parameters input to stubbed subprograms.