One of the new features in NAV 2009 SP1 is C/AL Testability.
Let try using the new feature to test a simple codeunit – let us call the codeunit doTest. The codeunit will only contain a simple function, that includes a confirm and will look like this:
PROCEDURE TestConfirm@1112800000(useConfirm@1112800000 : Boolean) out : Boolean; BEGIN IF useConfirm THEN IF CONFIRM('Do you confirm?',TRUE) THEN EXIT(TRUE) ELSE ERROR('No confirmation!'); EXIT(FALSE); END;
Before we are able to perform tests on codeunits / functions – we have to enable the Testability. This is done in Tools -> Option.
Here you must set the “Show C/AL Testability Properties” to Yes.
Now you are ready to build a codeunit, which must contain the functions to handle the test of the codeunit doTest. My simple test codeunit, does only contain one function. To test this function we will be needing the following functions:
So let’s take a close look on how to build the “Handler Test Functions” codeunit.
First create a new codeunit. Let us call it “Handler Test Functions”. In the Properties for the Codeunit set SubType to Test.
We want to test the function TestConfirm in the doTest codeunit. Because the function contains a CONFIRM, we need to have functions that can handle the answer Yes and No. So let us build these two function.
In the Global Var add the function Confirm_Yes. Before closing the Global Var window, select the Properties for the function and set the FunctionType to ConfirmHandler.
Next set the Local Vars for the new function. This is to create the necessary signatur to the ConfirmHandler. The Signatur for the ConfirmHandler is:
Function name(Question : Text; VAR Reply : Boolean)
So what you do, is adding the 2 parameters – Question and Reply to the Locals. Now you are ready to build the Code which shall handle the Confirm. It could look something like this:
[ConfirmHandler] PROCEDURE Confirm_Yes@1112800000(Question@1112800001 : Text;VAR Reply@1112800002 : Boolean); BEGIN IF Question <> 'Do you confirm?' THEN ERROR('Unknown Confirm text: '+ Question); Reply := TRUE; END;
Now do the same to create a function for Confirm_No – and you should end up with a function like this:
[ConfirmHandler] PROCEDURE Confirm_No@1112800004(Question@1112800001 : Text;VAR Reply@1112800002 : Boolean); BEGIN IF Question <> 'Do you confirm?' THEN ERROR('Unknown Confirm text: '+Question); Reply := FALSE; END;
Next is to create functions that handles the different test scenarios.
Again go into the Global Vars and add the new function here. Let us start with the function TestConfirm_TrueYes. This function, should handle the Yes case.
In the Properties for the function you now have to setup the FunctionType and the HandlerFunctions.
FunctionType is set to Test. When FunctionType is Test – the function will automatically be executed by the Test Runner codeunit. Next set HandlerFunctions to Confirm_Yes. Now the function knows, that if a Confirm Message occurs, then use the HandlerFunction Confirm_Yes.
All you now have to do is to call the TestConfirm function in the doTest codeunit. You should end up with a function like this:
[Test] [HandlerFunctions(Confirm_Yes)] PROCEDURE TestConfirm_TrueYes@1112800005(); VAR doTest@1112800000 : Codeunit 50001; BEGIN IF NOT doTest.TestConfirm(TRUE) THEN ERROR('Error in test - True Yes'); END;
Repeat this for the functions TestConfirm_TrueNo and TestConfirm_False.
TestConfirm_TrueNo will look like this:
[Test] [HandlerFunctions(Confirm_No)] PROCEDURE TestConfirm_TrueNo@1112800001(); VAR doTest@1112800000 : Codeunit 50001; BEGIN ASSERTERROR doTest.TestConfirm(TRUE); IF GETLASTERRORTEXT <> 'No confirmation!' THEN ERROR('Unexpected error: ' + GETLASTERRORTEXT); END;
Please notice, that the HandlerFunction is Confirm_No. Furthermore notice that we in this function uses ASSERTERROR and GETLASTERRORTEXT. We are doing this, because we are now expecting to get an error back from the doTest.TestConfirm function.
TestConfirm_False will look like this:
[Test] PROCEDURE TestConfirm_False@1112800003(); VAR doTest@1112800000 : Codeunit 50001; BEGIN IF doTest.TestConfirm(FALSE) THEN ERROR('Wrong answer'); END;
Now we have all test functions in place and are ready to build a test run codeunit. Let us call the codeunit Test Runner.
In the properties set the SubType to TestRunner.
Now all you have to do – is adding the call of the function in OnRun section:
CODEUNIT.RUN(CODEUNIT::"Handler Test Functions");
When you now executes the Test Runner codeunit – it will automatically execute all functions in the “Handler Test Function” codeunit, that has the FunctionType Test as property.
If you like to log any events that occur, you can add two functions to your Test Runner codeunit. OnBeforeTestRun and OnAfterTestRun. These function will be executed before and after each test run – a test run in our case – is equal with a function call.
PROCEDURE OnBeforeTestRun@1112800000(CodeunitID@1112800000 : Integer;CodeunitName@1112800001 : Text;FunctionName@1112800002 : Text) Ok : Boolean; BEGIN Before := CURRENTDATETIME; EXIT(TRUE); END; PROCEDURE OnAfterTestRun@1112800001(CodeunitID@1112800000 : Integer;CodeunitName@1112800001 : Text;FunctionName@1112800002 : Text;Success@1112800003 : Boolean); VAR log@1112800004 : Record 50000; BEGIN log.INIT; log.UnitId := CodeunitID; log.Unit := CodeunitName; log.Func := FunctionName; log.Before := Before; log.After := CURRENTDATETIME; IF Success THEN log.Status := log.Status::Success ELSE BEGIN log.Status := log.Status::Failure; IF FunctionName <> '' THEN log.Message := GETLASTERRORTEXT; END; log.INSERT(TRUE); END;
These functions, will save the test result to a table and you will get a log similar to this:
That’s all folks! Now you can test codeunits with the use of C/AL Testability 🙂
You really make it seem so easy with your presentation but I find this matter to be actually something which I think I would never understand. It seems too complex and very broad for me. I am looking forward for your next post, I will try to get the hang of it!