Last time out we went through running automated tests from the PowerShell terminal integrated into VS Code. We saw that you could define a task in the tasks.json file to run the tests and assign a keyboard shortcut for that task.
Great. But. In order to run tests they first need to have been added to a test suite. You could add some code to an install codeunit in your app to do that for you (see here). That approach is nice and clean, but leaves a couple of things to be desired:
- What if you want to specify the test suite, or codeunit(s) that you want to use?
- How can you clear the test suite?
You can’t. Which is why we have a “Build Helper” app to add and remove test codeunits from test suites. It contains a single codeunit which is exposed as a web service which we call from PowerShell.
This is the codeunit and the PowerShell it is called with (see here if you can’t see the embedded gist).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
codeunit 90100 "Automated Test Mgt. BHTMN" | |
{ | |
var | |
CALTestManagement: Codeunit "CAL Test Management"; | |
ObjectNotCompiledErr: Label 'Object not compiled.'; | |
procedure GetTests(TestSuiteName: Code[10]; StartID: Integer; EndID: Integer) | |
var | |
CALTestSuite: Record "CAL Test Suite"; | |
CALTestLine: Record "CAL Test Line"; | |
AllObjWithCaption: Record AllObjWithCaption; | |
begin | |
GlobalLanguage(1033); | |
if TestSuiteName = '' then | |
TestSuiteName := 'DEFAULT'; | |
if not CALTestSuite.Get(TestSuiteName) then begin | |
CALTestSuite.Init(); | |
CALTestSuite.Name := TestSuiteName; | |
CALTestSuite.Description := 'Automated Testing'; | |
CALTestSuite.Validate(Export, false); | |
CALTestSuite.Insert(true); | |
end; | |
CALTestSuite.SetRecFilter(); | |
if StartID = 0 then | |
StartID := 9000000; | |
//add test codeunits | |
AllObjWithCaption.SetRange("Object Type", AllObjWithCaption."Object Type"::Codeunit); | |
AllObjWithCaption.SetRange("Object Subtype", 'Test'); | |
if EndID > 0 then | |
AllObjWithCaption.SetRange("Object ID",StartID,EndID) | |
else | |
AllObjWithCaption.SetFilter("Object ID",'%1..',StartID); | |
AddTestCodeunits(CALTestSuite, AllObjWithCaption); | |
//add test methods | |
CALTestLine.SetRange("Test Suite", TestSuiteName); | |
CALTestLine.SetRange("Line Type", CALTestLine."Line Type"::Codeunit); | |
if CALTestLine.FindSet() then | |
repeat | |
CALTestManagement.RunSuite(CALTestLine, false); | |
until CALTestLine.Next() = 0; | |
CALTestLine.SetRange("Line Type"); | |
if CALTestLine.FindFirst() then; | |
end; | |
procedure ClearTestSuite(TestSuite: Code[10]) | |
var | |
CALTestLine: Record "CAL Test Line"; | |
begin | |
CALTestLine.SetRange("Test Suite",TestSuite); | |
CALTestLine.DeleteAll(true); | |
end; | |
local procedure AddTestCodeunits(CALTestSuite: Record "CAL Test Suite"; VAR AllObjWithCaption: Record AllObjWithCaption) | |
var | |
TestLineNo: Integer; | |
begin | |
if AllObjWithCaption.FIND('–') then begin | |
TestLineNo := GetLastTestLineNo(CALTestSuite.Name); | |
repeat | |
TestLineNo := TestLineNo + 10000; | |
AddTestLine(CALTestSuite.Name, AllObjWithCaption."Object ID", TestLineNo); | |
until AllObjWithCaption.Next() = 0; | |
end; | |
end; | |
local procedure GetLastTestLineNo(TestSuiteName: Code[10]) LineNo: Integer | |
var | |
CALTestLine: Record "CAL Test Line"; | |
begin | |
CALTestLine.SetRange("Test Suite", TestSuiteName); | |
if CALTestLine.FindLast() then | |
LineNo := CALTestLine."Line No."; | |
end; | |
local procedure AddTestLine(TestSuiteName: Code[10]; TestCodeunitId: Integer; LineNo: Integer) | |
var | |
CALTestLine: Record "CAL Test Line"; | |
AllObj: Record AllObj; | |
Object: Record Object; | |
CodeunitIsValid: Boolean; | |
begin | |
with CALTestLine DO begin | |
if TestLineExists(TestSuiteName, TestCodeunitId) then | |
exit; | |
Init(); | |
Validate("Test Suite", TestSuiteName); | |
Validate("Line No.", LineNo); | |
Validate("Line Type", "Line Type"::Codeunit); | |
Validate("Test Codeunit", TestCodeunitId); | |
Validate(Run, true); | |
Insert(true); | |
AllObj.SetRange("Object Type", AllObj."Object Type"::Codeunit); | |
AllObj.SetRange("Object ID", TestCodeunitId); | |
AllObj.FindFirst(); | |
if Format(AllObj."App Package ID") <> '' then | |
CodeunitIsValid := true; | |
if not CodeunitIsValid then begin | |
Object.SetRange(Type, Object.Type::Codeunit); | |
Object.SetRange(ID, TestCodeunitId); | |
CodeunitIsValid := Object.FindFirst(); | |
end; | |
if CodeunitIsValid then begin | |
CALTestManagement.SETPUBLISHMODE(); | |
SetRecFilter(); | |
Codeunit.Run(Codeunit::"CAL Test Runner", CALTestLine); | |
end else begin | |
Validate(Result, Result::Failure); | |
Validate("First Error", ObjectNotCompiledErr); | |
Modify(true); | |
end; | |
end; | |
end; | |
local procedure TestLineExists(TestSuiteName: Code[10]; TestCodeunitId: Integer): Boolean | |
var | |
CALTestLine: Record "CAL Test Line"; | |
begin | |
CALTestLine.SetRange("Test Suite", TestSuiteName); | |
CALTestLine.SetRange("Test Codeunit", TestCodeunitId); | |
exit(not CALTestLine.IsEmpty()); | |
end; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function Get-TestCodeunitsInContainer { | |
param ( | |
# Container to load test codeunits into | |
[Parameter(Mandatory=$false)] | |
[string] | |
$ContainerName = (Get-ContainerFromLaunchJson), | |
# Credentials to use to connect to web service | |
[Parameter(Mandatory=$false)] | |
[PSCredential] | |
$Credential = (New-CredentialFromEnvironmentJson), | |
# Name of the test suite to add the test codeunits to | |
[Parameter(Mandatory=$false)] | |
[string] | |
$TestSuite = '', | |
# Start of the range of objects to add | |
[Parameter(Mandatory=$false)] | |
[int] | |
$StartId = ((Get-AppKeyValue –SourcePath (Get-Location) –KeyName 'idrange').from), | |
# End of the range of objects to add | |
[Parameter(Mandatory=$false)] | |
[int] | |
$EndId = ((Get-AppKeyValue –SourcePath (Get-Location) –KeyName 'idrange').to) | |
) | |
Install-BuildHelper –ContainerName $ContainerName | |
$CompanyName = Get-ContainerCompanyToTest –ContainerName $ContainerName | |
$Url = "http://{0}:7047/NAV/WS/{1}/Codeunit/AutomatedTestMgt" -f (Get-NavContainerIpAddress –containerName $ContainerName), $CompanyName | |
Write-Host "Calling $Url to retrieve test codeunits" | |
$AutomatedTestMgt = New-WebServiceProxy –Uri $Url –Credential $Credential | |
$AutomatedTestMgt.GetTests($TestSuite,$StartId,$EndId) | |
} |
I think the AL is pretty self-explanatory. The PowerShell is a little more interesting.
Install-BuildHelper acquires the latest version of the app from the build artefacts (as described here). It checks whether it is already installed first by attempting to reach the address of the WSDL (http://<server + port>/NAV/WS/Codeunit/AutomatedTestingMgt). I’ve found that to be a little faster than Get-NavContainerAppInfo.
It uses New-WebServiceProxy (as described here) to call the web service methods.
Get-ContainerCompanyToTest fetches the name of (usually) the first company in the container to call the service against. It does this calling the SystemService web service rather than Get-CompanyInNavContainer – again, as it is slightly faster.
By “faster” I mean a couple of seconds. That’s trivial compared to the execution time of the test suite but given that I’m trying to move to a tighter {develop test -> run tests -> develop app -> run tests} loop I’ll take the saving.
I’m a fan of default values so it:
- Takes the container from launch.json
- Creates a credential object using the credentials we store in our environment.json file
- Takes the start and end of the range of codeunits to add from the idrange set in app.json
There is a Clear-TestSuite function as well. I haven’t bothered pasting it here because it’s just a simplified version of Get-TestCodeunitsInContainer.
Thanks Mate
LikeLike