Showing posts with label TDD. Show all posts
Showing posts with label TDD. Show all posts

Saturday, February 7, 2015

FluentShellUnit - Create a stub from script block

A stub is a test-specific replacement for a real object that feeds the desired indirect inputs to the system under test. FluentShellUnit supports stubbing by using a scriptblock that is loaded from a script file if the input to be provided by the stub/ the return value is a complex type. If the return value for a stub is a simple string, then you don’t need to use a script block.
For e.g, the below given method in the module calls the Get-SPWebApplication cmdlet that returns a SharePoint web application while invoked in the execution process of the method Get-WebApplicationTest

Function Get-WebApplicationTest{
      param([string] $webAppName)
      $webApp = Get-SPWebApplication -Name $webAppName
      return $webApp
}
In a test execution context, you don’t want to invoke the Get-SPWebApplication cmdlet using the SharePoint API. To create a stub for the Get-SPWebApplication cmdlet, you can now create a script file that contains a method Get-SPWebApplication and use your custom implementation there.

Function Get-SPWebApplication{
      param([string]$Name)
      @{"Url" = "http://dummywebapplication.nl"; "Description" = "Description of the webapplication"}
}

I’ve created a scriptfile WebApplication.ps1 that contains this method.
While invoking the Get-WebApplicationTest method from the FluentShellUnit tests, you can now pass this scriptblock to use as a stub to the actual Get-SPWebApplication cmdlet like

var actual = PsFactory.Create(HostState.Core)
                .Load(@"TestModule\Modules\TestModule.psm1")
                .StubFromFile("WebApplication.ps1")
                .Execute
                (
                    "Get-WebApplicationTest",
                    new Dictionary<string, string>
                    {
                        {"webAppName", "MySPWebApplication"}
                    }
                )
                .FirstResultItemAs<Hashtable>();
Assert.IsTrue(actual.Contains("Url"));




FluentShellUnit - Create a dummy object


In the previous entry of this series of FluentShellUnit, we saw how to use the framework to invoke a method on a PowerShell module and perform assertions. This article we’ll see how we can pass in parameters to a method and use a simple stub/ dummy object for a PowerShell cmdlet to isolate our code under test.
FluentShellUnit’s execute method’s overload takes a Dictionary as argument that is used to pass the parameters to the calling method. For e.g. The below method, passes the value “VSTest” to the parameter “context” for the Get-WelcomeMessage function in the TestModule

var actual = PsFactory.Create(HostState.Core)
                .Load(@"TestModule\Modules\TestModule.psm1")
                .Execute
                (
                    "Get-WelcomeMessage",
                    new Dictionary<string, string>
                    {
                        {"context", "VSTest"}
                    }
                )
                .FirstResultItemAs<string>();
Assert.IsTrue(actual.Contains("VSTest"));
                    
Function Get-WelcomeMessage{
      param([string] $context)
     
      "Welcome from context {0}" -f $context
}

