Using Pipeline.Input for Powershell testing

Last week, I posted about how to unit test powershell, which I have been working with a little bit over the weekend. One thing I quickly realized was testing interaction with the pipeline is a must for Powershell. It’s pretty easy to do.

Let’s start with the Get-Service cmdlet that ships with Powershell. If you load up reflector, you will see the follow class definition.

[Cmdlet("Get", "Service", DefaultParameterSetName="Default")]
public sealed class GetServiceCommand : MultipleServiceCommandBase
{
    // Methods
    public GetServiceCommand();
    protected override void ProcessRecord();
 
    // Properties
    [Parameter(Position=0, 
          ParameterSetName="Default", 
          ValueFromPipelineByPropertyName=true, 
          ValueFromPipeline=true),
     Alias(new string[] { "ServiceName" })]
    public string[] Name { get; set; }
}

Remembering the base test fixture from the previous post on TDD in Powershell, I can quickly write up a few tests that test a few different use cases of the get-service cmdlet with

 
[Test]
public void Get_Service_with_single_string_in_pipeline() 
{
    // "MySQL" | get-service
 
    var name = "MySQL";
 
    var pipeline = Runspace.CreatePipeline();
 
    pipeline.Input.Write(name);
    pipeline.Commands.Add("get-service");
 
    var result = pipeline.Invoke();
 
    AssertThatPipelineResultIsService(result, name);
}
 
[Test]
public void Get_Service_with_single_object_in_pipeline_using_Name_property()
{
    //New-Object PSObject | Add-Member NoteProperty Name "MySQL" -PassThru | get-service
 
    var name = "MySQL";
 
    var pipeline = Runspace.CreatePipeline();
 
    pipeline.Input.Write(new { Name = name });
    pipeline.Commands.Add("get-service");
 
    var result = pipeline.Invoke();
 
    AssertThatPipelineResultIsService(result, name);
}
 
[Test]
public void Get_Service_with_single_object_in_pipeline_using_ServiceName_property()
{
    //New-Object PSObject | Add-Member NoteProperty ServiceName "MySQL" -PassThru | get-service
 
    var name = "MySQL";
 
    var pipeline = Runspace.CreatePipeline();
 
    pipeline.Input.Write(new { ServiceName = name });
    pipeline.Commands.Add("get-service");
 
    var result = pipeline.Invoke();
 
    AssertThatPipelineResultIsService(result, name);
}
 
private void AssertThatPipelineResultIsService(Collection<PSObject> result, string name)
{
    Assert.That(result.Count == 1);
    Assert.That(result[0].BaseObject is ServiceController);
    Assert.That((result[0].BaseObject as ServiceController).ServiceName == name);
}

By using pipeline.Input.Write(), I can write to the pipeline before invoking commands. Really easy to do, and super useful, because each Powershell cmdlet can probably be called a bunch of different ways.

Where can you use it? Snap in development or testing your own Powershell scripts. Stop guessing how the pipeline is going to work and test it.

TDD with Powershell, from the client perspective

Is it possible? Yes, and it is actually relatively easy. The hardest part can be figuring out how to mock/stub out certain functionality so your testing small units as opposed to full acceptance testing. The line can get blurry, but I don’t really care. Testing is half the battle.

But to be clear, the focus of my testing is to test how a snap-in is working. It sounds like an integration test, and maybe it is, but there is no reason you couldn’t test any powershell script using some of the steps outlined here. Just be flexible and creative.

Start by creating a nice base test fixture.

public abstract class PowershellTestFixture
{
    protected Runspace Runspace { get; set; }
 
    public void CreateRunspace()
    {
        Runspace = RunspaceFactory.CreateRunspace(CreateConfiguration());
        Runspace.Open();           
    }
 
    public void CloseRunspace()
    {
        Runspace.Close();
        Runspace.Dispose();
    }
 
