主要内容

Write Test Fixtures Using Polyspace Test C++ API

You can group multiple tests written with the Polyspace® Test™ xUnit API in test suites. See Group C/C++ Tests into Suites with Common Setup and Teardown Code. If your tests use the same setup and cleanup code, you can use the Polyspace Test C++ API to create a test fixture to share the setup and cleanup code between the tests. You can also create a suite fixture to reuse the setup and cleanup code between tests across different test suites.

For example, if you run tests on an object that is costly to instantiate, such as a large database, you can create the object once during the fixture setup, and then share the object between the different tests.

This example shows how to write a C++ test fixture class with setup and teardown methods.

Prerequisites

This topic describes test authoring using the Polyspace Test xUnit API. To compile these tests, you are required to know some file paths in advance. For your convenience, you can define environment variables to stand for the file paths, or otherwise include the file paths in your build. For more information, see Set Up C/C++ Testing and Code Profiling Using Self-Managed Builds.

Workflow

To reuse setup and cleanup code between tests across different test suites:

  1. Declare a class derived from pst_suite_fixture, for example, class suiteWithFixture : public pst_suite_fixture{}.

    The inheritance is optional. If you do not derive your class from pst_suite_fixture, you must at least provide default methods for all the methods described in the next step.

  2. Inside the class, declare objects to use in your tests and define these methods as needed. Use only these names for each method:

    • static void suite_setup(){// function body} — This method is called once before any test in the suite starts.

    • static void suite_teardown(){// function body} — This method is called once after all the tests in the suite complete.

    • void test_setup() {// function body} — This method is called before each test runs.

    • void test_teardown() {// function body} — This method is called after each test runs.

  3. Use macro PST_SUITE_WITH_FIXTURE to declare a test suite that uses the suite fixture, for example PST_SUITE_WITH_FIXTURE(testSuiteName, suiteWithFixture);.

  4. Add tests to the test suite. To access objects that you declared in the class from step 2, use a fixture access macro. For example, in this code snippet, the test testName accesses the object aContainer that was declared in class suiteWithFixture:

    PST_TEST(testSuiteName, testName){
        T myContainer = PST_SUITE_TEST_FIXTURE_PTR() -> aContainer;
        //rest of test body
    } 

When you define tests with the Polyspace Test C++ API, you do not need to explicitly register the tests. Only if you compile your tests into a static library that you later link with the file containing the main function, register your tests manually in the main function.

Example

Example Files

Find the files for this tutorial in the folder polyspaceroot\polyspace\examples\doc_pstest\cpp_example. Copy these files to a writable location and continue the tutorial. Here, polyspaceroot is the Polyspace installation folder, for example, C:\Program Files\Polyspace\R2026a.

Inspect Function Under Test and Requirements

The example file Ranks.cpp defines three methods of class Ranks:

  • Ranks::addToList(const std::string& name, const size_t& score), which takes a name and score as an input and adds them as a pair to a players list that is sorted by scores.

  • Ranks::removeFromList(const std::string& name), which takes a name as an input and removes the corresponding pair from the players list if the name exists.

  • void Ranks::resetList(), which removes all entries from the players list.

#include "ranks.hpp"

void Ranks::addToList(const std::string& name, const size_t& score) {
    if (playersList.size()) {
        // Find first instance of smaller score and insert
        // before that instance
        auto insertPosition = std::find_if(playersList.begin(), 
                                        playersList.end(),
                                        [score](str_uint_pair i) { 
                                            return i.second < score; });
        playersList.insert(insertPosition, std::make_pair(name, score));
    } else {
        playersList.push_back(std::make_pair(name, score));
    }
}
void Ranks::removeFromList(const std::string& name) {
    auto erasePosition = std::remove_if(playersList.begin(), 
                                    playersList.end(),
                                    [name](str_uint_pair i) { 
                                        return i.first == name; });
    if (erasePosition != playersList.end()) {
        playersList.erase(erasePosition, playersList.end());
    }
}
void Ranks::resetList() {
    playersList.clear();
}

Suppose that you want to create separate test suites to test adding and removing pairs from the list. You might want to create a Ranks object with a prepopulated list that you can then reuse between the different suites.

In this case, create a class where you define test setup and teardown methods for a Ranks object.

Write Test

The following class in the file suite_test.cpp defines a suite fixture with these methods and members:

  • test_setup() — This method is called before each test runs and populates the list with 3 entries.

  • test_teardown() — This method is called after each test and removes all the entries in the list.

  • Ranks aRanks — This object instantiates the list during test setup.

To define a test suite that uses the suite fixture, use the PST_SUITE_WITH_FIXTURE macro. You can then use the PST_TEST macro to add tests to the suite, for example:

#include <pstunit.h>
#include <pstunit_main.h>
#include "ranks.hpp"

// Test using fixtures
class my_suite_with_fixture : public pst_suite_fixture {
  public:
    void test_setup() {
        aRanks.addToList("Jane Doe", 50);
        aRanks.addToList("John Smith", 60);
        aRanks.addToList("Ada Lovelace", 55);
    }
    void test_teardown() {
        aRanks.resetList();
    }
    Ranks aRanks;
    
};
//Test adding player
PST_SUITE_WITH_FIXTURE(suite_add_player, my_suite_with_fixture);

PST_TEST(suite_add_player, test_size_one_addition) {
    Ranks myRanks = PST_SUITE_TEST_FIXTURE_PTR()->aRanks;

    // Add new player
    auto expected_numPlayers = myRanks.getNumPlayers() + 1;
    std::string name = "Richard Roll";
    size_t score = 65;
    myRanks.addToList(name, score);

    PST_VERIFY_EQ_UINT(expected_numPlayers, myRanks.getNumPlayers());
}

PST_TEST(suite_add_player, test_rank_one_addition) {
    Ranks myRanks = PST_SUITE_TEST_FIXTURE_PTR()->aRanks;

    // Add new player with 2nd highest score
    auto expected_numPlayers = myRanks.getNumPlayers() + 1;
    std::string name = "John Doe";
    size_t score = 57;
    myRanks.addToList(name, score);
    
    size_t expected_rank = 2;
    PST_VERIFY_EQ_UINT(expected_rank, myRanks.getPlayerRank(name));

}
File suite_test.cpp also contains a test suite suite_remove_player (not shown here) to test the removal of entries from the list.

For each test, you access the Ranks object from the class my_suite_with_fixture by using the PST_SUITE_TEST_FIXTURE_PTR fixture access macro.

You do not need additional setup to use the prepopulated list in the test. For example:

  • The Polyspace Test C++ API automatically registers the tests. You do not need to explicitly invoke test registration macros.

  • You do not need to provide a main function.

Simply include the header file pstunit_main.h in your code. The header file is available in the include folder of your Polyspace Test installation, for example polyspaceroot/polyspace/pstest/pstunit/include. Here, polyspaceroot is the Polyspace installation folder, for example, /usr/local/Polyspace/R2026a.

Execute Test and Inspect Results

To run the tests, compile the files Ranks.cpp, suite_test.cpp, and the files shipped with Polyspace Test:

g++ -std=c++11 ranks.cpp suite_test.cpp <PSTUNIT_SOURCE> -I <PSTUNIT_INCLUDE> -o testrunner
For more information on the file paths <PSTUNIT_SOURCE> and <PSTUNIT_INCLUDE>, see Set Up C/C++ Testing and Code Profiling Using Self-Managed Builds.

Run the test executable:

testrunner.exe
(or ./testrunner in Linux®).

You see that all tests in both suites pass.

| Running tests   |            |
|-----------------|------------|
| Running         | suite      | suite_add_player | suite_test.cpp
|-----------------|------------|
| Running         | s.setup    | suite_add_player
|            PASS | s.setup    | suite_add_player
| Running         | test       | suite_add_player/test_size_one_addition
|            PASS | test       | suite_add_player/test_size_one_addition
| Running         | test       | suite_add_player/test_rank_one_addition
|            PASS | test       | suite_add_player/test_rank_one_addition
| Running         | s.teardown | suite_add_player
|            PASS | s.teardown | suite_add_player
|-----------------|------------|
|            PASS | suite      | suite_add_player
|-----------------|------------|
| Running         | suite      | suite_remove_player | suite_test.cpp
|-----------------|------------|
| Running         | s.setup    | suite_remove_player
|            PASS | s.setup    | suite_remove_player
| Running         | test       | suite_remove_player/test_size_one_delete
|            PASS | test       | suite_remove_player/test_size_one_delete
| Running         | test       | suite_remove_player/test_size_fake_delete
|            PASS | test       | suite_remove_player/test_size_fake_delete
| Running         | s.teardown | suite_remove_player
|            PASS | s.teardown | suite_remove_player
|-----------------|------------|
|            PASS | suite      | suite_remove_player

|        |  Total | Passed | Failed | Incomplete
|--------|--------|--------|--------|------------
| Suites |      2 |      2 |      0 |          0
|  Tests |      4 |      4 |      0 |          0