#define _XOPEN_SOURCE_EXTENDED #include "test.h" #include #include #include #include #include namespace Test { namespace { using std::endl; using std::string; using std::vector; } // End namespace // Test lifecycle // suiteSetupFn(); - This is called to allocate any suite level resources. This is called once when the suite begins. // These functions may be called in parallel but execution will not proceed past this block until they have all // finished. // testSetupFn(); - This is called once for every test in tests. You may use it to allocate resources or setup mocks, // stubs, and spies. testFn(...); - This is called once for every test to execute the test. Only one of these test // functions will actually be run for each test in tests. They should return true if the test passed, return false if // the test failed or there was an error, and be nullptr if they should be skipped. The executed function will be // called with expectedOutput and the result of testFn(...). They can be used to test functions with side effects, // especially void functions. maybe_compare_function; - This is the highest priority compare function. If it is not // nullptr then it will be called. suite_compare_function; - This is the second highest priority compare function. If // maybe_compare_function is nullptr and this is not nullptr then it will be called. // [](TResult expected, TResult actual) { return expected, actual; } - This is the lowest priority compare function. // If all other compare functions are nullptr then this will be called to evaluate the test. testTeardownFn(); - This // is called once for every test in tests. You must free/release any resources allocated by testSetupFn. // This ends the parallel test functions section all tests will have completed before execution proceeds. // Collect reports - Ths step is not visible to the user at this point, but data returned by all of the test functions // is collected here. This is where you will eventually be able to format/log data for reports. suiteTeardownFn(); - // This is called after all test calls have completed, all testTeardownFn calls have completed, and all test // reports/logs have been written. You should free any resources allocated in suiteSetupFn. // TODO: Add TShared(*)(string /*test_name*/, UUID /*testRunId*/) allocateSharedData to the test tuple to make some // shared data that can be used in a thread safe way by setup, teardown, and evaluate steps of the test. // TODO: Add TShared to be returned by the setup functions, and consumed by the evaluate and teardown functions. // Suite setup/teardown functions should allocate/free. // Test setup/teardown functions should consume the data allocated by Suite setup. // Test setup functions may allocate additional resources. If they do then the allocated resources they should be // freed by test teardown function. Suite and/or Test compare functions may consume this shared data, but it will not // be shared with the execution of testFn. // This function is called to execute a test suite. You provide it with some configuration info, optional utility // callback functions, and test data (input parameters for each call to testFn and the expected result). It returns a // TestResults that should be treated as an opaque data type. Not all parameters are named in code, but they are named // and explained in the comments and will be described by those names below. // string suite_label - This is the name of this test suite. It is used for reporting messages. // FnToTest testFn - This is the function to test. This may be replaced if necessary by function. It may not currently // support class methods, but that is planned. vector> tests - This is the test run data. Each tuple in the // vector is a single test run. It's members are explained below. // string test_name - This is the name of this test. It is used for reporting messages. // TResult expectedOutput - This is the expected result of executing this test. // bool(*)(const TResult expected, const TResult actual) maybe_compare_function - This is optional. If unset or set // to nullptr it is skipped. If set to a function it is called to evaluate the test results. It takes the expected // and actual results as parameters and should return true if the test passed and false otherwise. This may be // changed to return a TestResults at some point. void(*)(TInputParams...) testSetupFn - This is optional. If unset // or set to nullptr it is skipped. If set to a function it is called before each test to setup the environment for // the test. You may use it to allocate resources and setup mocks, stubs, and spies. void(*)(TInputParams...) // testTeardownFn - This is optiona. If unset or set to nullptr it is skipped. If set to a function it is called // after each test to cleanup the environment after the test. You should free resources allocated by testSetupFn. // bool isEnabled - This is optional. If unset or set to true the test is run. If set to false this test is skipped. // If skipped it will be reported as a skipped/disabled test. // bool(*)(const TResult expected, const TResult actual) suite_compare_function - This is optional. If unset or set to // nullptr it is skipped. If set to a function and maybe_compare_function is not called for a test run then this // function is called to evaluate the test results. It takes the expected and actual results as parameters and should // return true if the test passed and false otherwise. This may be changed to return a TestResults at some point. // void(*)() suiteSetupFn - This is optional. If unset or set to nullptr it is skipped. If set to a function it is // called before starting this test suite to setup the environment. You may use it to allocate resources and setup // mocks, stubs, and spies. void(*)() suiteTeardownFn - This is optional. If unset or set to nullptr it is skipped. If // set to a function it is called after all tests in this suite have finished and all reporting has finished. You // should free resources allocated by suiteSetupFn. // This method should be called like so. This is the minimal call and omits all of the optional params. This is the most // common usage. You should put one tuple of inputs and expected output for each test case. // results = collect_and_report_TestResultstest_fn( // "Test: functionUnderTest", // functionUnderTest, // vector({ // make_tuple( // "ShouldReturnAppleForGroupId_1_and_ItemId_2", // string("Apple"), // make_tuple(1,2), // ), // }), // ); // The suites can be run from one file as such. From a file called ThingDoer_test.cpp to test the class/methods // ThingDoer declared in ThingDoer.cpp. This isn't mandatory but is a best practice. You can use testFn without calling // collect_and_report_TestResults() and also could call it from a normal int main(int argc, char* argv[]) or other // function. // TestResults test_main_ThingDoer(int argc, char* argv[]) { // TestResults results; // results = collect_and_report_TestResults(results, testFn("doThing1", ...), argc, argv); // results = collect_and_report_TestResults(results, testFn("doThing2", ...), argc, argv); // return results; // } // Then some test harness either generated or explicit can call test_main_ThingDoer(...) and optionally reported there. // Reporting granularity is controlled by how frequently you call collect_and_report_TestResults(...). You can combine // test results with results = results + testFn(..); and then collect_and_report_TestResults on the aggregate // TestResults value. // _Step_9 - if T2 is a single value then make_tuple(T2) and call longer version // auto testFunction = [](int id){return id==0?"":"";}; // auto compareFunction = [](const string a, const string b){return a==b;}; // template // _Step_10 - // test_fn(string, _FnToTest, vector>>) // Default to (string, _FnToTest, vector error_messages, vector failure_messages, vector skip_messages) : error_messages_(error_messages), errors_(errors), failed_(failed), failure_messages_(failure_messages), passed_(passed), skip_messages_(skip_messages), skipped_(skipped), total_(total) {} TestResults& TestResults::error() { errors_++; return *this; } TestResults& TestResults::error(string message) { errors_++; error_messages_.push_back(message); return *this; } TestResults& TestResults::fail() { total_++; failed_++; return *this; } TestResults& TestResults::fail(const string& message) { total_++; failed_++; failure_messages_.push_back(message); return *this; } vector TestResults::failure_messages() { return failure_messages_; } TestResults& TestResults::pass() { total_++; passed_++; return *this; } TestResults& TestResults::skip() { total_++; skipped_++; return *this; } TestResults& TestResults::skip(const string& message) { total_++; skipped_++; skip_messages_.push_back(message); return *this; } vector TestResults::skip_messages() { return skip_messages_; } vector TestResults::error_messages() { return error_messages_; } uint32_t TestResults::errors() { return errors_; } uint32_t TestResults::failed() { return failed_; } uint32_t TestResults::passed() { return passed_; } uint32_t TestResults::skipped() { return skipped_; } uint32_t TestResults::total() { return total_; } TestResults TestResults::operator+(const TestResults& other) const { vector error_messages; error_messages.insert(error_messages.end(), error_messages_.begin(), error_messages_.end()); error_messages.insert(error_messages.end(), other.error_messages_.begin(), other.error_messages_.end()); vector failure_messages; failure_messages.insert(failure_messages.end(), failure_messages_.begin(), failure_messages_.end()); failure_messages.insert(failure_messages.end(), other.failure_messages_.begin(), other.failure_messages_.end()); vector skip_messages; skip_messages.insert(skip_messages.end(), skip_messages_.begin(), skip_messages_.end()); skip_messages.insert(skip_messages.end(), other.skip_messages_.begin(), other.skip_messages_.end()); return TestResults(errors_ + other.errors_, failed_ + other.failed_, passed_ + other.passed_, skipped_ + other.skipped_, total_ + other.total_, error_messages, failure_messages, skip_messages); } TestResults& TestResults::operator+=(const TestResults& other) { error_messages_.insert(error_messages_.end(), other.error_messages_.begin(), other.error_messages_.end()); errors_ += other.errors_; failed_ += other.failed_; failure_messages_.insert(failure_messages_.end(), other.failure_messages_.begin(), other.failure_messages_.end()); passed_ += other.passed_; skip_messages_.insert(skip_messages_.end(), other.skip_messages_.begin(), other.skip_messages_.end()); skipped_ += other.skipped_; total_ += other.total_; return *this; } void PrintResults(std::ostream& os, TestResults results) { auto skip_messages = results.skip_messages(); if (skip_messages.size() > 0) { os << "Skipped:" << endl; for_each(skip_messages.begin(), skip_messages.end(), [&os](const string& message) { os << "🚧Skipped: " << message << endl; }); } auto failure_messages = results.failure_messages(); if (failure_messages.size() > 0) { os << "Failures:" << endl; for_each(failure_messages.begin(), failure_messages.end(), [&os](const string& message) { os << "❌FAILED: " << message << endl; }); } auto error_messages = results.error_messages(); if (error_messages.size() > 0) { os << "Errors:" << endl; for_each(error_messages.begin(), error_messages.end(), [&os](const string& message) { os << "🔥ERROR: " << message << endl; }); } os << "Total tests: " << results.total() << endl; os << "Passed: " << results.passed() << " ✅" << endl; os << "Failed: " << results.failed() << " ❌" << endl; os << "Skipped: " << results.skipped() << " 🚧" << endl; os << "Errors: " << results.errors() << " 🔥" << endl; } } // End namespace Test