Saturday, January 17, 2015

Creating your own unit testing framework for PowerShell - Part 1

Recently I started working on a light weight unit testing framework for my PowerShell modules. There are a lot of testing frameworks for PowerShell that can be executed as script files from a PS host, but not many allows you to integrate with VSTests and write test methods and classes in C#. The following posts in this series is about how you can create a unit testing framework for Windows PowerShell and use it.

Being a big fan of the Page objects pattern and have seen the benefits of how easily you can model a page and reduce the amount of duplicate code when creating UI tests for websites, I wanted to do something similar modules for PowerShell also. So when I started writing the framework, one of the main considerations was to simplify my testing process by modelling a PowerShell module in code.

I also wanted to follow a more declarative approach on defining the metadata needed to provide inputs for my tests and model, so I started to think about attributes that I need to model a PowerShell module.

 To define a module name and the location of the module, I created the PsModuleAttribute with a Name and a Path property, so that I can use this attribute on my PSModule model for the ModuleObject pattern implementation.

[AttributeUsage(AttributeTargets.Class)]
public class PsModuleAttribute : Attribute
{
    public PsModuleAttribute(string name)
    {
        Name = name;
    }

    [Required(AllowEmptyStrings = false, ErrorMessage = "A PS module name should be provided to test a module")]  
    public string Name { get; set; }
    public string Path { get; set; }
}
Next I wanted to define the functions and the parameters for these functions in my model. The functions in the PowerShell module can be simulated as properties in the ModuleObject. Once you have these properties defined, you can use the same approach of using attributes to define name, parameters, return value etc. 

[AttributeUsage(AttributeTargets.Property)]
public class PsModuleFunctionAttribute : Attribute
{
    [Required(AllowEmptyStrings = false, ErrorMessage = "A module should have a name")]
    public string Name { get; set; }

    public PsModuleFunctionAttribute(string name)
    {
        Name = name;
    }
}            
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class PsModuleParameterAttribute : Attribute
{
    [Required(AllowEmptyStrings = false, ErrorMessage = "A parameter should have a name")]
    public string Key { get; set; }

    [Required(AllowEmptyStrings = false, ErrorMessage = "A parameter should have a value")] 
    public string Value { get; set; }

    public PsModuleParameterAttribute(string key, string value)
    {
        Key = key;
        Value = value;
    }
}

In the framework, I created the CommandInfo object to wrap these values in the properties.

public class PsCommandInfo
{
    public string Name { get; set; }
    public IDictionary Parameters { get; set; }
    public Collection<PSObject> Result { get; set; }
}

The final implementation of the ModuleObject should look like.

[PsModule("xModule", Path = @"E:\MyModules\xModule")]
public class XModule
{
    [PsModuleFunction("Get-HelloMessage")]
    [PsModuleParameter("context", "VSTest")]
    public PsCommandInfo GetGreetings { get; set; }
}

Next we’ll see how to extract this information in a unit test context to execute it.

No comments: