// Tester.h Unit Testing helpers #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 /* The following allow you to insert notes into source code that you can jump to by pressing F4 #pragma Note("This will become a string listed in the output window at compile time") */ #ifdef NO_NOTES #define Note(x) #define ToDo(x) #define Fix(x) #define Ask(x) #else #define _QUOTE(x) #x #define QUOTE(x) _QUOTE(x) #define __FILE__LINE__ __FILE__ "(" QUOTE(__LINE__) ") : " #define Note(x) message( __FILE__LINE__ ">>>>> Note: " x " <<<<<\n") #define ToDo(x) message( __FILE__LINE__ ">>>>> To do: " x " <<<<<\n") #define Fix(x) message( __FILE__LINE__ ">>>>> Fix: " x " <<<<<\n") #define Ask(x) message( __FILE__LINE__ ">>>>> Ask: " x " <<<<<\n") #endif #ifndef Assert #ifndef NO_TESTS #pragma warning(disable:4800) // This would warn us that x as an int would be converted to bool #define Assert(x) Tester::Test((x), __FILE__, __LINE__, #x) /* "Automatic Testing" or "programmer Testing" (once called "Unit Testing") can be easy and is necessary, so do it! There are many suites available to do testing but I found them all unnecessarily complex. The target is simple: for each non-trivial unit (class/function) write tests that will inform you if they fail. Like C++, Automatic Testing, or "making your code test itself" is a black art. C++ has around 50 key words. Most people can read a book on C++ in a weekend and remember enough to write a simple program... But useful C++ coding requires so much more than knowledge of the keywords! Similarly, my Testing class is tiny (15 lines), it will take you seconds to understand it, but making Automatic Testing work for you effectively will take time! If you're application is a DLL, a wonderful side-effect of my technique is that the tests run while you're compiling! (The registration at the end of the compilation instantiates the Tester classes) So here's what to do: It's always wise to test the release version as well as the debug version in case the compiler's optimisations change the behaviour of your code in some unexpected way (it happens). So use the Configuration Manager to create a new Configuration which is a copy of the Release Configuration and call it Beta. Tell the Beta Configuration to generate Debugging info like the Debug Version in the C++ and Linker Sections. Since the Beta Version has optimisations and debugging information, the debugging will not be good enough for single-stepping your program, but it will let you set breakpoints, and run the tests. The final change to the project settings is to define NO_TESTS in the Release Version's C++ Preprocessor settings. Include Tester.h in stdafx.h so that every file can see that the project has included it by testing for: #ifdef Assert Bear in mind that you need to test your Beta version as well as your Debug, so _DEBUG is not suitable! After each object that you want to test create a Tester class which is derived from the CTester. Your derived Tester class will have a constructor which is full of Asserts to test the object you wish to test. Instantiate the class in the header as a global and every time you run your application with testing enabled, the test will be run. You can obviously instantiate at any time, depending on your needs. Note that Assert will work in your Beta version as well as your Debug so that you can check for compiler bugs (differences in how your code works once the optimiser chews it up). For example here's a strange class that gets defined and tested: class CBadAdder { double d; public: CBadAdder() {} CBadAdder(double D) : d(D) {} operator double() const {return d;} CBadAdder operator+ (const double D) const {return CBadAdder(1+D+d);} bool operator==(const double D) const {return d==D;} bool operator!=(const double D) const {return d!=D;} }; // Now the magic unit-testing class (in this case a struct because all members are public): #ifdef Assert static struct CBadAdderTester : Tester { CBadAdderTester() { CBadAdder x(1.1); Assert(3.3+x==4.4); Assert(x+3.3==5.4); } } BadAdderTester; #endif //def Assert Since most classes will have many tests you should use lots of methods in the test struct to separate them. The constructor may end up looking as follows: CBadAdderTester() { ZeroTester(); PositiveTester(); NegativeTester(); ToleranceTester(); } ______________________________________________________________________________ You can test dialog boxes by specifying in advance which exit button to press. So you could set up a dialog, tell the system to press cancel after you call DoModal, then carry on testing. Then you can repeat the operation but tell the system to press OK and then do further tests. This sort of test is only possible when the Dialog being tested has a Parent, so you need to be running a tester window/Dialog and the testing must be done after that window has been painted, not in the Tester's OnInitDialog(). To test Dialogs, derive your Tester Window/Dialog from Tester: class CMyTesterDlg : public CDialog, public Tester { ... }; The desired behaviour is to send the Dialog a Message after it has completed its OnInitDialog(). Make the Dialog send a "Completion of OnInitDialog()" event using a message: At the end of the OnInitDialog() for the dialog you want to have tested, insert the following 3 lines: #ifdef Assert // Allow this dialog to be tested: GetOwner()->PostMessage(WM_COMMAND, 0, (LPARAM)this); #endif // def Assert Use ClassWizard to add an OnCommand event handler to the Tester (to handle the "Completion of OnInitDialog()" event) and give it the following implementation: BOOL CMyTesterDlg::OnCommand(WPARAM wParam, LPARAM lParam) {return wParam ? CDialog::OnCommand(wParam, lParam) : Tester::OnCommand(lParam);} This will reply with the command you gave to TryClicking. Now, when you want to test a function which may open a dialog box, you can give the dialog an action to perform once it has opened: TryClicking(IDCANCEL); // You can't use this in OnInitDialog() CTestableDlg dlg; dlg.DoModal(); // This Dialog will instantly have IDCANCEL 'pressed'. AfxGetApp()->RestoreWaitCursor(); // After a Dialog Box the WaitCursor gets messed up. Assert(dlg.GetDlgItemInt(IDC_SOMETHING)==123); // Check that The dialog default was correct To have your code check if the Heap has been corrupted use: ASSERT(Tester::ValidateHeap()); */ class Tester { enum Event {Nothing, Click} Action; int nID; public: static void Test(bool Flag, const char* Path, int LineNumber, char* LineText) { if(Flag) return; CString Msg; Msg.Format("%s(%u) : Test failed: %s\r\n", Path, LineNumber, LineText); OutputDebugString(Msg); Msg.Format("Test failed:\r\n %s\r\n Line %u of File:\r\n %s\r\n", LineText, LineNumber, Path); AfxMessageBox(Msg); DebugBreak(); // Use Windows Debug functions to return us to the debugger so we can find the Assert which failed in the Call Stack. FatalExit(668); // The neighbour of The Beast } static bool ValidateHeap() { HANDLE Handles[250]; // Keep it on the Frame DWORD HandleCount=GetProcessHeaps(250,Handles); unsigned long i; if(HandleCount>250) return false; // Too many heaps for(i=0; i(lParam))->GetSafeHwnd(); Event Act=Action; Action=Nothing; switch(Act) { default: return FALSE; case Nothing: return TRUE; case Click: return PostMessage(hWnd, WM_COMMAND, nID, (BN_CLICKED<<16)|(WORD)::GetDlgItem(hWnd, nID)); } } }; struct HeapCheck { HeapCheck() {ASSERT(Validate());} ~HeapCheck() {ASSERT(Validate());} static bool Validate() { const int MaxHandles=250; HANDLE Handles[MaxHandles]; // Keep it on the Frame DWORD HandleCount=GetProcessHeaps(MaxHandles,Handles); unsigned long i; if(HandleCount>MaxHandles) return false; // Too many heaps for(i=0; i