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 sepa开发者_StackOverflow社区rate 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...
Use a Global Fixture. Fixtures are a great way to set up initialization/shutdown code for each test. A global fixture lets you define initialization/shutdown code for your whole test suite.
Untested, but would a global variable that invokes CoInitializeEx() in the constructor help?
Why not do CoInitialize, CoUnitialize for each test?
Subsequent calls to CoInitialize or CoInitializeEx on the same thread will succeed, as long as they do not attempt to change the concurrency model, but will return S_FALSE.
EDIT:
Assuming that the tests are indeed executed concurrently. One way to do it is to have a thread_specific container which holds a RAII CoInitialize/CoUnitialize object.
You do NOT need to use init_unit_test_suite, since this function is executed before any of the tests run.
You do want to use Global fixture. Call CoInitializeEx(NULL, COINIT_MULTITHREADED); in constructor and CoUnInitializeEx(); in destructor
精彩评论