Below
is the folder structure of an extension I am working on. I want to test all four tasks in a single
project using the PowerShell
Tools for Visual Studio extension.
Dot Sourcing files
When
I use the PowerShell Tools for Visual Studio, I link the files to my test
project so as to not disrupt the task folder structure.
By
simply linking to the file, it is not copied to the test project folder. Because the files are not in the test project
folder, this can cause issues when you try to dot source a file to load
additional functions.
If
you want to dot source a file the standard way,
. .\utility.ps1
it
will only work if you copy the files to the project folder. If you were to run the following command in a
linked file,
(Get-Item -Path
".\" -Verbose).FullName
you
will see that the working folder is not the folder of the file. It is the
working folder of Visual Studio.
C:\Program
Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE
Because
of this . .\ will not find the desired file.
You can overcome this by using the PSCmdlet.MyInvocation property of
PowerShell like this.
. (Join-Path ($MyInvocation.MyCommand.Path.Replace($MyInvocation.MyCommand.Name,"")) utility.ps1)
This
form will work anywhere regardless of if the file is linked or not. This line is getting the Path to the
currently executing script. This will include the filename as well. I use the
Replace function to remove the filename. Finally, the Join-Path takes the
resulting path and adds the name of the desired file which I know to be in the
same directory.
Importing Distributed Task modules
Many
custom tasks written for VSTS use PowerShell modules provided by the agent to
perform actions in our task.
# Import the Task.Common dll that has all the cmdlets
we need for Build
import-module "Microsoft.TeamFoundation.DistributedTask.Task.Common"
If
you were to simply run this command, you will get the following error.
For
this command to work, you must have the modules on your development
machine. You can harvest these files
from any machine currently running an agent.
Simply copy the contents of the
[agentInstallfolder]\agent\worker\modules folder to C:\Program
Files\WindowsPowerShell\Modules on your development machine.
You
will also need to install a few assemblies into the Global Assembly Cache (GAC)
depending on the modules you import. For
example, Microsoft.TeamFoundation.DistributedTask.Task.Common requires
Microsoft.TeamFoundation.DistributedTask.Agent.Interfaces.dll.
These
DLLs can be found in the Worker folder of an agent. Copy the required files to your development
machine and start a Developer Command prompt as Administrator. Then, using gacutil,
install the required DLLs into the GAC.
Make
sure you restart Visual Studio after installing files into the GAC. Now when you test your PowerShell, the module
will successfully import.
Test with Pester
At
this point, you are ready to start testing your task with Pester in Visual
Studio. There is already a lot of great
information on Pester, so I will not go in to great detail in this post.
The
test files are in the test project folder but the files we are testing are
not. At the top of each test file, you
need to dot source the linked file from its real location. Below is an example of how I brought in my
utility script from one of my tasks.
$here = Split-Path
-Parent $MyInvocation.MyCommand.Path
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.'
. "$here\..\DockerRunTask\$sut"
Notice
on the line where I dot source the file I have to move up an over a directory
into DockerRunTask to locate the file I want to test.
Mocking
There
are certain aspects of your task that are going to rely on VSTS functionality
that will not be present on your development machine. You can use Pester Mocking to fake those
portions for testing. Just remember that
Pester can only mock what it can find.
If you attempt to mock Get-ServiceEndpoint, which you will need to do if
your task uses service endpoints, you might get the following error.
You
can simply define an empty function with the same name above your test or
create a script full of fake functions and dot source the file into your test
project. How many functions
I need signatures for and how often I will need them determines if they are
defined above my test or in a separate file.
File vs Functions
Normally,
when using Pester, you dot source the script that defines lots of functions that
you would like to test. Then you simply call the methods in your tests. But what if your script does not define a
named function? You simply dot source
your file in your test. That way, it is not loaded too early and you can pass in
all the parameters you need, like below.
$here = Split-Path
-Parent $MyInvocation.MyCommand.Path
$dir = $sut = $MyInvocation.MyCommand.Path -replace
'dockerrun.Tests.ps1', ''
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.'
function Get-ServiceEndpoint { }
Describe "dockerrun" {
Context "Exists" {
# This is used to
Get-Docker
Mock Get-Item { return
@{FullName = "c:\temp"} } -Verifiable
# The path does
not really exist but I need this to return
# true anyway.
Mock Test-Path { return $true } -Verifiable
# This is a call
that returns the Service Endpoint
Mock Get-ServiceEndpoint { return @{"Url"="test"; "Authorization"
= @{"Parameters" = @{"key"="key"; "username"="username"; "password"="password"} } } }
It "Runs" {
. "$here\..\DockerRunTask\$sut" -connectedServiceName
"d314d0f0-4295-4537-84d5-897b8ef6bc4c"
-cwd "c:\temp" -imageName peopletracker -authOptions
tls -ports 8080:8080 -removeConflictingContainers
"False" -verbose
}
}
}
Notice
how I dot source file in the call to It.
I
hope this helps you speed up the development time for VSTS tasks.