Debugging PowerShell Modules with Pester Unit Tests in Visual Studio Code

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

Description: If you are the author of a PowerShell module, you can use the built-in debugging tools in Microsoft Visual Studio Code to debug your script modules. In this video, we'll build a simple PowerShell script module, and then use the Pester unit testing module to write some failing tests. Then, we'll set a breakpoint in our module and break into the debugger when we execute the Pester unit tests. Once execution is paused, we'll step through the module code line by line, to discover where things are going wrong. We'll then fix the module code, and ensure that our Pester unit tests are passing.

👍︎︎ 1 👤︎︎ u/pcgeek86 📅︎︎ Feb 07 2020 🗫︎ replies
Captions
[Music] [Music] hi guys my name is trevor sullivan in this video i wanted to take a few moments to talk to you about how to debug a PowerShell module inside of microsoft visual studio code now this video was actually requested by one of my viewers so please feel free to leave any comments on any of my videos or on my channel somewhere with any recommendations about different videos that you would like to see or different topics that you'd like to see me talk about on my channel alright so in front of me here I am on my Mac OS system and I have Visual Studio code installed now if I hit command B and then do command shift X that'll switch me over to the extensions pane and what you'll notice is that I have the PowerShell extension installed here which is very well supported by Microsoft and I also have PowerShell core Edition installed on my Mac as well so to test to make sure that PowerShell is up and running what I'll do is just go ahead and hit command B to close that I'll do command n to create a new file and then do you command km and set the language mode in this file to PowerShell now also hit command Tildy and that will bring up my terminal down here at the bottom so because it's not responding I'm just going to do a command shift P and do PowerShell restart current session and as you can see that gets it back up and running to where it should be so what we're gonna do now is author a very basic PowerShell script module we're not going to talk about compiled modules that are written in c-sharp today that's just outside the scope of this particular video but I did want to start out by just building a very basic script module so the module itself will be created in PowerShell code we'll save that as a separate module file and then we will use the powershell pester module which is actually a unit testing module for powershell that allows us to write automated tests against our module and by writing automated tests you can help increase the robustness of your code and make sure that as you're changing things and refactoring your module that you are not breaking existing functionality in the past so we're going to build that module we're going to create a unit test and then we're going to use that unit test and show how to actually invoke the debugger from the unit test so let's go ahead and jump into our code here and the first thing we're gonna do is start by authoring the module so what I'll do is I'll create a couple of PowerShell classes and as you may know from my other video content PowerShell classes are basically just a way for you to create a blueprint of an object so you may have a car a person a house motorcycle something like that and you just want to represent it with different properties and interact with that object using methods or funk bass eclis functions that you define at that class level so let's start by defining a car so a car might have a string that's called manufacturer and it might have a second one called model we don't need to get too crazy here because the specific properties and methods that are on our classes are kind of irrelevant to this particular video so let's go ahead and just use the alt shift down arrow to paste that or to basically copy those first four lines and duplicate it and then we will change this to a person class and we'll do a first name and last name property on the person class okay so now we have two different object blueprints created now what I'm going to do is just create a very simple function called get something and what this command is going to do or what this function is going to do is have two different parameters and depending on which parameter you specify it'll either return a car object or a person object so what we need to do is specify a couple of switch parameters and a switch parameter is kind of like a boolean parameter but you don't actually have to specify a value of true or false like you do with a boolean a switch parameter in PowerShell is either present or not present so we'll take a look at how that works in just a moment here so we'll add a switch parameter that says car so we'll invoke the function with a car parameter and that'll omit a car or will have a switch parameter called person and that will omit a person object so now what I need to do is I need to make sure that the function can't be called with both parameters right I only want it to be called with either car or person however the way that it's written right now will actually allow someone to call the get something function with both the car and the person switch parameters together so PowerShell provides a facility that allows you to break apart parameters and kind of group them into logical sets and they're very logically called parameter sets so the way that we define a parameter set on a parameter a function parameter is to use the parameter attribute in the square brackets and then we can specify a parameter set name and so we'll say this is the car parameter set name so if I specify the switch parameter of car then the function is going to interpret that as being called as the entire function being called with this set of parameters now each set of parameters each parameter set that we're defining here is only going to have one parameter in it but just keep in mind for the future if you're designing other functions that you can actually group them together and have to three for as many parameters as you want inside of each parameter set so I'm just going to use the little option shift down arrow or alt shift down arrow if you're on PC again to copy that parameter attribute down and then we'll create a second parameter set called person so now if I call get something - person with that switch parameter then we will get the the command will be invoked with the parameter set name of person and we'll take a look at actually how to consume this in just a moment here when we implement the function body which is next so inside the function body we just need a simple statement that says if I could also use a switch statement but I'm just going to use a simple if statement here and so I'll say if PS command ilat so PS command is a an automatic variable in powershell inside of your function body as long as you have an advanced function that's denoted by the command let binding attribute here with the parameter statement as long as you have a function with that definition you get this PS command let variable that has a bunch of V very useful metadata about your functions execution environment when it's when it's called so what I can do is say if dollar PS command let dot parameter set name equals car then do something here else if PS command let dot parameter set name equals person than do this so what I'm going to do here is I'm going to intentionally break the function okay so what we're gonna do here is say if the parameter set name is car I'm actually going to return a person right and that's not the functionality we want but to demonstrate how the debugging works inside a vs code here we're going to just intentionally break it for now so we'll do person new and we didn't specify any custom constructors inside of our classes so when we call the new static method it's going to instantiate the object but none of our properties are actually gonna be set so the manufacturer model on a car or on a person object to the first name and last name properties those are actually going to be empty because they're not being set in the constructor and we're not going to add extra lines of code just for the sake of setting them so just keeping keep that in mind typically if you are actually implementing a useful class then you would want to either define a constructor or either or manually set those properties in additional lines of code but that's not what we're gonna do here so if the parameter set name here is going to be person then we are going to incorrectly return a car object and if you want to be a little bit more explicit about that you can also use the returns so only one of these will get called either a returned person or return car and it'll be incorrect for each of these but we're gonna write tests that actually catch that error and then we'll use those tests to actually step into our function code and actually see what's happening in the debugger and then we'll be able to use that debugger feedback to help us fix the function and get it back into a working state where the tests are passing okay so I've created this little module here where I've defined two classes and I've defined a single function so what I'm gonna do is go ahead and save this as a module file which is just a PowerShell script file with a dot PSM one file extension not a PS one extension like a script would have so what I'll do is I'll call this PS I don't want to use any conflicting names here so I'll call this cars two PS m1 so my module name is basically going to be cars - all right so now I've created this module and as you can see it changed to a pink icon here because it's actually a module now instead of a script so what I'm going to do is double click up here in the in the bar or I can do command n to create another file I'll do command K and then M set my language mode to PowerShell and it changed to blue indicating that's it's a PowerShell script file now so what I need to do first of all is I need to import my module so to do that I'll do import module - name and then the file system path to my module file the PSM one file so I'll do dollar home slash cars - PS m1 and if I do function f5 to run that you'll see my module is imported successfully and I can just verify that here interactively in the terminal by running the get module or the GMO alias which is the built in alias for get module and you'll see that I have this cars - module imported into my session and the cars - module exports a single function called get something so now I need to start writing my tester tests and even though you don't see pester in the list of modules here that's only because I haven't imported it into my PowerShell environment yet if you want to check if a module is installed but not imported you can always do get module list available and then optionally you can specify the name of the specific module that you're looking for so I can see that I do actually have pester installed it just hasn't been imported yet because I haven't called any of the functions that are exported by that module and that's a feature called module Auto loading in PowerShell which is enabled by default if PowerShell module Auto loading was not enabled I would either need to put a comma here and say also import pester or I could just duplicate this line down and be a little bit more verbose and say okay I'm importing cars too plus I'm also going to import pester but because of PowerShell module Auto loading I don't have to worry about manually importing pester because it's in my PS module search path whereas the reason I have to explicitly import my cars module is because my home directory is not in the PS module path search directory so in pester I don't want to get too deep into the weeds here but pestered has a top-level command called describe so basically that's just a mechanism to kind of group your different unit tests together so I'll say describe cars to so basically describe all the unit tests that pertain to my cars to module and then inside here you can optionally put a context block that allows you to further provide subgroups of your different individual unit tests I'm gonna skip over that for the sake of brevity and instead I'm just going to create an it statement so an it statement is a unit test and so what I'll say is it should return a car and inside of my block here what I'm gonna do is say get something - car should return a car object right and the way that we determine what type of object it is is we call the get type method so this is a dotnet concept any you've dotnet object should have a get type method it it's part I think it's defined at the system.object kind of route class that every object really inherits from but well we are what we want to do is basically take the output of the get something function and whatever object it is that that function call returns we want to call get type on that object and when we get the output of that get type method call we should have so we should say should be so the output of that should be a car so let's go ahead and do function f5 and run this now what you'll see down here in the terminal let me just move this up a little bit is that it's running my test here so it says run the describe block which is cars two and then inside there there's only a single unit test and it took ninety two milliseconds to run and it says it should return a car right so what does it say right below that so it ran this unit test that I wrote and it said it expected a car because I said should be car so that's what the unit test was expecting it to be however it actually got a person right so that unit test is failing so we need to add a second test because we have two different use cases for this function that we want to test so get something we're sorry it should return a person here let me just fix the capitalization so get something - person dot get type should be a person right so let's do function f5 to run that and now we have two unit tests that both failed right so expected car got person expected person but got car and we intentionally caused that breakage if you recall when we implemented our function earlier so now we need to debug the function that we wrote the get something function that we wrote because clearly the get something function is returning output that we're not expecting it to in two different use cases right all right so what we need to do first is set a breakpoint on the lines that are failing so what I can do here is do function f9 or if you're on a PC keyboard just f9 I'm on a Mac keyboard here and what you'll do what you'll see is that there's a breakpoint here on the left-hand side and what we can do is if we do function f5 to run this script from top to bottom actually sorry I need to set my breakpoint inside my module not inside of my script file so what I need to do is come over to the get something function and I'm going to specify a breakpoint on the first line of the function body so I'll do function f9 on line number 20 here so that as soon as it hits this first if statement it'll it'll break execution all right so let's do let's go back to our test our unit test file and we will run that so we executed our unit test file with f5 and as you can see because we have this cars2 module imported into our powershell environment it actually broke execution here on line number 20 and so if we do command shift d typically for these presentations I minimize the side bar with command B that just allows you to toggle the side bar on and off but it keeps it kind of nice and clean not to have it there but if you do command shift D it'll switch you over to the debugger context inside a visual studio code and as you can see here you actually have these variables that have been populated by the runtime by the powershell runtime from this unit test script that i started so what's happened here is we've called the get something function and if we drill into our local variables here here's this PS command let variable this automatic variable that I told you about before that has that parameter set name property on it and if we keep scrolling down here a little bit you'll see that the PS command variable has a child property called parameter set name and that in the current execution environment because we're broken into the debugger the current value of it is car because the first unit tests that ran was calling get something with the car switch parameter which is mapped to a parameter set name so when we specify that switch parameter it's calling the function with the car parameter set name right so on our second unit test when it loops back through and runs our second unit test this parameter set name is actually going to be person instead so because we can visually inspect and we can we can see in front of us here that the parameter set name is equal to car we're fairly confident that the next line it executes will actually be line 21 so let's verify that by saying step over or f10 if you prefer to use the keyboard shortcuts for it and sure enough it jumped over to line 21 so it's going to this function is ultimately going to invoke this return statement and return a person object here so immediately I can see that even though the parameter set name was car what's actually being returned by this function is this person object so that's wrong right so let's go ahead and cancel execution here because we've identified what the problem is so we need this car parameter set name to return a car so we'll save that and come back to our unit test file so let's go ahead and rerun this and see what happens so I'll hit command B to close my little sidebar again so I'm gonna hit function f5 rerun the script and we broke again on line number 20 because that's where we expected it to break and let's take a look at parameter set name so PS command lit if I hover over it you'll see I have parameter set name as car perfect that's what we expect from the first unit test and then we'll do f10 and f10 again to step out of the function and now it's going to actually break into the pester module we don't need to debug the pester module so what we're going to do is actually just continue execution through to the end by hitting f5 or the continue function and so now it's going to run our second unit test so we got the output from our first unit test which actually failed wait wait why why did that fail why did our first unit test fail if we fixed the code so when we called get something with car it should be a car but in my module file I fixed the code from person to car so it should be passing now right well let's do f5 and just finish our unit tests now both are still failing but our first unit test we fixed so what's going on here well because even though we made a change to our module file and even though we have this import module statement at the top of our script our unit test script it isn't actually remodel what's happening is it's saying oh the modules already imported I'm not going to re-import it and it's going to basically not silently fail but it's going to silently proceed right so what you actually have to do in PowerShell is use the dashed force parameter that we just added here on line number one for your import module statement and that will force PowerShell to actually reprocess your module file and re-imported into the environment so now that we've added the force parameter let's go ahead and it have five and we're going to brake on our first unit test again so let's do F 10 F 10 F 10 and I'll section just do function of five it's going to break again because it's running our second unit test now and now check this out our first unit test actually passed right and yeah the execution time was 10 seconds because we were sitting in the debugger for quite a while there as we were talking through things but as you can see our first unit test passes and now our second unit test is broken so all we have to do is do the same thing so we'll rerun our unit test script we'll hit function f5 to pass the first unit test and then we'll break again on line 20 here in our module when the second unit test runs so now if I hover over the parameter set name it's equal to person on the second unit test because we specified the person switch parameter which maps to a parameter set named named person so it's not going to equal car so it's going to skip over line 20 skip over line 21 and 22 and then it's going to jump to line 23 to run our else--if statement so there we go if we hit f10 or function f10 on my Mac keyboard to do the step over function with the debugger it's going to jump to line 23 and sure enough parameter set name does equal person so we're rather confident that it'll jump to line 24 so it's returning a car and as we discussed earlier it's supposed to be returning a person so let's go ahead and fix that one really quickly because you get the idea I think right so we want that parameter set name to return a person let's switch back to our so now that we're confident we fixed that let's actually remove our breakpoint because we don't need it anymore we've used it for its purpose so let's switch back to our unit test do you function f5 and sure enough our tests are now passing so there you go so that's an overview of how to use the debugger in Microsoft Visual Studio code to debug a PowerShell script module and it also kind of demonstrates how to use some of the basics of pester unit testing there's a guy named Jacob over in Europe who has done a very good job of building pester over the years so huge shout out to him for all the work you've done on pester thank you very much for that unit testing module it's great that it's a core part of PowerShell now all right that about sums up this video please leave a like if you liked this video please leave a comment down below if you'd like to share any additional feedback about my videos and please hit the subscribe button it really helps encourage me to produce more content for you guys and please keep coming back to the channel to consume more of my videos I make it for you guys so please let me know what you like thank you Cheers you
Info
Channel: Trevor Sullivan
Views: 2,746
Rating: 5 out of 5
Keywords: Microsoft, PowerShell, Visual Studio Code, VSCode, Debugging, Developer, Programming, Unit Testing, Software Testing, Automation, Coding, Learn to Code, Scripting, Shell, Administrator, Sysadmin, Information Technology, Computer
Id: ORgJCAhigs8
Channel Id: undefined
Length: 24min 37sec (1477 seconds)
Published: Thu Feb 06 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.