Click here to Skip to main content
15,883,739 members
Articles / Desktop Programming / MFC
Article

MFC Testing framework

Rate me:
Please Sign up or sign in to vote.
3.64/5 (10 votes)
26 Aug 20015 min read 89.7K   1.6K   22   11
A simple MFC automated testing framework for test-first programming, the eXtreme Programming way.

Sample Image

Why and what for...

One of the eXtreme Programming rule is to test first before code. Special test frameworks have been developed for various languages, including C++ (known as CPPUnit), and all share the same design.

They probably all work very well, but I found the CPPUnit not simple enough and finally, it does not fit the way I'd like to test before code, as we are working on Visual C++ and using exclusively the MFC classes.

So I made another test framework, that seems like any other XUnit, but simplest to my mind.

Be careful, I'm just going to explain the way I understand some XP principles, and the way we use them for our software development. They could not be the right way, but it works for us.

This test framework allows you to code tests linked to your software. They are developed in parallel and make sure you code what is needed and also make sure there is no code regression as you integrate any other software functions or classes...All the tests are ran every time. If you make a change and the whole test result is not 100% free of error or misses, then YOU broke something. It makes even more sense when you work with different teams or when you have to modify code lines of another person. You can't break the code. For more information about test-first programming, see this link.

Take a little time to make test-first programming, and you will find your code simpler, smoother and more robust to changes.

The way it works or should work is:

  1. Make a test of something you want to do or of something the software must do. You include assertion where there are critical results that must be verified. You make the code more by intention than algorithms : some classes or functions might not be yet written.
  2. You compile. Some compile errors appear; make them pass the simplest way they could pass (Create the class, functions with the simplest return value possible).
  3. You compile again. No error occurs.
  4. Run the test. The window shown above will appear, with or without any errors or misses. Misses are assertions you wrote, that return false. Errors are exceptions or any other error-like returns from your test. Misses and errors are shown in the main window, with the line and class test name for you to find it.
  5. Make the test pass, always in the simplest way.
  6. Run the test. It should be OK.

You can then make any other test. Your program will then grow and smoothly go to the one you expected. This way, be sure it will be a very simple program, readable by any other programmer, and that no errors will arise.

If you or someone else find bugs, make a test that should arise it and correct it. This way, it should never arising again.

I'm almost dubitative about this way of programming, but now, I can't event imagine another way of writing lines of code.

How to use the class

First, let's have a look at the core Test class.

//////////////////////////////////////////////////////////////////////
class AFX_EXT_CLASS Test : public CObject 
{
    DECLARE_DYNAMIC(Test)
public:
    void Lancer(TestResultat& resultat);
    virtual CString VersChaine();
    static void assertTest(bool bEvaluation, 
       const CString& strCondition, long lLigne, const CString& strFichier);
    static void assertTest(BOOL bEvaluation, 
       const CString& strCondition, long lLigne, const CString& strFichier);
    virtual void Tester() = 0;
    virtual void AvantTest();
    virtual void ApresTest();
    Test();
    virtual ~Test();
};

Your only work when you design a new test is to derive a new class from Test and to implement the Tester() function. In this function, use assert() to make assertions on your code and to send fails if something does not work. If you need to manage some data before the Test function begins, use the AvantTest() function and if you need to manage data after the test was completed, use the ApresTest() function.

How it works

If you look at the code below, which is run every time the test is requested, you'll probably understand the way it works. First, I try the Tester() function, and then catch a TestEchec exception, which mean a fail in the assertions wrote in the Tester() function, and then add a failure. If not a TestEchec, I try to catch any other exceptions and then add an error.

//////////////////////////////////////////////////////////////////////
void Test::Lancer(TestResultat& leResultat)
{
    AvantTest();
    try {
        Tester();
    }
    catch (TestEchec* p_echec) {
        leResultat.AjouterEchec(this, *p_echec);
        p_echec->Delete();
    }
    catch (CException* p_e) {
        char szErreur[255];
        p_e->GetErrorMessage(szErreur, 255);
        TestEchec* p_echec = new TestEchec(szErreur);
        leResultat.AjouterErreur(this, *p_echec);
        p_e->Delete();
    }
    catch (exception _e) {
        CString str;
        str.Format( "exception : %s", _e.what() );
        TestEchec* p_echec = new TestEchec( _e.what() );
        leResultat.AjouterErreur(this, *p_echec);
    }
    catch (...) {
        CString strErreur = GetDernierMsgErreur();
        TestEchec* p_echec;
        if ( !strErreur.IsEmpty() ) {
            p_echec = new TestEchec(strErreur) ;
        }
        else {
            p_echec = new TestEchec( _T("?") );
        }
        leResultat.AjouterErreur(this, *p_echec);
    }
    ApresTest();
}

All the work is to make the test and add any failure or error to a list that can then be displayed in a dialog box. This is the work of the TestResultat class which is used in the dialog box class:

