Parameterized Test Units

From STRIDE Wiki
Revision as of 19:00, 26 November 2014 by Ivailop (talk | contribs) (Don't Declare More Than One Public Constructor)
Jump to: navigation, search

You can leverage the tests you write in Test Units by using STRIDE's parameterization feature. This allows you to supply parameters to your test units at run time to customize test behavior.

How Parameters are Passed from the Host

Parameters to be passed to a target test unit are supplied on the stride.exe command line used to invoke the test unit. Test unit parameter values are specified between parentheses characters immediately following the test unit name on the command line.

Parameter Rules

  • String parameters are enclosed with double quotes
  • Number parameters are not quoted

Additionally, the command processor imposes these rules:

  • If the run option's value contains one or more spaces (except within a quoted string parameter), it must be enclosed within double-quotes.
    • Within these quotes, all double-quote and backslash characters must be escaped with a preceding backslash

Number Parameter

For example, assume that you have a test unit implemented on the target named MyTest and it has been coded to accept a single integer parameter. To run this test unit with the parameter value 17, use the following run specification:

 --run=MyTest(17)

String Parameter

As a second example, consider a test unit coded to take a single string as a parameter. The parameter is supplied as follows:

 --run=MyOtherTest(\"string\")

Note that the string parameter must be enclosed with double quote characters. The double quotes are escaped with a backslash as this is required by the command line processor. Here you are not required to enclose the

Parameter Containing Spaces

When the string parameter contains one or more spaces, the command processor further requires you to enclose the entire --run argument in double quotes as follows:

 --run="MyOtherTest(\"this is the parameter\")"


File Path Parameters

File paths are a special case of string parameters; they require special handling due to their path delimiters.

On Windows systems, file paths are delimited using the backslash character ('\') which must be escaped within a string parameter. A simpler approach (recommended) is to use forward-slashes to delimit any file path that is passed as a parameter. These file paths with forward-slash delimiters can be used directly by the stride file stuff on the target (even when the host system is running under Windows).

For example:

 --run="FileTest(\"c:/myData Test/testParams.ini\")"

Note that in this example the file path has a space in it, so that the entire run parameter must be enclosed in double quotes.

Multiple Parameters

You may pass any number of parameters to a test unit, and each parameter may be of any supported type. An example is shown below:

 --run="YetAnotherTest(19.58, -1, \"Encinitas\", 92024, \"south\")"

A few additional examples are shown on the STRIDE Test Runner Reference page.

Note that the command line processor requires you to enclose with double quotes if there are any spaces at all in the --run argument. This can be counter-intuitive when passing only number types.

No space in argument list
 --run=TestX(123,555,3.14)
Space in argument list
 --run="TestX(123, 555, 3.14)"

How Parameters are Received on the Target

Parameters are passed from the stride.exe command line and are presented on the target as follows:

C++ test unit
Parameters are presented as constructor arguments to the test unit class
C-Class test unit
Parameters are presented as arguments to the Initialization function.
Flist test unit
Not supported

C++ Test Unit

When using parameters with a C++ test unit, you declare and implement a public constructor that takes the number and type of arguments you want to receive.

Important: Do Not declare or implement a default constructor (constructor taking no arguments) in a class that gets parameters.

MyTest.h
#include <srtest.h>

class MyTest : public stride:srTest
{
public;
    MyTest(int nParam, const char* szParam)
    {
        // typically, you will store parameter values into member variables for use by
        // the member test methods
        m_nParam = nParam;
        strncpy(m_szParam, szParam, MAX_LEN);
    }

    // Tests
    void Test1();
    ...
    ...
    ...
private:
    int m_nParam;
    char m_szParam[MAX_LEN];
};

#ifdef _SCL
#pragma scl_test_class(MyTest)
#endif

C-Class Test Unit

MyTest.h
#include <srtest.h>
  
typdef struct MyTest
{
    int m_nParam;
    char m_szParam[MAX_LEN];

    void (*Test1)(struct MyTest* self);
    ...
    ...
    ...
} MyTest;

void MyTest_Init(MyTest* self, int nParam, const char* szParam);

#ifdef _SCL
#pragma scl_test_cclass(MyTest, MyTest_Init)
#endif
MyTest.c
#include  "MyTest.h"
 
static void Test1(MyTest* self)
{
    ...
    ...
    ...
}

/* other test implementations */
...
...
...


void MyTest_Init(MyTest* self, int nParam, const char* szParam)
{
    self->m_nParam = nParam;
    strncpy(self->m_szParam, szParam, MAX_LEN);

    self->Test1 = Test1;
    ...
    ...
    ...
}

Supported Parameter Types

ANSI strings and number parameter types are directly supported.

String types
  • char*
  • const char*
Number types
  • all integer types
  • float
  • double

Constructor Rules for Parameterized C++ Test Classes

Because STRIDE test classes are instantiated dynamically by the STRIDE runtime, the rules for construction are subtly different from garden-variety C++ classes.

Don't Declare More Than One Public Constructor

You must declare only one public constructor in a test class.

This is typically not a problem for non-parameterized test classes, as a common default constructor is what's needed. Often you will begin with a non-parameterized test class and add the parameterization later as your testing gets more sophisticated. A common mistake here is to add a second public parameterized constructor to the class in addition to the original default constructor; this is perfectly legal in C++, but will not give you the desired result.

You must ensure that you have only one public class constructor.

Example

// MyTest.h

#include <srtest.h>

class MyTest : public stride::srTest
{
public:
    // Don't do this!
    MyTest();  // WRONG! DON'T DECLARE DEFAULT (NULLARY) CONSTRUCTOR!

    // Do only this
    MyTest(int param1, const char* param2);  // CORRECT: Declare only your paramterized constructor

   ...
};

Default Constructor Parameters Are Not Used

Default constructor parameters are a very common and useful pattern in C++ programming. Where declared, default parameter values are inserted where appropriate by the C++ compiler at build-time (static polymorphism).

In the case of STRIDE, each test class is instantiated dynamically on demand by the harnessing code. The harnessing code does not know at build time whether constructor parameters will be omitted when the class is ultimately instantiated. (Recall that constructor parameters are specified on the stride.exe command line on the host computer.)

Therefore, STRIDE imposes the following rules:

  • Test class constructor arguments may be omitted
    • Only trailing arguments may be omitted; they must be the last argument(s)
    • The test class declaration does not need to specify default arguments for the above to work. (In fact the default arguments won't be used!)
  • Any arguments that are omitted will be supplied to the constructor with the value 0 (or NULL for char pointer types).

Example

// MyTest.h

#include <srtest.h>

class MyTest : public stride::srTest
{
public:
    // Default parameter values WILL NOT be used!
    // If a parameter is not specified on stride command line,
    // its value is always passed to the constructor as 0
    MyTest(int param1 = 100, int param2 = 200);  // WRONG! DON'T DO THIS!
    ...
};

A Pattern To Implement Non-Zero Default Construction Parameters

// MyTest.h

#include <srtest.h>

#define MAX_STRING 256

class MyTest : public stride::srTest
{
public:
    MyTest(int nParam, float fParam, const char* szParam);
    ...

private:
    int m_nParam;
    float m_fParam;
    char m_szParam[MAX_STRING];
};


// MyTest.cpp

static const int nDEFAULT_NPARAM = 100;
static const float fDEFAULT_FPARAM = 3.1415;
static const char szDEFAULT_SZPARAM[] = "This is the default value";

MyTest::MyTest(int nParam, float fParam, const char* szParam)
{

    m_nParam = nDEFAULT_NPARAM;
    if (nParam)
    {
        m_nParam = nParam;
    }

    m_fParam = fDEFAULT_FPARAM;
    if (fParam)
    {
        m_fParam = nParam;
    }

    memset(m_szParam, 0, MAX_STRING);
    strncpy(m_szParam, szDEFAULT_SZPARAM, MAX_STRING - 1);
    if (szParam[0])
    {
        strncpy(m_szParam, szParam, MAX_STRING - 1);
    }
}

Additional Tips When Using Parameters

Setting the Test Unit Name Dynamically

The purpose of parameterizing a test unit is to provide additional test coverage using a single test unit implementation. In a single stride.exe invocation you will typically run the same test unit many times, passing a different set of parameters for each test.

Note: Option files are a necessity, since the stride.exe command line gets unwieldy in these situations. Option files give you repeatibility and also eliminate a lot of typing!

Test Units With Many Parameters


Notes