There we go. Let’s turn to our companion on this journey, our Test List, pick out our first test:
# |
Description | Status |
1 |
Create PI (purchase invoice) with 1 line and check that total line amount is calculated right | In Progress |
2 |
Create PI with multiple (2) lines and check that total line amount is calculated right | |
3 |
Create PI with multiple lines and check that doc. amount verification succeeds | |
4 |
Create PI with multiple lines and check that doc. amount verification fails |
… and sing the RED/GREEN/REFACTOR mantra:
1. Write our first test code
So let’s open our NAV classic client (CC) and create our first test codeunit:
… and define our first test based on our Test List entry #1, name it PIwithOneLine and first make it create a purchase header:
PIwithOneLine()
// Create a purchase header
PurchHeader.INSERT(TRUE); //TRUE ensures that the system assigns a unique number to the header
Let’s compile:
Of course, no surprise; lazy me counting on the compiler, my other companion, to tell me to define the PurchHeader variable.
Now continue to create one purchase line to this header (and not to forget the define the appropriate variable). The function will look like this:
PIwithOneLine()
// Create a purchase header
PurchHeader.INSERT(TRUE); //TRUE ensures that the system assigns a unique number to the header
// Create one purchase line to the header
PurchLine.”Document No.” := PurchHeader.”No.”;
PurchLine.”Line Amount” := 1;
PurchLine.INSERT;
I can tell you: it compiles and we are set to complete this test code. We have a purchase header and a line and now we want to check if the amount on the line (indeed only one, which feels stupid, doesn’t it?) equals the total document amount calculated on the header. For this we could write a simple IF-statement, but we will be using one of the functions in the the test libraries provided by MS: codeunit 130000 (Assert). This way we assure that our test code contains easy recognizable (test) patterns.
In our case we will apply the AreEqual method from the Assert library; i.e we assert that the amount on the line is equal to the calculated (total) line amount:
PIwithOneLine()
// Create a purchase header
PurchHeader.INSERT(TRUE); //TRUE ensures that the system assigns a unique number to the header
// Create one purchase line to the header
PurchLine.”Document No.” := PurchHeader.”No.”;
PurchLine.”Line Amount” := 1;
PurchLine.INSERT;
//Check if line amount equals the line amount calculated by CalcDocAmount
Assert.AreEqual(PurchLine.”Line Amount”,PurchHeader.CalcDocAmount,‘Calc. Doc. Amount’)
Our first test is ready so we will …
2. Compile the test code
… which shows RED:
Indeed we so far only created our test and against this we will …
3. Implement just enough to compile
… i.e. implement just enough production code to get it compiled. What do we do and where do we do this?
We build the CalcDocAmount function there where it belongs: on the Purchase Header table (38), returning an amount, being of type decimal:
CalcDocAmount() Amount : Decimal
Is that all? Yep, that’s all. It compiles. “Just enough to compile”, you know.
Does it make sense? According to the mantra it does, but probably in the context of all our experience in writing code for NAV applications, nope. Feel it itching? Just let and never mind, we follow the rules of the TDD game and …
4. Run the test and see it fail
Well … almost. We first have to compile codeunit 60000 to be able to run it. Pfff, it compiles! [^]
Run:
Aha, this is new (for those who have never worked with the testability features): the result window of a single test codeunit! It’s telling us that the PIwithOneLine test function failed (first FAILURE), making BTW the whole run fail (last FAILURE). In between we get informed on the result of the Assert.AreEqual statement. The expected value (of the CalcDocAmount function as you might recall) was 1, being the amount of that single purchase line, but turned out to be 0. Sure, my CalcDocAmount function did not return any specific value, so it yields by default 0.
OK, let’s go for GREEN as fast as possible and …
5. Implement just enough to make the test pass
CalcDocAmount() Amount : Decimal
EXIT(1)
[:O]
Indeed just enough “to make the test pass”. Fake it till you make it!
Compile and ready to …
6. Run the test and see it pass
…? Are you? I am, so here we go: GREEN
7. Refactor for clarity and to eliminate duplication
Anything to refactor? Looking at both the production and test code we wonder if we have something to refactor. No duplication of code to be found, hence we could be moving on to the next step.
Even though we have no duplication we might want to make our code a bit more generic, i.e. make it work for any “one lined” purchase invoice having any amount on the line. Consequently we …
8. Repeat from top
…
1. (Re)write our first test code (second time)
Let’s change the code a little bit so that the line amount is ‘generic’:
PIwithOneLine()
// Create a purchase header
PurchHeader.INSERT(TRUE); //TRUE ensures that the system assigns a unique number to the header
// Create one purchase line to the header
PurchLine.”Document No.” := PurchHeader.”No.”;
PurchLine.”Line Amount” := RANDOM(100);
PurchLine.INSERT;
//Check if line amount equals the line amount calculated by CalcDocAmount
Assert.AreEqual(PurchLine.”Line Amount”,PurchHeader.CalcDocAmount,‘Calc. Doc. Amount’)
And now …
2/3. Compile the test code (second time)
¡No problema!
4. Run the test and see it fail (second time)
By all means CalcDocAmount can no longer fake it: it’s failing clearly.
Nothing is going to stop us to get GREEN … so let’s …
5. Implement just enough to make the test pass (second time)
Thus we have to be more intelligent and collect the line amount from the relevant purchase line:
CalcDocAmount() Amount : Decimal
PurchLine.SETRANGE(“Document No.”,”No.”);
IF PurchLine.FINDFIRST THEN
EXIT(PurchLine.”Line Amount”);
EXIT(0)
Compile and ready to …
6. Run the test and see it pass (second time)
GREEN !!!!!
7. Refactor … (second time)
Anything to refactor? No.
8. Repeat from top
We have completed the first entry on our Test List and are ready to move to the next. Stay tuned for my next blog entry. [O]
Notes
- NAV CC should be NAV 2009 SP1 or higher
- we are using NAV w1
- by default every function defined in a codeunit of subtype Test will have FunctionType set to Test
- Update 20190407 this is not true when developing in VSCode
- we do nothing more and nothing less than it’s needed, both in writing our test and production code