FluentShellUnit also allows you to create a simple Dummy object (http://xunitpatterns.com/Mocks,%20Fakes,%20Stubs%20and%20Dummies.html) , that has no implementation and does not do anything when invoked. The below example shows how to create a Dummy for the Write-Host cmdlet when invoked from a method.


var actual = PsFactory.Create(HostState.Core)
                .Load(@"TestModule\Modules\TestModule.psm1")
                .Stub("Write-Host")
                .Execute
                (
                    "Get-WelcomeMessage",
                    new Dictionary<string, string>
                    {
                        {"context", "VSTest"}
                    }
                )           
                .FirstResultItemAs<string>();
Assert.IsTrue(actual.Contains("VSTest"));

Where the Get-WelcomeMessage implementation is as

Function Get-WelcomeMessage{
      param([string] $context)
      $message = "Welcome from context {0}" -f $context
      Write-Host "Setting the welcome message as {0}" -f $message
      $message
}

Introducing FluentShellUnit - A unit testing framework for PowerShell in C#

FluentShellUnit is a unit testing framework of testing PowerShell scripts and modules. It provides a simple to use API to write your tests in C# for loading and executing the functions in a PowerShell module or script file. It follows a very easy approach to stub the existing cmdlets or functions so that you can easily isolate your test code. This makes it a very good candidate for teams practicing TDD and using PowerShell in their code base.

Using the framework:

You can download the entire source code from the GitHub repository or use the Nuget package that is available from the Nuget gallery in Visual studio.
To download the source code and compile it to use in you projects visit the GitHub page at https://github.com/prajeeshprathap/FluentShellUnit.
To download the nuget package, open the package download manager from visual studio and search for FluentShellUnit. Click install to install the package to your project.

Creating your first test.

Create a new Unit test project in C# and a new test class to the project
Use the DeploymentItem attribute in the class to deploy the PowerShell modules to the test execution local folder if you don’t want to refer the modules from a fixed location in your drive. This approach will ensure that the modules are not loaded from an absolute path on the developer’s machine that created the tests. With the use of DeploymentItem you can also ensure that, the tests will work fine in the build server as well.

[TestClass]
[DeploymentItem(@"Data\Modules\Host\Host.psm1", "Modules")]
public class HostTests

Create a test method and ues the PsFactory.Create method to create a new intsance of the PowerShell host that allows you to load and execute tests.
[TestClass]
[DeploymentItem(@"Data\Modules\Host\Host.psm1", "Modules")]
public class HostTests
{
    [TestMethod]
    [TestCategory("Host Module")]
    public void ConfirmLocalSession_should_return_true_if_tests_are_executed_in_the_local_machine()
    {
        var actual = PsFactory.Create(HostState.Core);                   
    }
}

The load method will load a module into the runspace from a path mentioned as parameter to the method. If you have not used the DeploymentItem attribute, then you can make use of the IsAbsolute overload of the method and pass the absolute path instead of a relative path.
var actual = PsFactory.Create(HostState.Core)
            .Load(@"Modules\Host.psm1");          

To execute a method from the loaded module call the Execute method with the method name. The example show in this sample is calling a method that does not except any parameters. We’ll see later how to pass parameters to the method if needed.

var actual = PsFactory.Create(HostState.Core)
            .Load(@"Modules\Host.psm1")
            .Execute("Confirm-LocalSession");

Finally the ResultAs and FirstResultitemAs methods will tanslate the result of execution into a type mentioned and can be used for assertions in the code.
var actual = PsFactory.Create(HostState.Core)
            .Load(@"Modules\Host.psm1")
            .Execute("Confirm-LocalSession")
            .FirstResultItemAs<bool>();

        Assert.IsTrue(actual);

Sunday, January 18, 2015

Creating your own unit testing framework for PowerShell - part 4

Before we proceed into how we can stub out commands in our test framework, we’ll see how the AddScript method works and how to use it to execute a script block in powershell. The AddScript method adds a script to the end of the pipeline of the PowerShell object and can be invoked by the Invoke method. We’ll use this method to add our dummy function as a script object to the pipeline so that when this command is called later from a function, it’s going to call the dummy function that was added using the AddScript method.

So our Stub() is implemented as.

public PowerShellHost Stub(string method)
{
    if (_runspace == null)
    {
        throw new ArgumentException("The PowerShell host should be setup before invoking the methods");
    }
    var script = String.Format("Function {0} {{}}", method);
    _shell.AddScript(script).Invoke();          
    return this;
}

You can also see that I’ve used the return value of the method as the PowerShellHost, so that I can use a fluent interface model for my test methods. A sample test method using Stub to the Write-Host command can be written as

var psHost = new PowerShellHost<XModule>();
var actual = psHost
    .Stub("Write-Host")
    .Execute("Get-Greetings");

var result = actual.GetGreetings.Result.Select(psObject => psObject.BaseObject).OfType<string>().First();
Assert.AreEqual<string>(result, "Hello from VSTest");


Next we’ll see how exception handling can be taken care.

Creating your own unit testing framework for PowerShell - Part 3

The windows PowerShell engine can be hosted in the System.Management.Automation.PowerShell class. Using this class you can create, execute and manage commands in a Runspace. We’ll use these features of the PowerShell class to load and execute our Modules and interact with the PowerShell engine whenever needed in our unit test framework. While creating the PowerShell host for our test framework, it’s good to define the Runspace that is responsible for the operating environment for command pipelines. In our framework I preferred to use the constrained runspace which will allow us to restrict the programming elements that can be applied by the user.
We’ll later use this ability of constrained runspaces to simulate a Stub behavior in our test framework. To restrict the availability of aliases, applications, cmdlets, functions and scripts, we’ll create an empty InitialSessionState and use that for creating a runspace. Later we’ll use the AddPSModule, AddCommand, AddPSSnapin methods to include the required functionality in our runspace in a text context.

InitialSessionState has three different methods to create a container that holds commands
  • Create - Creates an empty container. No commands are added to this container.
  • CreateDefault - Creates a session state that includes all of the built-in Windows PowerShell commands on the machine. When using this API, all the built-in PowerShell commands are loaded as snapins.
  • CreateDefault2 - Creates a session state that includes only the minimal set of commands needed to host Windows PowerShell. When using this API, only one snapin – Microsoft.PowerShell.Core - is loaded.

We’ll use the CreateDefault2 overload method to create the InitialSesionState instance.

private InitialSessionState CreateSessionState(string path)
{
    var state = InitialSessionState.CreateDefault2();
    if (!String.IsNullOrEmpty(path))
    {
        state.ImportPSModulesFromPath(path);
    }
    return state;
}

In the CreateSessionState method, we use the Path property of the PSModuleAttribute created in the part 1 of this series to load all modules from the path provide to the InitilSessionState.

Once we have the InitialSessionState, we’ll use this container to create the Runspace and then the PowerShell host

_runspace = RunspaceFactory.CreateRunspace(state); 
_runspace.Open();
_shell = PowerShell.Create();
_shell.Runspace = _runspace;

We’ll use this PowerShell instance to execute the commands needed from the module. In the framework, I have my execute method created as

public TModule Execute(string method)
{
    if (_shell == null)
    {
        throw new ArgumentException("The PowerShell host should be setup before invoking the methods");
    }
    _shell.AddCommand(_moduleInfo.Name + @"\" + method);
    var methodProperties = typeof(TModule).GetProperties()
        .Where(prop => prop.IsDefined(typeof(PsModuleFunctionAttribute), false)).ToList();
    var property = methodProperties.First(p => p.GetCustomAttribute<PsModuleFunctionAttribute>().Name == method);
    var commandInfo = property.GetValue(_module) as PsCommandInfo;
    var parameters = commandInfo.Parameters;
    if (parameters != null)
    {
        _shell.AddParameters(parameters);
    }
    var results = _shell.Invoke();
    commandInfo.Result = results;
    property.SetValue(_module, commandInfo);
    DisposeContext();
    return _module;
}

As you can see from the highlighted code, we add the commands and the parameters to the shell using reflection from the metadata defined in the attributes to invoke the shell. The results are set back as property values to the ModuleObject and returned back to the test to assert the conditions.

A simple test code using the framework can be created like

var psHost = new PowerShellHost<XModule>();
var actual = psHost
    .Execute("Get-Greetings");

var result = actual.GetGreetings.Result.Select(psObject => psObject.BaseObject).OfType<string>().First();
Assert.AreEqual<string>(result, "Hello from VSTest");


Next in the series, we’ll see how to make use of our framework to stub methods/ cmdlets in the execution pipeline.