Mega Code Archive

 
Categories / Delphi / Examples
 

How to build Testcases with DUnit

Title: How to build Testcases with DUnit Question: Finding meaningfull testcases isn't that easy. As we grow with experiences I will show few testcases to inspire and deliver some ideas. Answer: The idea of DUnit or another xUnit is that, as we develop or change code, we develop appropriate verification tests at the same time, rather than postponing them to a later test phase. By keeping the tests up-to-date and re-applying them at regular intervals, it becomes easier to produce reliable code, and to be confident that alterations (above all refactorings) do not break existing code. Applications should become self-testing. But finding meaningfull testcases isn't that easy. As we grow with experiences I will show few testcases to inspire and deliver some ideas. Note that DUnit builds a separate instance of the class for each method that it finds, so test methods cannot share instance data. We dig into 4 methods: 1. Test Roboter for Forms 2. Test an Exception (like testing a disaster recovery) 3. Test the Fileformat to prevent Viruses, Hoaks and so on 4. Test a Reference Value It's important to be a little familiar with DUnit. First we build the testclass which will test our units: (Testing procedures should be published so RTTI can find their method address with the MethodAddress method.) Add an instance variable for every fixture (i.e. starting situation like myForm, FFull...) you wish to use. type TTestCaseMethods = class(TTestCase) private fFull: TList; myForm: TForm1; incomeref: CIncome; protected procedure SetUp; override; procedure TearDown; override; published procedure testFormRobot; procedure testExceptionIndexTooHigh; procedure testFileFormat; procedure testReferenceValue; private function WaveFileCheck(flname: string): boolean; end; As we can see the 4 testmethods in the publish section, we should keep 3 things in mind: - Test methods are parameter-less procedures. - Test methods are declared published. - Test methods generally map methods from an application But it's possible to set private methods which can be used independent of an application, like the function WaveFileCheck! We often need to do some common preparation before running a group of tests, and some freeing afterwards. For example, when testing a form or a file, we might want to create an instance of that form, run some checks on it, and finally free it. If we have a lot of tests to make, we'll end up with repetitive code in each test method. DUnit provides support for these situations through the virtual methods SetUp and TearDown, which are called before and after each test method is executed. In Xtreme testing jargon, a prerequisite state like the one provided by these two methods is known as a fixture. Our fixture for the 4 cases are: //------------------------------------------------------------------ procedure TTestCaseMethods.SetUp; begin myform:= TForm1.Create(NIL); fFull:= TList.Create; fFull.Add(TObject.Create); fFull.Add(TObject.Create); end; //------------------------------------------------------------------ procedure TTestCaseMethods.TearDown; var i: Integer; begin for i:= 0 to fFull.Count - 1 do TObject(fFull.Items[i]).Free; fFull.Free; myform.Release; end; //------------------------------------------------------------------ Now the 4 testmethods: 1. The following calls check() to see if everything went OK. It's up to you to enlarge the check with more calls or methods like a test roboter does. procedure TTestCaseMethods.testFormRobot; begin myForm.lblserial.Caption:= 'another text test'; myform.ShowModal; check(myForm is TForm, 'this is not the right type'); end; 2. The following example extends exception handling to do a couple of tests which can prove exception handling works. This case shows that it is possible to test the exceptions of the test subject: procedure TTestCaseMethods.testExceptionIndexTooHigh; begin try fFull.Items[2]; //Check(false, 'should have been an EListError.'); except on E: Exception do Check(not(E is EListError)); end; end; 3. Next a test to prove if it's or not the right format like a scanner does: resourcestring rSrc_RIFF = 'RIFF'; rSrc_WAVE = 'WAVE'; procedure TTestCaseMethods.testFileFormat; begin check(waveFileCheck('maxmor2.wav'),'this is not a wave'); end; function TTestCaseMethods.WaveFileCheck(flname: string): boolean; var f: file; BuffHeader: array[1..12] of char; begin AssignFile(f, flname); FileMode:= 0; Reset(f,1); try BlockRead(f, BuffHeader, 12); result:=(Copy(BuffHeader, 1, 4)=rSrc_RIFF) and (Copy(BuffHeader, 9, 4)=rSrc_WAVE); finally CloseFile(f); end; end; 4. At last a reference test in this case a function which returns a value, so we can refactor as much we want as long the reference value is still the same. This test checks with checkEquals() if the rate of an income results the right value 1040. // When checkEquals fails, it will end up in the TestResult as a failure (not reference value, expected: but was: ). procedure TTestCaseMethods.testReferenceValue; begin incomeRef:= createIncome2; incomeref.SetRate(4,1); checkEquals(1040, incomeRef.GetIncome(1000), 'no reference value'); incomeRef.FreeObject; end; Let's have a look at the main program which since Delphi 9 and 10 (Delphi 2006) will be build with the help of an integrated expert: program ListTest; uses TestFramework, KylixGUITestRunner, GUITestRunner, TListTestCase in 'TListTestCase.pas'; {$R *.res} begin {$IFDEF LINUX} KylixGUITestRunner.runRegisteredTests; {$ELSE} GUITestRunner.runRegisteredTests; {$ENDIF} end. have Fun and Run, greetings from maxland ;)