Until recently I've used C++Builder 10.2 for a project, and I had begun to use DUnitX to add some unit tests for the project.
Now I have upgraded to C++Builder 11.2, and found that DunitX is no longer supported for C++Builder when using this version. Instead, Embarcadero recommends to use DUnit or Googletest.
On further research, it seems that Googletest cannot be used with the classic compiler (but I'm not actually interested in using the pre-C++11 classic compiler), but also that DUnit cannot be used when targeting the Firemonkey framework, and that DUnit (1) is unmaintained and (2) does not work well with the Clang-based compiler.
I'm interested in using googletest because I have already used both, googletest and googlemock, on less niche platforms than C++Builder such as Linux/GCC, Apple/Clang and Windows/MinGW-w64. I am aware that the googletest project itself refuses to accept build files or patches for C++Builder because they do not want to spend effort to support niche compilers (see e.g. here, here, and here).
I'm happy to learn that some patched version of googletest is currently available for C++Builder through the GetIt package manager, even though it is not clear who has actually made that patch, and although I realize that Embarcadero may remove googletest from the GetIt package manager an any moment.
I've found two blog posts explaining how to install googletest in C++Builder and how to use it, however, I cannot successfully follow the second blog post when it comes to point 6, which reads
In your project group create a new windows64 bit VCL console application. Set this to use the debug settings (this allows you to debug code that doesn’t pass a unit test).As well as the files you want to test and the files containing the testing code you need to add to the project the library file …GT2021.09\cbuilder\lib\Win64\Debug\gtest.a.
I'm not sure how I am supposed to "add to the project the library file". I've tried to
copy the gtest.a and gmock.a files into the project directory and then
right-click on the project name in the "Projects" view of the IDE and select "Add...", then change file type to "static libraries", then select gtest.a and repeat with gmock.a.
Here I've gone ahead and have already added gmock.a because I have experience with googlemock and envision that its additional matchers and mock class generators will help me writing tests.
When I compile a simple test project that does not actually perform any tests, everything compiles and links fine, but when I execute the resulting command line program, then it fails with exit code (errorlevel) -1073741819 and produces no output. This does not happen if I comment all usages of googletest out.
The simple test project which fails during execution consists only of
#include <gtest/gtest.h>
#include <vcl.h>
#include <tchar.h>
int _tmain(int argc, _TCHAR* argv[])
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
which should cause googletest to print that 0 tests were executed, but instead it crashes as described. When replacing the two lines in main with a simple printf, which does not use googletest, but leaving all includes unaltered and without altering the project with regard to libraries, it works fine (the new printf prints something) but of course cannot perform any tests.
How to fix this?
One more observation: When adding the static libraries to the project as described above, I get a notification message box from the IDE, saying "One or multiple lines were too long and have been truncated". I have no idea how this message could make sense with regard to adding a static library to the project. It seems however, that this is not an error, and the linker actually uses the static libraries when linking.
The main problem here was the inclusion of the gmock.a library as it was compiled by the GetIt googletest package. This gmock project and basically all other gmock projects in the GetIt package are broken and need to be repaired before using them. I may post more details about this in a future topic. The gmock.cbproj project as distributed by GetIt, e.g., includes the unrelated source file googletest\samples\sample8_unittest.cc, among other errors.
A simple method to use googletest with C++Builder 11.2, which is based on the blog posts by Cigol, but which does not require to copy include files and library files:
When installing googletest with the getit package manager, the IDE automatically opens a group project Googletest.groupproj and compiles two of the contained projects (gtest and gtest_main) for the Windows 64 bit platform in "Release" mode. Furthermore, all other project files in the Googletest project group are modified probably because they have been updated from an earlier C++Builder version and want to be saved when closing the IDE.
There is no need to compile googletest in "Debug" mode, one would need that only for debugging the unit testing framework itself.
Next, create a new VCL Windows 64 bit console application to start using googletest:
File -> New -> "Console Application - C++Builder"
Source Type: C++, Target Framework: Visual Component Library, [OK]
Add Target Platform Windows 64-Bit in the "Projects" view (right-click on Target Platforms).
Delete Target Platform Windows 32-Bit.
Save all in a dedicated directory:
File -> Save All
Create a new folder, e.g. MyUnitTests.
Place project file as e.g. MyUnitTests.cbproj into that folder.
Rename File1.cpp to MyTestsMain.cpp and store in that folder
This creates a C++ source file MyTestsMain.cpp with some includes and an empty main function:
#include <vcl.h>
#include <tchar.h>
int _tmain(int argc, _TCHAR* argv[])
{
}
For convenience, googletest provides a library gtest_main.a which only contains a main function that one can use to execute all unit tests compiled into an executable. By linking against the gtest_main.a library, users can avoid writing their own main function and concentrate on only writing test code. But since the C++Builder wizard has already created a main function, one can as well fill the generated main function with the necessary boilerplate code (only two lines are required, compare against the googletest main function in C:\Users\yourLogin\Documents\Embarcadero\Studio\22.0\CatalogRepository\GoogleTest-2021.09\googletest\src\gtest_main.cc) and add the gtest.h include directive:
#include <gtest/gtest.h>
#include <vcl.h>
#include <tchar.h>
int _tmain(int argc, _TCHAR* argv[])
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
Trying to build this project fails because the gtest/gtest.h include file is not found. This can be fixed in Project -> "Options..." -> Building -> C++ Shared Options -> "Include path": After selecting "All configurations - All platforms" in the drop-down list "Target", add the following entry to "Include path":
$(BDSCatalogRepository)\GoogleTest-2021.09\googletest\include
Using the variable $(BDSCatalogRepository) avoids machine- and developer-specific absolute PATHs. Save the changed project settings with File -> "Save all". Trying again to build this project now fails because of different errors, which is progress! The errors now are "Unresolved external"s, which means we have to tell the project to link against gtest.a and where to find it. Linking against gtest.a can be done by adding a pragma to the top of the file containing the main function:
#pragma comment(lib,"gtest")
#include <gtest/gtest.h>
#include <vcl.h>
#include <tchar.h>
int _tmain(int argc, _TCHAR* argv[])
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
Where to find the library can be configured in Project -> "Options..." -> Building -> C++ Shared Options -> "Library path": Again first select "All configurations - All Platforms", then add the following entry to "Library path":
$(BDSCatalogRepository)\GoogleTest-2021.09\cbuilder\lib\$(Platform)\Release
After File -> "Save All", a new, clean build generates different "Unresolved external"s, progress! This time, symbols from the standard C++ library are missing, which can be fixed via Project -> "Options..." -> Building -> C++ Linker, again for Target "All configurations - All platforms", check the checkbox "Link with Dynamic RTL, Windows...". After another File -> "Save All", a clean build succeeds and executing the generated Win64\Debug\MyUnitTests.exe generates this output:
[==========] Running 0 tests from 0 test suites.
[==========] 0 tests from 0 test suites ran. (0 ms total)
[ PASSED ] 0 tests.
One can now add tests to the test project. Tests can be added to the source file which contains the main function or to different, topic-specific source files. I'll add two tests in new files for demonstration:
In the "Projects" view, right click on the current project, which is confusingly named "MyUnitTest.exe" in the project view with an ".exe" extension instead of a project file extension, then select "Add new..." -> Unit in the popup menu. "Unit" here is C++Builder's language for a pair of one source and one header file, and is not necessarily related to unit testing.
The new files are initially named "Unit1.cpp" and "Unit1.h" but can be renamed when doing File -> "Save All". I name this first test file to "SelfContainedTest.cpp" because its test will be self-contained. Add the following code to the .cpp file after the IDE-Generated boilerplate:
#include <gtest/gtest.h>
TEST(SelfContained, Addition) {
EXPECT_EQ(3, 1+2);
EXPECT_GT(3, 2+2);
}
Rebuilding succeeds, execution reveals that the second EXPECT fails as it should, the number 3 is in fact not greater than the sum 2+2. Fix if you like.
In a second test, I want to test non-GUI methods of an existing VCL form. In a real-world scenario, the GUI project and my test project would be part of the same project group and live in the same directory or below the same parent directory, and I would add the VCL form's .cpp file also to the test project with (Project View) -> right click -> "Add..." -> C++Builder unit (*.cpp). My form TAdderForm that I'm using here is a simple form with two VCL TEdit fields for entering numbers and a VCL TLabel to display the sum of the two numbers. The sum is computed in a method
int TAdderForm::add(int num1, int num2)
{
return num1 + num2;
}
which I want to test here. To write the test, I add a new "Unit" to the test project as before, naming the source file "VCLTest.cpp" this time. After the IDE-generated boilerplate, I add this code to the .cpp file:
#include "adderFormx.h"
#include <gtest/gtest.h>
TEST(VCL, Addition) {
// Have to instantiate VCL form before calling its method.
Application->CreateForm(__classid(TAdderForm), &AdderForm);
EXPECT_EQ(3, AdderForm->add(1,2));
EXPECT_GT(3, AdderForm->add(2,2));
delete AdderForm; // Delete no longer used form.
AdderForm = nullptr; // clear pointer, another test may allocate new instance
}
This is basically the same test as before. The second expectation will fail again and needs to be fixed because 3>4 is a wrong expectation. The test uses the global instance pointer "AdderForm" from the form's source file for simplicity, this can be modified if required. If multiple tests want to instantiate the same form, a fixture should be used and the setup and teardown done here inside the test should be moved to the fixture's respective methods, but this is no longer C++Builder specific.
Note that Application->Run() is never called, and no GUI elements actually appear on the screen when executing the tests. I'm restricting tests to non-GUI methods of the GUI classes.
When running my test from the Test Explorer in Visual Studio 2022 [17.2.0], testhost.exe crashes when my unit test has a gMock EXPECT_CALL call in it. What am I doing wrong?
Minimal code:
#include <CppUnitTest.h>
#include <CppUnitTestAssert.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using ::testing::Return;
class MockCow
{
public:
MOCK_METHOD0(Moo, int());
};
TEST_CLASS(the_unit_test)
{
TEST_METHOD(the_test_method)
{
MockCow cow;
EXPECT_CALL(cow, Moo())
.WillOnce(Return(42));
cow.Moo();
}
};
If the EXPECT_CALL call is commented out, the unit test will not crash.
I created this project by making an Empty C++ project, then installing the NuGet gMock package, latest stable version 1.11.0, and adding library directories/dependencies in the project settings. The project configuration type is Dynamic Library (.dll).
For C/C++ Additional Include Directories, I have:
$(VCInstallDir)Auxiliary\VS\UnitTest\include;
For Linker Additional Library Directories, I have:
$(VCInstallDir)Auxiliary\VS\UnitTest\lib;
For Linker Input Additional Dependencies, I have:
x64\Microsoft.VisualStudio.TestTools.CppUnitTestFramework.lib
It compiles and links successfully, so the above is just for completeness. I have also tried installing previous versions of the gMock NuGet behavior, but I get the same behavior.
Faulting module name: MyTest.dll_unloaded, version: 0.0.0.0, time stamp: 0x62cd8255
Exception code: 0xc0000005
Fault offset: 0x000000000011e8f7
Faulting process id: 0x8b04
Faulting application start time: 0x01d895fa0d052ac3
Faulting application path: C:\Program Files\Microsoft Visual Studio\2022\Professional\Common7\IDE\Extensions\TestPlatform\testhost.exe
There are other posts about gMock crashes that may be tangentially related, but they either don't have solutions or are missing details and were never updated.
You mix the very different test frameworks.
The more important thing is that you do not initialize Google test framework.
TEST_CLASS, TEST_METHOD are of another test framework, do not perform required pre and post stuff, Google mock is not supposed to work there.
Choose one framework and use it.
#include <gmock/gmock.h>
#include <gtest/gtest.h
using ::testing::Return;
class MockCow
{
public:
MOCK_METHOD0(Moo, int());
};
TEST(the_unit_test, the_test_method) {
MockCow cow;
EXPECT_CALL(cow, Moo())
.WillOnce(Return(42));
cow.Moo();
};
int main(int argc, char *argv[]) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
I have a library inside which there are some testing programs written using Boost.Test. The test files don't have #define BOOST_TEST_DYN_LINK or #include <boost/test/included/unit_test.hpp>. They only have #include <boost/test/unit_test.hpp>. So the main() function isn't there implicitly.
Now I have to debug some library functions which have been used in the test cases. Given that I cannot add or change anything in the test programs, how can I invoke the test programs under a debugger ?
Create a test runner (e.g. main_test.cpp) and link your library against that.
# main_test.cpp
// --- Boost Includes ---
#define BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>
Invoking the resulting executable should run your tests. You can then debug individual tests by invoking the runner with ./myrunner --run_test='some_testsuite'/../'some_testname'.
I just set up a new (native) unit test in a new Solution. But even the example program does not work if I add an Assertion. It compiles without problems and the default generated (empty) test succeeds. But when I add a simple Assertion it fails with error code C0000005.
This is probably related to a similar issue, but I don't even have some library connected and therefore can't use the proposed solutions.
The test looks like this:
#include "stdafx.h"
#include "CppUnitTest.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace Test_Native
{
TEST_CLASS(UnitTest1)
{
public:
TEST_METHOD(TestMethod1)
{
Assert::AreEqual(1, 1, L"message", LINE_INFO()); // Without this line everything is fine
// TODO: Your test code here
}
};
}
This is a known bug. Unfortunately, Microsoft considers this as "Won't Fix".
In short, there are two workarounds:
Compile the actual project in release mode and the test project in debug mode.
Outsource all testable functions to a static library project.
If you download the latest version of Google Mock (1.7.0) there are project files for VS2005 and 2010! The project to test is written in VS2008,so I opened the VS2005 file and converted it for VS2008 and compiled with
Multi-threaded Debug (/MTd)
Dynamic Library (.dll)
In the test solution:
Project to test:
Configuration type: Dynamic Library (.dll)
Runtime library: Multi-threaded Debug (/MTd)
UnitTest project:
#include <iostream>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
int main(int argc, char **argv)
{
::testing::InitGoogleMock(&argc, argv);
return RUN_ALL_TESTS();
}
Additional Library Directories: ..\..\gmock\msvc\2005\Debug
Additional Dependencies: gmock.lib gmock_main.lib
Runtime library: Multi-threaded Debug (/MTd)
If I run the UnitTest project I get following error:
Windows has triggered a breakpoint in Program_UnitTests.exe.
This may be due to a corruption of the heap, which indicates a bug in Program_UnitTests.exe or any of the DLLs it has loaded.
in xmtx.c:
_RELIABILITY_CONTRACT
void __CLRCALL_PURE_OR_CDECL _Mtxunlock(_Rmtx *_Mtx)
{ /* unlock mutex */
LeaveCriticalSection(_Mtx);
#ifdef _M_CEE
System::Threading::Thread::EndThreadAffinity();
#endif
} // <------- STOPPED HERE
#endif /* !_MULTI_THREAD */
What is wrong here? Thank you for any help!
Super-short answer: compile googlemock as a static library instead of a dll; that might fix your problem.
Much longer answer:
Problems like this are typically the result of mismatched compiler settings. These kinds of problems are a pain to diagnose, which is a large part of the reason for the following guidance given in the googletest FAQ:
If you compile Google Test and your test code using different compiler flags, they may see different definitions of the same class/function/variable (e.g. due to the use of #if in Google Test). Therefore, for your sanity, we recommend to avoid installing pre-compiled Google Test libraries. Instead, each project should compile Google Test itself such that it can be sure that the same flags are used for both Google Test and the tests.
This applies equally to googlemock. Basically, they're suggesting that you compile the googletest & googlemock source code alongside your own code to avoid this problem. They also make this quite easy: check out the fused-src directory of the gmock distribution for a voltron .cc file and the corresponding headers, gmock.h and gtest.h.
If you'd like to continue linking against a completely separate library, you'll need to verify that all of the compiler settings match in the VS project. Basically you'll need to check every single configuration in the properties dialog, paying special attention to the toolset, exceptions, preprocessor definitions, RTTI, etc.