//////////////////////////////////////////////////////////////////////
void Testeur::Afficher()
{
    DlgTest dlg(m_resultat);

    dlg.m_nNbTest = m_listeTests.GetCount();
    dlg.m_nNbEchec = m_resultat.NombreEchec();
    dlg.m_nNbErreur = m_resultat.NombreErreur();
    if (m_resultat.NombreEchec() > 0 || m_resultat.NombreErreur() > 0)
        dlg.m_strResultatAffiche = "ERREUR";
    else
        dlg.m_strResultatAffiche = "OK";
    dlg.DoModal();
}

Install the library

  1. Create a directory (e.g. TESTLIB) and copy all library files into it.
  2. Add this path to your Visual C++ options for Include files, Library and Source .

Image 2

Create a project and add tests for it

  1. Create a new project for what you want to do...
  2. Create a new Dialog Box project for the tests in the same workspace as above, with these options.

    Image 3

  3. Remove any 'black' files from the test project, as they have to be linked to the main project. Here is the workspace and the CTestForToUpper1pp::InitInstance() at creation.

    Image 4

  4. You should have the code and workspace as below. Remove any reference to the dialog and resources created by the wizard, as we only need to show the test dialog box. For now, code is in French, but you will soon have it in English. Change the code above to look like the one below.

    Image 5

  5. You should follow these screens to modify your TestApp settings so it can run tests. First, add a path to your main project, so classes created in your main project are accessible from the test project.

    Image 6

    Image 7

  6. Add the TESTLIBD.LIB for a debug setting and TESTLIB.LIB for a release setting.

    Image 8

  7. Modify the resource path to the main project compiled resources. It should be:

    Image 9

  8. As you make new classes and add resources to your main project, you should add some .rc files and .cpp files to your test project.

    Image 10

    Image 11

How to use the sample program

It is a demo workspace I wrote for your eyes only... Open it and study it. Here is a sample of test code.

class TestToUpper : public Test
{
public:
    virtual void Tester() {
      CToUpperDlg myDlg;
      assert( myDlg.MakeToUpper(_T("sentence")).Compare(_T("SENTENCE")) == 0 );
    }
};

class TestDlgToUpper : public Test
{
public:
    CToUpperDlg myDlg;
    virtual void AvantTest() {
        myDlg.Create(IDD_TOUPPER_DIALOG);
    }

    virtual void Tester() {
        myDlg.SetDlgItemText( IDC_EDIT_SENTENCE, _T("sentence") );
        ::SendMessage( myDlg.GetSafeHwnd(), 
            WM_COMMAND, IDC_BUTTON_CHANGE, NULL );
        CString strResult;
        myDlg.GetDlgItemText(IDC_EDIT_SENTENCE, strResult);
        assert( strResult.Compare(_T("SENTENCE")) == 0 );
    }

    virtual void ApresTest() {
        myDlg.DestroyWindow();
    }
};

Finally

This library is very useful if you plan to do test-first programming, but it can also be useful to test after programming, as you can run the test in an automated way.

Have fun, and if you have any remarks, bad or good, they are welcome. Also, if someone knows how to link the listed lines to developer studio environment, to open the file at the line indicated, it could be very useful.

Thanks.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
Software engineer since 1998, I worked on a medical software as designer and developper for e-med innovations inc.
Now out of job, but working on PHP and still VC++

Comments and Discussions

 
Generalyou do a good job Pin
huqingya25-Mar-04 15:04
huqingya25-Mar-04 15:04 
Generalassert is alreadt used bt standard C++ Pin
Reuven Yagel3-Sep-01 18:30
Reuven Yagel3-Sep-01 18:30 
I would switch the 'assert' to something like 'test_assert'
since assert is part of standard c++ (#include <cassert>) thus avoiding collisions.
GeneralRe: assert is alreadt used bt standard C++ Pin
Pierre Canthelou3-Sep-01 22:45
Pierre Canthelou3-Sep-01 22:45 
QuestionWhat's the advantage over CppUnit ? Pin
Martin Bohring31-Aug-01 0:03
Martin Bohring31-Aug-01 0:03 
AnswerRe: What's the advantage over CppUnit ? Pin
Pierre Canthelou31-Aug-01 0:28
Pierre Canthelou31-Aug-01 0:28 
GeneralThanks and upgrade Pin
Pierre Canthelou30-Aug-01 23:09
Pierre Canthelou30-Aug-01 23:09 
QuestionASSERT? Pin
Andreas28-Aug-01 0:37
Andreas28-Aug-01 0:37 
GeneralVote Pin
27-Aug-01 9:00
suss27-Aug-01 9:00 
GeneralRe: Vote Pin
27-Aug-01 17:40
suss27-Aug-01 17:40 
GeneralRe: Vote Pin
John M. Drescher29-Aug-01 9:31
John M. Drescher29-Aug-01 9:31 
GeneralRe: Vote Pin
27-Aug-01 21:27
suss27-Aug-01 21:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.