This is an example of Test Driven Development at compile time.
As you read this blog and look at the sample code you'll notice that I am believer in Test Driven Development. Years ago I started a company that created testing software to do regression testing. I did it because I do not enjoy testing and re-testing software, I think it is the death of any programmer.
Now with all the test frameworks available every programmer should be involved with testing their software. There should be no excuse. There are handful of frameworks and I'm sure everyone is going to choose their preferred package. I've used Google Test, CPP Unit, JUnit, and QTest. They essentially work the same so moving from one to another shouldn't be too difficult.
Test driven development is great at keeping you focused on what you are delivering and it lets you know immediately if something is broken. The unit tests created should be compact and run quickly. If possible they should run almost every time you do a local build and they should definitely run each time you do a full build.
The example code linked above was created with Qt. It uses QTest as the test framework. The project builds a static library, a dynamic library and a main app. The library code is essential tested at compile time, as soon as each library is built it is immediately tested. If the test passes the next library is built, if the test fails all compiling stops.
The build sequence is as follows;
Static library is built
Static library Test App is built and executed.
If the static library test passes ....
Dynamic library is built
Dynamic library Test is built and executed
If that dynamic library test passes ...
Main Project is built and executed
The project layout is:
SUBDIRS =
./AddLibrary/AddLibrary.pro
SUBDIRS =
./AddLibrary/AddLibrary.pro
TARGET = AddLibrary
./AddLibraryTest/AddLibraryTest.pro
TARGET = AddLibraryTest
./SubLibrary/SubLibrary.pro
SUBDIRS =
./SubLibrary/Sublibrary.pro
TARGET = SubLibrary./SubLibraryTest/SubLibraryTest.pro
TARGET = SubLibraryTest
./MainProject/MainProject.pro
TARGET = MainProject
I chose the Qt environment because it makes the testing easy. In the project file a command is added to execute the just built file. So the static library test project looks like:
TARGET = AddLibraryTest
CONFIG += c++14
CONFIG += console
CONFIG += testcase
QT -= gui
QT += testlib
INCLUDEPATH = ../AddLibrary
HEADERS += \
AddLibraryTest.h
SOURCES += \
AddLibraryTest.cpp \
AddLibraryTestMain.cpp
macx {
DESTDIR = .
CONFIG -= app_bundle
LIBS += ../AddLibrary/libAddLibrary.a
}
QMAKE_POST_LINK = $$DESTDIR/$$TARGET
Its this last line that makes it all work:
QMAKE_POST_LINK = $$DESTDIR/$$TARGET
The QMAKE_POST_LINK tells the build environment to execute what it is assigned to. In my case the TARGET app is my unit test. It immediately runs at the end of a the test code build.
If you watch the compiler output window you'll see the code being compiled, then you'll see the test output:
........ rms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk -mmacosx-version-min=10.14 -Wl,-rpath,@executable_path/../Frameworks -Wl,-rpath,/Users/user/Qt/6.2.3/6.2.3/macos/lib -o ./AddLibraryTest AddLibraryTest.o AddLibraryTestMain.o moc_AddLibraryTest.o -F/Users/user/Qt/6.2.3/6.2.3/macos/lib ../AddLibrary/libAddLibrary.a -framework QtTest -framework Security -framework AppKit -framework ApplicationServices -framework Foundation -framework QtCore -framework DiskArbitration -framework IOKit
(and more code being compiled)
./AddLibraryTest
********* Start testing of AddLibraryTest *********
Config: Using QtTest library 6.2.3, Qt 6.2.3 (x86_64-little_endian-lp64 shared (dynamic) release build; by Clang 13.0.0 (clang-1300.0.29.3) (Apple)), macos 12.6
PASS : AddLibraryTest::initTestCase()
PASS : AddLibraryTest::AddTenTest()
PASS : AddLibraryTest::AddOneHundredTest()
PASS : AddLibraryTest::cleanupTestCase()
Totals: 4 passed, 0 failed, 0 skipped, 0 blacklisted, 0ms
********* Finished testing of AddLibraryTest *********
(and more code being compiled)
......... table_path/../Frameworks -Wl,-rpath,/Users/user/Qt/6.2.3/6.2.3/macos/lib -o SubLibraryTest SubLibraryTest.o main.o moc_SubLibraryTest.o -F/Users/user/Qt/6.2.3/6.2.3/macos/lib /Users/user/Workspace/Projects/AlsCube/TestingFromTheStart/build/SubLibrary/SubLibrary/libSubLibrary.dylib -framework QtTest -framework Security -framework AppKit -framework ApplicationServices -framework Foundation -framework QtCore -framework DiskArbitration -framework IOKit
(then the main project)
/Users/user/Workspace/Projects/AlsCube/TestingFromTheStart/build/SubLibrary/SubLibraryTest/SubLibraryTest
********* Start testing of SubLibraryTest *********
Config: Using QtTest library 6.2.3, Qt 6.2.3 (x86_64-little_endian-lp64 shared (dynamic) release build; by Clang 13.0.0 (clang-1300.0.29.3) (Apple)), macos 12.6
PASS : SubLibraryTest::initTestCase()
PASS : SubLibraryTest::SubTenTest()
PASS : SubLibraryTest::SubOneHundredTest()
PASS : SubLibraryTest::cleanupTestCase()
Totals: 4 passed, 0 failed, 0 skipped, 0 blacklisted, 0ms
********* Finished testing of SubLibraryTest *********
........ utable_path/../Frameworks -Wl,-rpath,/Users/user/Qt/6.2.3/6.2.3/macos/lib -o MainProject main.o -F/Users/user/Qt/6.2.3/6.2.3/macos/lib /Users/user/Workspace/Projects/AlsCube/TestingFromTheStart/build/MainProject/../AddLibrary/AddLibrary/libAddLibrary.a /Users/user/Workspace/Projects/AlsCube/TestingFromTheStart/build/MainProject/../SubLibrary/SubLibrary/libSubLibrary.dylib -framework QtCore -framework DiskArbitration -framework IOKit
Another example is the FileStore SDK project. You can download it from Github. The sample project provided is the actual unit tests used to validate the FileStore I/O and record operations. The FileStore library is built as a dynamic library, the test code is built and it validates the library. There are four separate test files with a total of 93 tests.
/Users/user/Workspace/Projects/AlsCube/TestingFromTheStart/build/MainProject/MainProject
MainProject add 50
MainProject sub 800
Another example is the FileStore SDK project. You can download it from Github. The sample project provided is the actual unit tests used to validate the FileStore I/O and record operations. The FileStore library is built as a dynamic library, the test code is built and it validates the library. There are four separate test files with a total of 93 tests.
If you have never done test driven development you'll need to get use to not writing a lot of production code up front. The purists will tell you to write the test first then the code the test operates against. The test code validates what you want to do and the production code is how you want to do it. I still write the production code first, but only up to the point that it can do something useful, then I write the test. Only when I have proven my general theory do I go back to the production code and start implementing the details and exception handling, and as I do each addition I go back and write the supporting test to prove the additional code. If you end up doing this correctly you can achieve a very high level of code coverage.
When it comes to writing unit tests my approach is to write multiple unit tests per function to be tested, not a single unit test that tests all features and cases. So you'll find that I have one or more positive tests, these test the function as it should work. Then I'll have a series of negative tests, forcing it to fail. If you find that your unit tests are becoming long, that may be a sign that you should break up the production code into smaller functions.
At first it will seam like you are writing a lot of extra code, but your code will function as expected and when a change comes a long and breaks things you immediately know it.
No comments:
Post a Comment