The other day, I decided that I needed to know about test driven development for C++ on the Windows platform (using Visual Studio 2010 Premium).
I had a look around before settling on trying out boost's unit test framework. I should say that I opted for boostpro.com's release (current is 1.44 if I recall correctly). This has a build of the static library, so I don't use the DLL in my tests.
Boost's unit test documentation talks about seperating your code from your test suite, which seems reasonable. But then you must deal with the problem of referencing your code from your now seperate test suite project.
So I have a library project that I want to test (but I'm still not sure how I'd write tests that can reference an .exe project...)
So I created a seperate project in my solution called unit tests. I added the following code:
#include "stdafx.h"
#define BOOST_TEST_MODULE Crash
#include <boost/test/unit_test.hpp>
#include "LameEncoder.h"
BOOST_AUTO_TEST_SUITE(CrashTestSuite)
BOOST_AUTO_TEST_CASE(EncoderAvailable)
{
using namespace Crash::SystemDevices::Audio::Compressors::LameEncoder;
HRESULT hr = S_OK;
CComPtr <IBaseFilter> spEncoder;
hr = spEncoder.CoCreateInstance( CLSID_LAMEDShowFilter );
if( spEncoder.p )
spEncoder.Release();
BOOST_CHECK_EQUAL( hr, S_OK );
}
BOOST_AUTO_TEST_CASE(ProfilesGenerated)
{
using namespace Crash::SystemDevices::Audio::Compressors::LameEncoder;
BOOST_CHECK_EQUAL ( EncoderProfiles.size(), 6 );
}
BOOST_AUTO_TEST_SUITE_END()
I statically link to my "crash" library project output, then I added the following post-build event to get a report post-build:
"$(TargetDir)\$(TargetName).exe" --result_code=no --report_level=short
The post build output looks like this:
1>------ Build started: Project: UnitTests, Configuration: Debug Win32 ------
1> UnitTests.cpp
1> UnitTests.vcxproj -> F:\Projects\Crash\trunk\Debug\UnitTests.exe
1> Running 2 test cases...
1> f:/projects/crash/trunk/unittests/unittests.cpp(19): error in "EncoderAvailable": check hr == ((HRESULT)0L) failed [-2147221008 != 0]
1>
1> Test suite "Crash" failed with:
1> 1 assertion out of 2 passed
1> 1 assertion out of 2 failed
1> 1 test case out of 2 passed
1> 1 test case out of 2 failed
I expected the EncoderAvailable test to fail, since I haven't initialized a COM apartment for the thread. I'd assume that I can't use auto tests, and instead I need to replace the auto tests with tests I manually define myself in a main function, and do my CoInitializeEx() calls in the main function.
I've read here that you can define the entry point and register your own functions, so I gave this a go:
#include "stdafx.h"
#include <boost/test/unit_test.hpp>
using namespace boost::unit_test;
#include "LameEncoderTests.h"
test_suite*
init_unit_test_suite( int argc, char* argv[] )
{
CoInitializeEx(NULL, COINIT_MULTITHREADED);
framework::master_test_suite().
add( BOOST_TEST_CASE( &LameEncoderAvailable ) );
framework::master_test_suite().
add( BOOST_TEST_CASE( &LameEncoderProfilesGenerated ) );
CoUninitialize();
return 0;
}
Here's the build ouptut:
1>------ Build started: Project: UnitTests, Configuration: Debug Win32 ------
1> UnitTests.cpp
1> UnitTests.vcxproj -> F:\Projects\Crash\trunk\Debug\UnitTests.exe
1> Running 2 test cases...
1> f:/projects/crash/trunk/unittests/lameencodertests.h(17): error in "LameEncoderAvailable": check hr == ((HRESULT)0L) failed [-2147221008 != 0]
1>
1> Test suite "Master Test Suite" failed with:
1> 1 assertion out of 2 passed
1> 1 assertion out of 2 failed
1> 1 test case out of 2 passed
1> 1 test case out of 2 failed
1>
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
That test failure fails on the first test LameEncoderAvailable, which is the following simple function:
void LameEncoderAvailable()
{
using namespace Crash::SystemDevices::Audio::Compressors::LameEncoder;
HRESULT hr = S_OK;
CComPtr<IBaseFilter> spEncoder;
hr = spEncoder.CoCreateInstance( CLSID_LAMEDShowFilter );
if( spEncoder.p )
spEncoder.Release();
BOOST_CHECK_EQUAL( hr, S_OK );
}
Can anyone tell me where the correct place to make the CoInitializeEx() call - I don't think I should be doing so once per test - it should only be done once per thread...
As for testing exe projects, I guess you could specify a separate main.cpp (testmain.cpp or something) and exclude your real main.cpp from the build to access your code. If anyone knows of a more elegant solution to that one, I'd be keen to hear about it...