Frost has hit the night. Sitting in our Finnish fireplace heated kitchen, looking at our garden wall, Wheather Report humming in the back, and having done my preps last week, I am ready to start writing another post on test automation. Being a protagonist of using standard tests and their associated libraries many have asked me: “Luc, what is the best way to find and use useful test helper functions?” So, that will be today’s topic: how to find and use standard test helper functions.
As the saying goes all roads lead to Rome, which applies to our enigma too. The goal of today’s post is to (1) address the different roads, i.e. tools, I have utilized so far, (2) effectively defining Rome, i.e. describing what we are searching for, and (3) showing the best way to use these standard helper functions. Let’s first address the roads.
The roads I have been walking can be roughly dived over two categories. A main category being the roads of source code. An alternative, but also extinct, category is the single road of index. This one is to be found on the product DVD up until (and including) NAV 2018. It is known as ALFXDOC.chm being a compiled html project with references to and information on all helper functions in the various test libraries. As it is extinct I will not discuss it in detail, but here is an example of where to find it.
So, how about the roads of source code?
Having been busy with test automation for some time already my start was way back in the times AL did not exist and, IMHO, too many of us did not use any formal way of source code management. For me the latter has been practice ever since I started working at the Global Development Localization team (GDL) at MS way back in 2003. Having all C/AL source as text files in a (local copy of a) source code repository enabled me to search for anything I was looking for. Then and now. Of course we did not yet have tools like Idyn’s OMA, Statical Prism or VSCode with AL Language extension that were NAV aware, but Notepad++ and Visual Studio were around and of great help.
Basically I always have made sure that I have the relevant source code available on my local system. Formerly a local copy of our source code repository, that would contain standard (test) code. Nowadays the downloaded artifacts of the BC version I am working on.
Now what tools do I use on these roads of source code?
It is clear that VSCode is becoming for me, and many of us, more and more the one-stop-shop to get my work done. It has a nice efficient and effective number of tools onboard, and can even be extended with powerful extensions. Most often I use the onboard file search like shown in the next screen utilizing the regular expressions (regex) to search for the global procedures that start with CreateLocation.
For this of course I need to have the source code available as text (.al) files. With the artifacts downloads and scripts provided by, for example, waldo it’s relatively easy to instantly get these files in place. I have been using waldo’s script to download all Microsoft Dynamics 365 Business Central source code with PowerShell a lot lately. In no time you have a local copy the source you need.
Where the file search needs all source code in text files, and waldo’s script needs get the source unzipped, AL Search can directly access the .app files. You only need to incorporate in your work folder all .app files you want to roam about in search of that standard helper function. Like for example those from:
AL Search is a premium feature of AL Studio and allows you to search in both the project source files and the . app files that reside in your project. The goals was to incorporate this into the VSCode file search feature, but this showed to be not extensible, so, for now you have to trigger it form the command palette (Ctrl+F9 or F1) by typing AL Search and press enter. At this moment it does not enable regex. See the result below for the search string procedure CreateLocation.
Always having been a fan of Statical Prism in the C/AL days, I have not been into it’s AL offspring that much yet. But the latter seems to do q good job. I can both operate it on a .al source files directory and a .app extensions directory. The nice thing about it is that it builds up a whole model of the source enabling you to easily jump form one to another object given their relation. See the result below for the search string CreateLocation.
Although I will be walking all three roads, my favourite road is the VSCode file search. First of all because it is VSCode where I am developing, secondly the regex possibilities empower me more, thirdly it feels that I can more easily jump into the source code. But of course compared to Statical Prism it’s less powerful from a go-to-definition perspective.
Now the roads have been addressed, how about our goal, Rome?
Having the tools is one thing, but how do we know that they lead to the right goal? Or in terms of helper functions, how do you know what to search for? It helps to know that most standard test helper functions do follow a similar naming pattern. The functions, like any well named function,
- starts with a verb, like
- followed by a noun the object it operates on, like
And of course your knowledge of the standard application will help to point you in the right direction as you know it does, for example, handle vendors and customers.
If I will be needing a service order in one of my tests I will typically search a global procedure that starts with CreateServiceOrder and see if the results point me to reusable standard helper functions.
- Notice that the result, depending on how strict your search string is defined, might hit also examples of how these helper functions have been applied, these can be very instructive.
- As another saying tells us Rome wasn’t build in one day, I had to learn how to search for possible applicable helper functions and how to reuse them. Meanwhile, btw, I also did learn a lot about how to build up my tests.
OK, all roads and Rome covered, i.e. we know how to find useful standard test helper functions, how about using them?
As with any global function you can call it anywhere if you are allowed to reference it, be it in a test function or any other normal function. I reckon it to be best practice to encapsulate any call to a standard (or actually any) library function into a local helper function. You might want to dig into xUnit Patterns on this topic. You can find abundant examples of this best practice in the standard MS tests (although not all their test codeunits do follow this practice, unfortunately). Having performed quite a number of workshops on test automation, I know it is very tempting for developers to not encapsulate and use direct calls to library helper functions. Encapsulation makes your test code better extensible and maintainable.
The following code examples (taken from one of my test automation examples on Github) illustrates the above.
[Test] procedure DeleteByUserWithNoAllowanceManuallyCreatedWhseShptLine() // [FEATURE] Unblock Deletion of Whse. Shpt. Line enabled var WarehouseShipmentNo: Code; begin // [SCENARIO #0001] Delete by user with no allowance manually created whse. shpt. line Initialize(); // [GIVEN] Warehouse employee for current user with no allowance CreateWarehouseEmployeeForCurrentUser(NoAllowance(), LocationCode); // [GIVEN] Manually created warehouse shipment from released sales order with one line // with require shipment location WarehouseShipmentNo := CreateManuallyCreatedWarehouseShipmentFromReleased SalesOrderWithOneLineWithRequireShipmentLocation(LocationCode); // [WHEN] Delete warehouse shipment line DeleteWarehouseShipmentLine(WarehouseShipmentNo); // [THEN] Warehouse shipment line is deleted VerifyWarehouseShipmentLineIsDeleted(WarehouseShipmentNo); end;
Having a test codeunit calling a local helper function CreateWarehouseEmployeeForCurrentUser.
local procedure CreateWarehouseEmployeeForCurrentUser( WithAllowance: Boolean; LocationCode: Code) begin LibraryUnblockDeletion.CreateWarehouseEmployeeForCurrentUser( WithAllowance, LocationCode); end;
The local CreateWarehouseEmployeeForCurrentUser encapsulates the library helper function CreateWarehouseEmployeeForCurrentUser.
procedure CreateWarehouseEmployeeForCurrentUser( WithAllowance: Boolean; LocationCode: Code) var WarehouseEmployee: Record "Warehouse Employee"; begin LibraryWarehouse.CreateWarehouseEmployee( WarehouseEmployee, LocationCode, false); if WithAllowance then begin WarehouseEmployee."Allowed to Delete Shpt. Line FLX" := true; WarehouseEmployee.Modify(); end; end;
The library helper function CreateWarehouseEmployeeForCurrentUser on its turn encapsulates and extends the standard library function CreateWarehouseEmployee.