    private RunspaceConfiguration CreateConfiguration()
    {
        var config = RunspaceConfiguration.Create();
 
        PSSnapInException warning;
        config.AddPSSnapIn("SomeCustomSnapIn", out warning);
 
        return config;
    }
 
    protected Collection<PSObject> ExecutePowershell(string script)
    {
        return Runspace.CreatePipeline(script).Invoke();
    }
}

In this base class, I just created some nice helper methods, and one method that samples how to add a snap-in to the runspace.

Now, just write a test, and add your assertions. And it will work!

[TestFixture]
public class TestSomethingStupid : PowershellTestFixture
{
    [SetUp]
    public void Setup()
    {
        CreateRunspace();
    }
 
    [TearDown]
    public void TearDown()
    {
        CloseRunspace();
    }
 
    [Test]
    public void GetServiceWhereNameIsMySql()
    {
        string cmd = @"
get-service | where { $_.name -eq 'MySQL' }
";
        var result = ExecutePowershell(cmd)
            .Select(o => o.BaseObject)
            .Cast<System.ServiceProcess.ServiceController>()
            .First();
 
        Assert.AreEqual(result.ServiceName, "MySQL");            
    }
}

Obviously this test is stupid. But that’s not the point. In a very few lines, I have tested the output of some powershell. Pretty cool if you ask me, especially if your coding a snap in. If you aren’t automating the tests, how else can you verify it works except to recompile, install, open powershell, and type some code? Very slow and painful!

It is important to note, there are other ways to test snap-in’s. What I really wanted to see is how my cmdlet’s looked from the powershell perspective, not the C# perspective.

Re: Roanoke Code Camp

I attended my first Code Camp this past weekend. Overall, it was a great experience, although for a few reasons you might not expect. I learned a little about new .NET features, but the biggest benefit for me was the networking. My code community, for the most part, revolves around Mailtrust. Through Code Camp, blogging, and Twitter (I will blog on this later, since you might remember my last Twitter post) I have joined a community of developers much larger than Mailtrust’s.

This is a good thing.

With that being said, I did really enjoy learning a few things.

  • WPF is pretty wild, especially with Blend. Let designers design interfaces directly, and developers just make it work. This isn’t the first time this concept has been tried out, but it works pretty damn well from what I saw.
  • .NET 4.0 – Oh ya. I loved that stuff. Learning about dynamic, DLR, and language enhancements is my crack.
  • PEX and Code Contracts – Good concepts (but some things I didn’t like)

What could be improved on?

  • Don’t shun open source. NHibernate and NUnit are great tools, and we shouldn’t ignore them because Microsoft is competing with them ( Entity Framework and Unit Testing Framework).
  • More code. Powerpoint should be banned.
  • Don’t trivialize the importance of good testing. Pex is really an amazing concept, that can complement testing, not supplement (Yes, I love trig).

It was a great experience though. I am looking forward to more RVNUG involvement, as well as my next Code Camp.

My First Powershell SnapIn – Get-Url (wget)

There are already a lot of people who have solved this problem, but I wanted to solve it too. Powershell doesn’t ship with a wget‘ish cmdlet, at least that I could easily find. So I decided to kill two birds with one stone, create my first SnapIn and create a Get-Url cmdlet. It’s much easier than I thought.

Getting started

Start by creating a new class library project. There are two references you are going to need, System.Configuration.Install and System.Management.Automation. The former ships with .NET. The latter ships with the Windows SDK.

snapinreferences

Create the snap-in class

Add a class for the snap in. This class is fairly worthless, but nevertheless it is required. It helps the installer know what needs to be installed, and what the SnapIn will be called.

[RunInstaller(true)]
public class WebSnapIn : PSSnapIn
{
    public override string Description
    {
        get { return "Snap for web related activities"; }
    }
 
    public override string Name
    {
        get { return "WebSnapIn"; }
    }
 
    public override string Vendor
    {
        get { return "Brian Hartsock"; }
    }
}

Create the cmdlet

