Friday, September 17, 2010

Automated testing of PowerShell

I have been expanding my library of PowerShell build scripts that I use for my work, and as such I want to ensure that they keep working as I expect them too. The obvious answer is automated tests but it was a case of how. I spent some time trying out PSUnit but to be honest I found it a bit hard, possibly just because I was unfamiliar with the framework. After a bit of digging here is the solution that I now use, and to make it harder I was using Visual Studio Express 2010.

Tools
  • Visual Studio 2010 C# Express
  • NUnit 2.5
  • PowerShell 2 
Setup
I have a solution that contains two projects, one with my PowerShell scripts and the other with my tests just to keep things clean.

Scripts project

My PowerShell scripts are all part of a module so for my integration tests I install this module in the user directory. I am just using a build event to do this


rmdir %USERPROFILE%\Documents\WindowsPowerShell\modules\DevPipeline\ /s /q
mkdir %USERPROFILE%\Documents\WindowsPowerShell\modules\DevPipeline\
xcopy $(ProjectDir)\PowerShellModule %USERPROFILE%\Documents\windowspowershell\modules\DevPipeline

Tests project


To run tests in Visual Studio express I use the following work around after setting the project to be a Console Application rather than a library.

using System;
using System.Reflection;
namespace Tests.ALM.Build {
class Program {
static void Main(string[] args) {
AppDomain.CurrentDomain.ExecuteAssembly(
@"C:\Program Files\NUnit 2.5.5\bin\net-2.0\NUnit-console.exe",
new string[] { Assembly.GetExecutingAssembly().Location });
}
}
}


And then to run the command for my test I use the following class

using System.Collections.ObjectModel;
using System.Management.Automation;
namespace Tests.ALM.Build {
/// <summary>
/// Run powershell scripts for unit test purposes
/// </summary>
internal static class PowerShellCommandRunner {
/// <summary>
/// Runs the given script from the imported module
/// </summary>
/// <param name="moduleName">Name of the powershell module to import</param>
/// <param name="command">The command that will be run</param>
/// <returns>powershell output</returns>
internal static Collection<PSObject> ExecuteModuleCommand(string moduleName, string command) {
Collection<PSObject> output;
using (PowerShell ps = PowerShell.Create()) {
ps.AddCommand("Import-Module", true).AddParameter("Name", moduleName);
ps.Invoke();
ps.Commands.Clear();
PowerShell cmd = ps.AddScript(command);
output = ps.Invoke();
}
return output;
}
}
}
view raw Run_PS_Command hosted with ❤ by GitHub


Integration Tests


As I mentioned above I am treating these tests as integration tests and I import the module as part of the setup.

An example of the integration tests is below

using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Management.Automation;
using NUnit.Framework;
namespace Tests.ALM.Build {
[TestFixture]
public class ExampleTests {
private string TestingSolutionsDirectory = Path.Combine(Directory.GetCurrentDirectory(), "Tests", "Assets", "Solutions");
private const string LibrariesFailingUnitTest = "LibrariesFailingUnitTest";
private const string DevPipelineModule = "DevPipeline";
private const string ApplicationsOne = "Applications.One";
[Test]
[Category("Integration")]
public void TestSolution_WillReturnFalseWhenUnitTestsFail() {
string runTestsForFailingUnitTestScript = string.Format("Set-SolutionPath -name {0} -path {1} | Test-Solution", LibrariesFailingUnitTest, TestingSolutionsDirectory);
Collection<PSObject> resultObject = PowerShellModule.ExecuteModuleScript(DevPipelineModule, runTestsForFailingUnitTestScript);
bool passed = (bool)resultObject[1].ImmediateBaseObject;
Assert.False(passed, "unit tests failed");
}
[Test]
[Category("Integration")]
public void SetSolution_ReturnsMessageThatCurrentSolutionIsNowSetAsTestPath(){
string setSolutionScript = string.Format("Set-SolutionPath -name {0} -path {1}", ApplicationsOne,TestingSolutionsDirectory);
Collection<PSObject> resultObject = PowerShellModule.ExecuteModuleScript(DevPipelineModule, setSolutionScript);
String actualMessage = (String)resultObject[0].ImmediateBaseObject;
String expectedMessage = string.Format("Current solution is now set as [{0}]", Path.Combine(TestingSolutionsDirectory, ApplicationsOne));
Assert.AreEqual(expectedMessage, actualMessage);
}
}
}


No comments:

Post a Comment