Functional Testing in NAV 2009 SP1

Because SP1 for Microsoft Dynamics NAV 2009 was released, I tried to create short demo of the new functionality, which allows developers to create tests for their code. Official documentation for this part of NAV 2009 SP1 could be found here.

 

How it works

You have new property on codeunit –Subtype – which could be “Normal,Test,TestRunner”.

Normal – you know it… ;-)

  • Test – this codeunit have functions, which are testing the functionality, this is the main codeunit doing the testing
  • TestRunner – this is codeunit which runs the test codeunits and e.g. collecting the results. Because you can run all tests by this one codeunit, it is not problem to run the tests e.g. in NAS or through webservices.

 

If you use Test subtype, you can use new property on each function in the codeunit – FunctionType – which could be

  • Normal – again you know it…
  • Test – this is the function which tests the functionality
  • MessageHandler – handles cases, when you expect that some message will be displayed
  • ConfirmHandler – handler for Confirm dialog, in which you can simulate the answer by user (yes,no)
  • StrMnuHandler – same like ConfirmHandler but for String menu dialogs
  • FormHandler – you can handle displayed form which you expect
  • ModalFormHandler – same for modal form, you can simulate the result (Yes, No, Ok, Cancel, LookupOK, LookupCancel etc.)
  • ReportHandler – handling reports which are runned by the tested function

Using the handlers could be complex task, how to create them is described here.

Each test function could have assigned one or more handlers, and if the handler is not used during the testing, it leads to test Failed result (it means you are expecting something but it didn’t happened).

In testing function you can use new keyword ASSERTERROR before some command to say that you are expecting error in the command or block of commands. After that you can test if correct error was raised by testing GETLASTERRORTEXT. If there is wrong error text, you can call ERROR and the test function will have result Fail, else it will be Success.

TestRunner codeunit could have two functions – OnBeforeTestRun and OnAfterTestRun – which could e.g. store the date and time of start of some test function, result of the test etc. More info is here.

Short Demo

1st Step – something to test

Because creating the tests is not easy task, best is to test only the base functions you are using. I cannot imagine to create test for testing all aspects of posting codeunit or something like that.

We will start by creating some function which we will test. I have created one codeunit with one function with one parameter of type boolean and result of type boolean. Logic is this:

If parameter is True, Confirm is displayed and if user select Yes, function return true. If User select No, function display Error with text ‘Canceled’.

If parameter is False, function return False.

The function looks like this:

PROCEDURE TestedFunction@1000000001(Parameter@1000000000 : Boolean) : Boolean;
BEGIN
  IF Parameter THEN BEGIN
    IF CONFIRM(‘My Confirm’,TRUE) THEN
      EXIT(TRUE)
    ELSE
      ERROR(‘Canceled’);
  END ELSE
    EXIT(FALSE);
END;

You can see that I have used hardcoded texts. Please, take it as something, which will not happen in real development, I have used it to have as simple example as possible.

 

2nd Step – Testing codeunit

Ok, now we will create the test for this function. We want to test all three paths – Parameter = true, Answer = Yes; Parameter = true; Answer = No; Parameter = False. It means we will use three test functions:

  • TestMyFunctionTrueYes
  • TestMyFunctionTrueNo
  • TestMyFunctionFalse

All three functions are of type ”Test”, whole codeunit is of type “Test”. There are no parameters or return datatype for these functions.

There is the code for these functions:

[Test]
[HandlerFunctions(ConfirmHandlerYes)]
PROCEDURE TestMyFunctionTrueYes@1000000000();
VAR
  TestedFunction@1000000000 : Codeunit 50010;
BEGIN
  IF NOT TestedFunction.TestedFunction(TRUE) THEN
    ERROR(‘Wrong Answer’);
END;

[Test]
[HandlerFunctions(ConfirmHandlerNo)]
PROCEDURE TestMyFunctionTrueNo@1000000001();
VAR
  TestedFunction@1000000000 : Codeunit 50010;
BEGIN
  ASSERTERROR TestedFunction.TestedFunction(TRUE);
  IF GETLASTERRORTEXT <> ‘Canceled’ THEN
    ERROR(‘Unexpected error: %1′, GETLASTERRORTEXT);
END;

[Test]
PROCEDURE TestMyFunctionFalse@1000000006();
VAR
  TestedFunction@1000000003 : Codeunit 50010;
  ErrorText@1000000002 : TextConst ‘ENU=The update has been interrupted to respect the warning.';