Now to the meat and potatoes, create the cmdlet. I don’t want to get into the details of everything that is necasary, just pay attention to the attributes and the ProcessRecord method. There are much better teachers at the intricacies of SnapIn creation than I.

[Cmdlet(VerbsCommon.Get, "url")]
public class GetUrl : Cmdlet
{
    public GetUrl()
    {
        Progress = new ProgressRecord(1, "Downloading...", "Starting...");
    }
 
    [Parameter(Mandatory=true, Position=0)]
    public Uri Url { get; set; }
 
    private ProgressRecord Progress { get; set; }
 
    protected override void ProcessRecord()
    {
        UpdateProgress("Connecting...");
 
        var request = WebRequest.Create(Url);
        var response = request.GetResponse();           
 
        UpdateProgress("Connected");
 
        using (var responseStream = response.GetResponseStream())
        using (var fileStream = new FileStream(GetFilePath(), FileMode.OpenOrCreate))
        {
            CopyStream(
                responseStream, 
                fileStream, 
                1024,
                response.ContentLength,
                (current, total) => UpdateDownloadProgress(current, total));
        }
    }
 
    private string GetFilePath()
    {
        var fileName = Url.Segments.Last();
        var fullPath = Path.Combine(Directory.GetCurrentDirectory(), fileName);
 
        return fullPath;
    }
 
    private void CopyStream(Stream source, Stream sink, int bufferSize, long sourceLength, Action<long, long> notification)
    {
        byte[] buffer = new byte[bufferSize];
 
        long numberOfBytesCopied = 0;
 
        while (true)
        {
            var actual = source.Read(buffer, 0, buffer.Length);
            if (actual == 0)
            {
                break;
            }
            sink.Write(buffer, 0, actual);
            numberOfBytesCopied += actual;
            notification(numberOfBytesCopied, sourceLength);
        }
    }
 
    private void UpdateProgress(string description)
    {
        Progress.StatusDescription = description;
 
        WriteProgress(Progress);
    }
 
    private void UpdateDownloadProgress(long current, long total)
    {
        int percent = (int)((100 * current) / total);
 
        Progress.StatusDescription = string.Format("{0} bytes of {1} ({2}%)", current, total, percent);
        Progress.PercentComplete = percent;
 
        WriteProgress(Progress);
    }
}

Build, and your almost ready to start using it.

Install and run

InstallUtil must be used to register the DLL, then you have to add the snap-in to the current session.

c:\Windows\Microsoft.NET\Framework\v2.0.50727\InstallUtil.exe WebSnapIn.dll
 
add-snapin WebSnapIn
get-url http://prdownloads.sourceforge.net/nunit/NUnit-2.4.8-net-2.0.zip?download

Here it is in action, although the video is pretty terrible. Imagine the text is clear and you can see what is going on. (I am a video noob)


Get-Url in action from Brian Hartsock on Vimeo.

Lastly, my appologies to NUnit/SourceForge. I probably downloaded NUnit 30 times testing this script out.

Database integration testing, Interested?

Testing database queries is super important, but not easy enough. It can be done, but usually you have to write custom test fixtures and spend too much time doing it. Every project needs to test database integration, so why isn’t there a library out there to make it easier? NDbUnit seems somewhat dead, and only works on a specific set of database providers. Even worse, it relies on a ton of XML setup, which I hate.

So I decided to play around with creating an NUnit addin that makes this a lot easier. Does the following code interest anyone?

 
[MySqlDbTestFixture("Data Source=localhost;User ID=root;"), 
RandomDatabaseName, 
ImportFromFile("schema.sql")]
public class SomeTestFixture
{
    [DbTest,
    ImportFromFile("BadCompanyDataInDb.sql")]
    public void SomethingWorksWithBadCompanyData(IDbConnection conn)
    {
        ...
    }
}

I have most of it working, and want to see if there are any developers that might think of using it. Am I on the right track? Any thoughts or ideas for improvement?

(I told you I would have some code in a post this week)