DUnit Testing in C++ Builder

By | December 30, 2016

Overview

So while building a new OTA project to provide C++ Builder with Ctrl+Shift+Up/Down code navigation and code completion functionality I needed to write some C++ DUnit tests. Now I know that DUnit is no longer being developed and that I should use DUnitX but since I know the framework and am already learning C++ 11 and OTA in C++ builder I didn't want to add another thing to learn at this time however I will sometime in the future.

So why am I writing about this? Well the documentation I found on http://docwiki.embarcadero.com/RADStudio/Seattle/en/DUnit_Overview doesn't work in Berlin and I suspect that it hasn't worked for C++ Builder since the introduction of the new RTTI. It compiles BUT it throws an RTTI exception when run.

So why don't I use the DUnit wizards from the IDE? Well the project one works but the test case version raises an blank exception. I've raised this as a bug (https://quality.embarcadero.com/browse/RSP-16631). Please vote for this if you want it fixed.

DUnit Project

So the DUnit Project wizard in the IDE will create a DUnit project for you as shown below.

//---------------------------------------------------------------------------
// DUnit Project File.
//   Entry point of C++ project using DUnit framework.
//---------------------------------------------------------------------------

#include <System.hpp>
#pragma hdrstop
#include <tchar.h>
#include <vcl.h>
#include <GUITestRunner.hpp>

#ifdef ENABLE_CODESITE
#pragma comment(lib,"CodeSiteLoggingPkg.lib")
#pragma link "CodeSiteLogging"
#endif

int WINAPI _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int)
{
  try
  {
 Application->Initialize();
 Guitestrunner::RunRegisteredTests();
  }
  catch (Exception &exception)
  {
 Application->ShowException(&exception);
  }
  catch (...)
  {
 try
 {
   throw Exception("");
 }
 catch (Exception &exception)
 {
   Application->ShowException(&exception);
 }
  }
  return 0;
}

The only other things I've added are some compiler conditionals to include CodeSite so that I could put messages in the constructors and destructors of my collaboration object to ensure they were being fired correctly.

DUnit TestCase

Below is a test case similar to that found on the docwiki above.

Declaration

To solve the problem with the RTTI exception I had to add __declspec(delphirtti) to the class declaration. Once this was added the tests ran however there are some peculiarities. It runs tests in the public and published scopes not just published.

The other mistake I made which took a few hours for the penny to drop was I had spelt Setup wrong. It should be spelt SetUp.

#ifndef CPPBuilderToolsParserTestH
#define CPPBuilderToolsParserTestH

#include "CBTBaseTestCase.h"
#include <TestFramework.hpp>
#include "CPPBuilderToolsParser.h"

class __declspec(delphirtti) TCBTParserTests : public TCBTBaseTestCase {
  private:
    std::unique_ptr<TStringList> FCode;
  protected:
    void __fastcall SetUp();
    void __fastcall TearDown();
  public:
    virtual __fastcall TCBTParserTests(String strName) : TCBTBaseTestCase(strName) {};
  __published:
    void __fastcall TestParser();
};

#endif

Implementation

Below is the implementation of the above class which should be straight forward.

#pragma hdrstop
#include "CPPBuilderToolsParserTest.h"
#include "CPPBuilderToolsFunctions.h"
#pragma package(smart_init)

void __fastcall TCBTParserTests::SetUp() {
  OutputCodeSiteMsg("Setup");
  FCode = std::unique_ptr<TStringList>{ new TStringList() };
  String strFileName = ExtractFilePath(ParamStr(0)) + "..\\..\\TestFile.cpp";
  FCode->LoadFromFile(strFileName);
}

void __fastcall TCBTParserTests::TearDown() {
  OutputCodeSiteMsg("TearDown");
}

void __fastcall TCBTParserTests::TestParser() {
  std::unique_ptr<ICBTParser> P { new TCBTParser(L"MyUnit.cpp") };
  ICBTUnit* U = P->Parse(FCode.get());
  CheckEquals(1, U->ClassCount);
}

static void registerTests()
{
  _di_ITestSuite iSuite;
  TTestSuite* testSuite = new TTestSuite("CBT Parser Tests");
  if (testSuite->GetInterface(iSuite)) {
    testSuite->AddTests(__classid(TCBTParserTests));
    Testframework::RegisterTest(iSuite);
  } else {
    delete testSuite;
  }
}
#pragma startup registerTests 33

The only other difference that is noticable is with the static funtion declared at the end to register the test case suites. This and the following #pragma code are there to workaround C++ not having the Object Pascal equivalent to an initialization section.

After Thoughts

I've written this blog post mostly to remind me later on how I solved the issues but hopefullly it will be useful to others wanting to use DUnit in C++ Builder.

Dave.

Leave a Reply

Your email address will not be published. Required fields are marked *