BEGIN
  IF TestedFunction.TestedFunction(FALSE) THEN
    ERROR(‘Wrong answer’);
END;

 

Again, I have used hardcoded texts, do not use them in your real development!

You can see, that first two functions have assigned Handler Function. One is simulating the answer Yes, one answer No. In second function we are expecting error, thus ASSERTERROR was used. Than we test if the correct error was triggered.

Now how the handlers looks like:

[ConfirmHandler]
PROCEDURE ConfirmHandlerYes@1000000002(Question@1000000000 : Text[1024];VAR Answer@1000000001 : Boolean);
BEGIN
  IF Question <> ‘My Confirm’  THEN
    ERROR(‘Unknown Confirm text: %1′,Question);
  Answer := TRUE;
END;

[ConfirmHandler]
PROCEDURE ConfirmHandlerNo@1000000003(Question@1000000001 : Text[1024];VAR Answer@1000000000 : Boolean);
BEGIN
  IF Question <> ‘My Confirm’  THEN
    ERROR(‘Unknown Confirm text: %1′,Question);
  Answer := FALSE;
END;

 

Both are ConfirmHandlers, we are handling the confirm dialog. We are testing that correct confirm dialog was displayed.

Now we have the testing codeunit. Ok, what’s next?

3rd Step – Test runner codeunit

Now we just prepare codeunit which will run this test. This codeunit could be more complex than in our example, e.g. it could run tests based on some setup table, where you can select which tests to run etc. I will give you example of both functions which could be used in this codeunit.

Start with creating new codeunit, select the Test runner subtype. In the OnRun trigger add this line:

CODEUNIT.RUN(CODEUNIT::"Test codeunit");

That’s all if you want only to test the functionality. If you want to do something more, e.g. log the results, you can add these two functions:

 

VAR

  Before@1000000000 : DateTime;
  TestResult@1000000001 : Record 50000;

PROCEDURE OnBeforeTestRun@1000000002(CodeunitID@1000000000 : Integer;CodeunitName@1000000001 : Text[30];FunctionName@1000000002 : Text[30]) : Boolean;
BEGIN
  Before := CURRENTDATETIME;
  EXIT(TRUE);
END;

PROCEDURE OnAfterTestRun@1000000003(CodeunitID@1000000002 : Integer;CodeunitName@1000000001 : Text[30];FunctionName@1000000000 : Text[30];Success@1000000003 : Boolean);
BEGIN
  TestResult.INIT;
  TestResult."Entry No." := 0;
  TestResult."Codeunit ID" := CodeunitID;
  TestResult."Codeunit Name" := CodeunitName;
  TestResult."Function Name" := FunctionName;
  TestResult."Test Start" := Before;
  TestResult."Test End" := CURRENTDATETIME;
  IF  Success THEN
    TestResult.Status := TestResult.Status::Success
  ELSE BEGIN
    TestResult.Status := TestResult.Status::Failure;
    TestResult."Error Text" := GETLASTERRORTEXT;
  END;
  TestResult.INSERT(TRUE);
END;

TestResult global variable is using new table I have created. You can create it easily, just look at the code, it is clear which datatype is used and how to name the fields. The PK of the table is AutoIncrement=Yes.

4th Step – Run the test

Ok, what to say – Run the Test Runner… :-D

Now you can play with the tested function and e.g. modify the logic to return wrong error or wrong result. All will lead to Failed test.

 

Conclusion

Ok, now you have imagination how it works. But, are we able to use it to test some complex functionality? That’s the question. I do not know. I am a beginner in Test Driven Development, I can imagine using it for some basic functions, may be that when we will test each function, it will mean that complex functionality is tested too, but I cannot imagine that Customer will want to pay for the time we will spend by creating the test functions. Or, may be he will pay, if the result will be much less bugs which costs money too… What’s your opinion on this? Is it possible to create e.g.. Fully tested Addon? Biggest problem will be the GUI and functionality connected to the GUI, e.g. when I wanted to test the Availability warning, if it is correctly displayed, I was not able to do it, because the warning isconditioned by CurrFieldNo and I am not able to simulate it (the warning is not displayed when the functionality is called from code).

2 thoughts on “Functional Testing in NAV 2009 SP1”

  1. Hi,
    I have recently downloaded this NAV2009 SP1, but could not find this Property of codeunit “Sub-type”, can you please guide me on this?

  2. Sorry to bother you, I found the answer, basically one needs to do the following to see the sub-type property:
    Tools>> Options>>Show C/AL Testability properties

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>