http://blogs.clariusconsulting.net/kzu

Daniel Cazzulino's Blog

Go Back to
kzu′s Latest post

PowerShell: how to unit test your cmdlet

If you miss VS, intellisense, TD.NET, etc., you might want to try extending PowerShell with custom cmdlets, which are .NET classes deriving from Cmdlet. They allow you to extend PowerShell while still programming in your favorite language.

Read Pablo Galiano’s post for a step-by-step introduction to Cmdlets.

I’m hooked to PowerShell. It’s been really fun to learn, and I’m loving it. 

I’m also hooked to Test Driven Design (that’s what TDD should mean, IMO), so I naturally looked for a way to develop my cmdlets in a TDD way. Turns out that it’s fairly easy. For this example, I will show a simple cmd-let that should load an assembly in a flexible way (by file name, full name or partial name).

First, create your unit test (ha! you thought I was going to create the cmdlet first?? :p):

[TestMethod]

public
void ShouldCreateCmdLet()

{

    LoadCommand cmd = newLoadCommand();

 

    Assert.IsTrue(cmd isCmdlet);

}

 

In order to get that test to compile, create your class deriving from CmdLet:

 

[Cmdlet("Load" , "Item", DefaultParameterSetName="Item")]

public
class
LoadCommand : Cmdlet

{

    protectedoverridevoid ProcessRecord()

    {

        base.ProcessRecord();

    }

}

 

For comprehensive guidelines on CmdLet development, see the MSDN documentation. 

Next, create the test that passes input to the cmdlet. Here’s where the real cmdlet testing occurs:

 

[TestMethod]

public
void ShouldLoadAssemblyWithFileName()

{

    string asmFile = this.GetType().Module.FullyQualifiedName;

 

    LoadCommand cmd = newLoadCommand();

    cmd.Item = asmFile;

 

    IEnumerator result = cmd.Invoke().GetEnumerator();

 

    Assert.IsTrue(result.MoveNext());

    Assert.IsTrue(result.Current isAssembly);

 

    Assert.AreEqual(this.GetType().Assembly.FullName, ((Assembly)result.Current).FullName);

}

 

Note that there’s no way to call ProcessRecord directly. The way to run the cmdlet is to call Invoke, and getting an enumerator from it. Remember that when placed in the pipeline, the cmdlet will be called once for each input in the pipeline. Let’s now implement the cmdlet to make the test pass (and compile!):

 

[Cmdlet("Load" , "Item", DefaultParameterSetName="Item")]

public
class
LoadCommand : Cmdlet

{

    private string assembly;

 

    [Parameter(Mandatory=true, ValueFromPipeline=true, ParameterSetName="Item", Position=0, HelpMessageResourceId="LoadCmdlet_Item")]

    public string Assembly

    {

        get { return assembly; }

        set { assembly = value; }

    }

 

    protectedoverridevoid ProcessRecord()

    {

        base.ProcessRecord();

 

        if (File.Exists(assembly))

        {

            WriteObject(Assembly.LoadFrom(fileName));

            return;

        }

    }

}

 

Note that in order to return output from your cmdlet, you call WriteObject. It’s interesting to debug the test we wrote. You will notice that the call to Invoke doesn’t actually execute the cmdlet. Instead, moving the enumerator does. This is how the pipeline achieves lazy evaluation of each “step” and continues executing with the following commands. Very cool.

And that’s pretty much all there is to it. Now you can start adding tests and the corresponding features to your cmdlet, with the amazing piece of mind that comes from having a unit test that says that it actually works ;) . Needless to say, this test-code-run is much faster than testing the cmdlet directly in PowerShell (you need to constantly exit PS and re-enter, re-add your snapin, etc., otherwise the output assembly gets locked).  

Comments