Home > Uncategorized > Test Strategy in SharePoint: Part 4 – Event Receiver as layered Feature

Test Strategy in SharePoint: Part 4 – Event Receiver as layered Feature

December 12th, 2010 Leave a comment Go to comments

In Test Strategy in SharePoint: Part 3 – Event Receiver as procedural, untestable feature we came up with some nice code that we believe to be nicer – the code was going to be layered and more testable. This entry will look at the tests and the code to make that code come alive.

The starting design – which may get tweaked a little as we go. In practice, we started with this design in mind (ie use attributes to declarative decide on what to provision) but refactored the original code test-first trying to work out what was unit vs integration testable and what code was in the domain, infrastructure and ui layers (as based on good layering to aid testability). We won’t take you through that process but it did only take a few hours to knock out the first attempt.

Here we’ll take you through the creation of one type of the page the NewPage. We have put the others there to show that we made the design because we are going to require many types of pages and we are hoping that the benefit of an attribute and its declarative style will payoff against its cost. We are looking for accessibility and maintainability as we bring on new developers – or come back to it ourselves in a couple of weeks!

using System.Runtime.InteropServices;
using YourCompany.SharePoint.Domain.Model.Provisioning;
using YourCompany.SharePoint.Infrastructure;
using YourCompany.SharePoint.Infrastructure.Configuration;
using Microsoft.Office.Server.UserProfiles;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Publishing;

namespace YourCompany.SharePoint.MySites.Features.WebFeatureProvisioner
{
    [Guid("cb9c03cd-6349-4a1c-8872-1b5032932a04")]
    public class SiteFeatureEventReceiver : SPFeatureReceiver
    {
        [NewPage("Home.aspx", "Home Page", "HomePage.aspx")]
        [ActivateFeature(PackageCfg.PublishingWebFeature)]
        [RemovePage("Pages/default.aspx")]
        [MasterPage("CustomV4.master", MasterPage.MasterPageType.User)]
        [MasterPage("CustomMySite.master", MasterPage.MasterPageType.Host)]
        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            PersonalSiteProvisioner
              .Create(properties.Feature.Parent as SPWeb)
              .Process();
        }
    }
} 

Overview of strategy

I want to drive as much testing back into unit tests and the rest can go to integration testing. However, there is another issue here. As a team member, I actually want to have a record of the operational part of the tests because this is about installation/deployment and it is at the stage of provisioning/activation. So what we’ll need to do is write a BDD-style acceptance test to tease out the feature activation process too. Thus:

  • the acceptance test will have the ability to actually activate a feature
  • the unit test should help specify any specifics of this activation – which is the abstraction mechanism we will use to get code abstractions
  • the integration test be any tests proving specific API calls

System Acceptance test

Before we try and drive out a design, let’s understand what needs to be done. To write this acceptance test requires a good knowledge of SharePoint provisioning – so these are technically-focussed acceptance tests rather than business one’s.

We will write a system test Acceptance\Provisioning\ (we will implement this in StoryQ later on)

Story is Solution Deployment

In order to create new pages for user
As a user
I want a 'MySites' available

With scenario have a new feature
  Given I have a new wsp package mysites.wsp
  When site is deployed
    And I am on site http://mysites/personal
  Then Publishing Site Feature is site activated

Design

We now start to drive out some code concepts from tests. I think that our TODO list is something like this:

TODO

  • have an attribute
  • be able to read the attributes from a class
  • return a publisher for a page rather than a page
  • create a service for processing a publisher
  • actually publish pages in the context of SharePoint (integration)
  • check that it all works in SharePoint when deployed (system)
  • add new types of publishers into the factory class

Unit test: have an attribute

Because this is the first test, I will add in the namespace that we are driving out code from the domain and in the unit test project. This code is creating an attribute that we can to represent a new page. This is straightforward code that we want to be able to simple say what names of the pages are that we want to be in the new page.

namespace Test.Unit.Provisioning
{
    [TestFixture]
    public class SiteProvisioningAttributesTests
    {
        [Test]
        public void ProvisioningHasHomePage()
        {
            Assert.IsTrue(typeof(TestClass).GetMethod("OneNewPage").GetCustomAttributes(typeof(NewPageAttribute), false).Count() == 1);
        }

        [Test]
        public void CanReturnPageValues()
        {
            var page = ((IProvisioningAttribute)typeof(TestClass).GetMethod("OneNewPage").GetCustomAttributes(typeof(NewPageAttribute), false)).Page;
            Assert.AreEqual("Home.aspx", page.Name);
        }

        public class TestClass
        {
            [NewPage("Home.aspx", "Home Page", "HomePage.aspx")]
            public void OneNewPage()
            {

            }
        }
    }
} 

Now the skeleton code for the attribute – it will need to do provisioning later on but let’s leave that for now. At this stage, we are just going to make the attribute be able to return the value object of a page:

using System;
using YourCompany.SharePoint.Domain.Model;
using YourCompany.SharePoint.Domain.Model.Provisioning;
using YourCompany.SharePoint.Domain.Services.Provisioning;

namespace YourCompany.SharePoint.Infrastructure.Provisioning
{
    [Serializable, AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)]
    public class NewPageAttribute : Attribute, IProvisioningAttribute
    {
        public Page Page { get; private set; }
        public NewPageAttribute(string name, string title, string pageLayout)
        {
            Page = new Page(name, title, pageLayout);
        }
    }
} 
public struct Page
{
    public string Name { get; private set; }
    public string Title { get; private set; }
    public string PageLayout { get; private set; }
    
    public Page(string name, string title, string pageLayout)
        : this()
    {
        Name = name;
        Title = title;
        PageLayout = pageLayout;
    }
}s
public interface IProvisioningAttribute
{
    Page Page { get; }
}

Unit test: be able to read the attributes from a class

TODO

  • have an attribute
  • be able to read the attributes from a class
  • return a publisher for a page rather than a page
  • create a service for processing a publisher
  • actually publish pages in the context of SharePoint (integration)
  • check that it all works in SharePoint when deployed (system)
  • add new types of publishers into the factory class

Now that we have an attribute designed that can return the values for a page, now we need to create a parser across the class that can return the attributes.


[TestFixture]
public class SiteProvisioningAttributesTests
{
    [Test]
    public void NewProvisioningCountIsOneForNewPageMethod()
    {
        var pages = AttributeParser.Parse<NewPageAttribute>(typeof(TestClass), "OneNewPage");
        Assert.IsTrue(pages.Count() == 1);
    }

    [Test]
    public void NewProvisioningCountIsTwoForNewPageMethod()
    {
        var pages = AttributeParser.Parse<NewPageAttribute>(Type, "TwoNewPages");
        Assert.IsTrue(pages.Count() == 2);
    }

    public class TestClass
    {
        [NewPage("Home.aspx", "Home Page", "HomePage.aspx")]
        public void OneNewPage()
        {
        }

        [NewPage("Home.aspx", "Home Page", "HomePage.aspx")]
        [NewPage("Home2.aspx", "Home2 Page", "HomePage2.aspx")]
        public void TwoNewPages()
        {
        }
    }
}

With code something like this we are going to get all the New pages.

public class AttributeParser
{
    public static IEnumerable<Page> Parse<T>(Type type, string method)
        where T : IProvisioningAttribute
    {
            return type.GetMethod(method).GetCustomAttributes(typeof(T), false)
                .Select(attribute => ((T)attribute).Page));
    }
} 

Now that we are return a page by iterating over the the method, I can now see that we don’t want a page per se but rather a specific type of publisher.

Unit tests: return a publisher for a page rather than a page

TODO

  • have an attribute
  • be able to read the attributes from a class
  • return a publisher for a page rather than a page
  • create a service for processing a publisher
  • actually publish pages in the context of SharePoint (integration)
  • check that it all works in SharePoint when deployed (system)
  • add new types of publishers into the factory class

Returning a publisher is going to be refactor as we are adding a new concept through the attribute. Let’s rewrite the test that our parser is going to instead return an explicit IPagePublisher rather than an implicit Page.

[TestFixture]
public class SiteProvisioningAttributesTests
{
    [Test]
    public void NewProvisioningCountIsOneForNewPageMethod()
    {
        var pages = AttributeParser.Parse<IPagePublisher, NewPageAttribute>(typeof(TestClass), "OneNewPage");
        Assert.IsTrue(pages.Count() == 1);
    }

    public class TestClass
    {
        [NewPage("Home.aspx", "Home Page", "HomePage.aspx")]
        public void OneNewPage()
        {
        }
    }
}

As we unpick this small change, we get the introduction of a new interface IPagePublisher. In practice, I add an empty interface but here I want to show that we introduce the concept of publishing without actual implementations – this is the benefit that we are looking for. In summary, I give the activator a “new page” with details and it provides back to me a publisher that knows how to deal with this information. That sounds fair to me.

public interface IPagePublisher
{
    void Add();
    void Delete();
    void CheckIn();
    void CheckOut();
    void Publish();
    bool IsPublished();
    bool IsProvisioned();
} 

So now our AttributeParser becomes:

public class AttributeParser
{
    public static IEnumerable<T> Parse<T, T1>(Type type, string method)
        where T : IPagePublisher
        where T1 : IProvisioningAttribute
    {
        return type.GetMethod(method).GetCustomAttributes(typeof(T1), false)
            .Select(attribute => ((T1)attribute).Publisher(((T1)attribute).Page))
            .Cast<T>();
    }
} 

Which requires an change to our interface. This may change require a little bit of explanation for some. Why the Func? Bascially, we can have a property that accepts parameters and we can swap out its implementation as needed (for testing).

public interface IProvisioningAttribute
{
    Func<Page, IPagePublisher> Publisher { get; }
    Page Page { get; }
}

So the concrete implementation now becomes:

[Serializable, AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)]
public class NewPageAttribute : Attribute, IProvisioningAttribute
{
    public Func<IPage, IPagePublisher> Publisher
    {
        get { return new PagePublisher(Page); }
    }

    public IPage Page { get; private set; }
    public NewPageAttribute(string name, string title, string pageLayout)
    {
        Page = new Page(name, title, pageLayout);
    }
} 

Wow, all we did was add IPagePublisher to AttributeParser.Parse<IPagePublisher, NewPageAttribute>(typeof(TestClass), "OneNewPage"); and we got all that code!

Unit tests: create a service for processing a publisher

TODO

  • have an attribute
  • be able to read the attributes from a class
  • return a publisher for a page rather than a page
  • create a service for processing a publisher
  • actually publish pages in the context of SharePoint (integration)
  • check that it all works in SharePoint when deployed (system)
  • add new types of publishers into the factory class

So far, we are able to make a method with attributes for each page. When we parse these attributes, we get back the publishers with the page. Now we need to be able to process a publisher. We are going to move into mocking out a publisher to check that the publisher is called in the service. This service is a provisioner and it will be provisioning the personal site. So hopefully calling it the PersonalSiteProvisioner makes sense.

Let’s look at the code below. We are creating a new personal site provisioner and inside this we want to ensure that the page publisher actually adds a page. Note: we are deferring how the page is actually added. We just want to know that we are calling add. (Note: we using Moq as the isolation framework.)

using Moq;

[TestFixture]
public class SiteProvisioningTest
{
    [Test]
    public void CanAddPage()
    {
        var page = new Mock<IPagePublisher>();
        var provisioner = new PersonalSiteProvisioner(page.Object);

        provisioner.Process();

        page.Verify(x => x.Add());
    }
 }

So here’s the code to satisfy the test:

public interface IProvisioner
{
    void Process();
}

With the implementation:

namespace YourCompany.SharePoint.Domain.Services.Provisioning
{
    public class PersonalSiteProvisioner : IProvisioner
    {
        public List<IPagePublisher> PagePublishers { get; private set; }

          public PersonalSiteProvisioner(List<IPagePublisher> publishers)
          {
              PagePublishers = publishers;
          }
          public PersonalSiteProvisioner(IPagePublisher publisher) 
            : this(new List<IPagePublisher>{publisher})
          {
          }

        public void Process()
        {
            PagePublishers.TryForEach(x =>x.Add());
        }
    }
} 

Right, that all looks good. We can Process some publishers in our provisioner. Here’s some of the beauty (IMHO) that we can add to these tests without going near an integration point. Let’s add few more tests like adding multiple pages, adding and deleting, ensuring that there is error handling and then trying different combinations of adding and deleting and finally that if one adding errors that we can still process others in the list. Take a look at the tests (p.s the implementation in the end is easy once we had tests but it took an hour or so).

Unit tests: really writing the provisioner across most cases

TODO

  • have an attribute
  • be able to read the attributes from a class
  • return a publisher for a page rather than a page
  • create a service for processing a publisher
  • actually publish pages in the context of SharePoint (integration)
  • check that it all works in SharePoint when deployed (system)
  • add new types of publishers into the factory class
[TestFixture]
public class SiteProvisioningTest
{

    [Test]
    public void CanAddMultiplePages()
    {
        var page = new Mock<IPagePublisher>();
        var provisioner = new PersonalSiteProvisioner(new List<IPagePublisher> { page.Object, page.Object });

        provisioner.Process();

        page.Verify(x => x.Add(), Times.Exactly(2));
    }

    [Test]
    public void CanRemovePage()
    {
        var page = new Mock<IPagePublisher>();
        page.Setup(x => x.IsPublished()).Returns(true);

        var provisioner = new PersonalSiteProvisioner(page.Object);

        provisioner.Process();

        page.Verify(x => x.Delete());
    }

    [Test]
    public void RemovingPageThatDoesntExistDegradesNicely()
    {
        var page = new Mock<IPagePublisher>();
        page.Setup(x => x.IsPublished()).Returns(true);
        page.Setup(x => x.Delete()).Throws(new Exception());

        var provisioner = new PersonalSiteProvisioner(page.Object);

        provisioner.Process();

        page.Verify(x => x.Delete());
    }

    [Test]
    [Sequential]
    public void SecondItemIsProcessedWhenFirstItemThrowsException(
        [Values(false, true)] bool delete,
        [Values(true, false)] bool add)
    {
        var page = new Mock<IPagePublisher>();
        page.Setup(x => x.IsPublished()).ReturnsInOrder(
            () => delete,
            () => add);
        page.Setup(x => x.Delete()).Callback(() => { throw new Exception(); });

        var provisioner = new PersonalSiteProvisioner(new List<IPagePublisher> { page.Object, page.Object, page.Object });
        provisioner.Process();

        page.Verify(x => x.Add(), Times.Once());
        page.Verify(x => x.Delete(), Times.Once());
    }

    [Test]
    public void AlreadyInstalledPagesWontReinstallCatchesException()
    {
        var page = new Mock<IPagePublisher>();
        page.Setup(x => x.Add()).Throws(new Exception());

        var provisioner = new PersonalSiteProvisioner(page.Object);
        provisioner.Process();

        page.Verify(x => x.Add(), Times.Once());
    }
}

and the implementation is below. Just a couple of notes: this class should have logging in it and that this should also be interact tested (this is the key point in the system that we need in the logs) and also there is a helper wrapper TryForEach which is a simple wrapper around the Linq ForEach that null checks. Believe it or not, the tests above actually drove out a lot of errors even in this small piece of code because it had to deal with list processing. We now don’t have to deal with these issues at integration (and particularly in production).

namespace YourCompany.SharePoint.Domain.Services.Provisioning
{
    public class PersonalSiteProvisioner : IProvisioner
    {
        public List<IPagePublisher> PagePublishers { get; private set; }

        public PersonalSiteProvisioner(List<IPagePublisher> publishers)
        {
            PagePublishers = publishers;
        }
        public PersonalSiteProvisioner(IPagePublisher publisher) 
          : this(new List<IPagePublisher>{publisher})
        {
        }

        public void Process()
        {
            PagePublishers.TryForEach(x =>
            {
                if (x.IsPublished()) {
                    x.Delete();
                } else {
                    x.Add();
                }
            });
        }
    }
} 

Now we are ready to do something with SharePoint.

Integration tests: publish pages in the context of SharePoint

TODO

  • have an attribute
  • be able to read the attributes from a class
  • return a publisher for a page rather than a page
  • create a service for processing a publisher
  • actually publish pages in the context of SharePoint (integration)
  • check that it all works in SharePoint when deployed (system)
  • add new types of publishers into the factory class

Now we have have to deal with SharePoint. This means that we are going to an integration test. In terms of the classes, are now ready to implement the PagePublisher. Let’s write a test. The basic design is that we can we will hand in the publishing web context (and page) and then add it. (Note: we could have handed in only the web context in the constructor and then the page dependency in the method – this looks better in hindsight). The code then asserts that the item exists. Now, those of you familiar with the SharePoint API know that neither the Site.OpenPublishingWeb or SiteExpectations.ExpectListItem calls exist. These are wrappers to make the code more readable. We’ll include those below.

What you need to be seeing is that there no references to SharePoint in the Domain code – which in the flip-side is that the Infrastructure code is the place where these live.

using YourCompany.SharePoint.Domain.Model.Provisioning;
using YourCompany.SharePoint.Infrastructure;
using Microsoft.SharePoint.Publishing;
using NUnit.Framework;
using Test.Unit;

namespace Test.Integration.Provisioning
{
    [TestFixture]
    public class AddPagePublisherTest
    {
        [Test]
        public void CanAddPage()
        {
            var page = new Page("Home.aspx", "Home Page", "HomePage.aspx");   
            Site.OpenWeb("http://mysites/", x => new PagePublisher(x.Web, page).Add());
            SiteExpectations.ExpectListItem("http://mysites/", x => x.Name == "Home.aspx");
        }
    }
}

Additional wrapper code – one in Infrastructure and the other in the Test.Integration project – these both hide the repetitive complexity of SharePoint.

namespace YourCompany.SharePoint.Infrastructure
{
    public static class Site
    {
        public static void OpenWeb(string url, Action<SPWeb> action)
        {
            using (var site = new SPSite(url))
            using (var web = site.OpenWeb())
            {
                action(web);
            }
        }
        public static void OpenPublishingWeb(string url, Action<PublishingWeb> action)
        {
            using (var site = new SPSite(url))
            using (var web = site.OpenWeb())
            {
                action(PublishingWeb.GetPublishingWeb(web));
            }
        }
    }
}

And the test assertion:

namespace Test.Integration
{
   public class SiteExpectations
   {
       public static void ExpectListItem(string url, Func<SPListItem, bool> action)
       {
           Site.OpenPublishingWeb(url, x => Assert.IsTrue(x.PagesList.Items.Cast<SPListItem>().Where(action).Any()));
       }
   }
}

So now let’s look at (some of) the code to implement Add. Sorry, it’s a bit long but should be easy to read. Our page publisher code doesn’t look a lot different from the original code we found in the blog Customising SharePoint 2010 MySites with Publishing Sites in in the SetupMySite code. It is perhaps a little clearer because there are a few extra private methods that help describe the process that SharePoint requires when creating a new page. But the other code would have got there eventually.

The key difference is how we get there. The correctness of this code was confirmed by the integration test. In fact, we had to run this code tens (or perhaps one hundred) times to iron the idiosyncrasies of the SharePoint API - particularly the case around DoUnsafeUpdates. Yet, we didn’t have to deploy the entire solution each time. And no way do we have to debug by attaching a process. There were some times we were at a loss and did resort to the debugger but we were able to get context in Debug in the test runner. All of this has lead to increases in speed and stability.

There’s a final win in design. This code has one responsibility: to add a page. No more, no less. When we come to add multiple pages we don’t change this code. If we come to add another type of page – perhaps we don’t change this code either but create another type of publisher. Later on we can work out whether these publishers have shared, base code. A decision we can defer until refactoring.

using Microsoft.SharePoint;
using Microsoft.SharePoint.Publishing;

namespace YourCompany.SharePoint.Infrastructure.Provisioning
{
    public class PagePublisher : IPagePublisher
    {
        public const string SiteCreated = "MySiteCreated";

        private readonly Page _page;
        private readonly SPWeb _context;
        private PublishingWeb _pubWeb;

        public PagePublisher(SPWeb web, IPage publishedPage)
        {
            _page = publishedPage;
            _context = web;
        }

        public void Add()
        {
           using (var web = _site_context.OpenWeb()) {
             _pubWeb = web;
             if (_page.Equals(null) && !IsProvisioned()) return;

             DisableVersioning();

             if (!HomePageExists)
                 CreatePage();

             AddAsDefault();
             Commit();

             if (!IsProvisioned())
                 DoUnsafeUpdates(x =>
                 {
                     x.Properties.Add(SiteCreated, "true");
                     x.Properties.Update();
                 });
           }
        }

        public bool IsProvisioned()
        {
            return _context.Properties.ContainsKey(SiteCreated);
        }

        private void AddAsDefault()
        {
            _pubWeb.SetDefaultPage(HomePage.ListItem.File);
        }

        private void Commit()
        {
            _pubWeb.Update();
        }

        private bool HomePageExists
        {
            get { return _pubWeb.HasPublishingPage(_page.Name);  }
        }

        private void DoUnsafeUpdates(Action<SPWeb> action)
        {
            var currentState = _context.AllowUnsafeUpdates;
            _context.AllowUnsafeUpdates = true;
            action(_context);
            _context.AllowUnsafeUpdates = currentState;
        }

        private void DisableVersioning()
        {
            var pages = _pubWeb.PagesList;
            pages.EnableVersioning = false;
            pages.ForceCheckout = false;
            pages.Update();
        }

        private void CreatePage()
        {
            var layout = _pubWeb.GetAvailablePageLayouts()
                .Where(p => p.Name == _page.PageLayout)
                .SingleOrDefault();

            var page = _pubWeb.GetPublishingPages().Add(_page.Name, layout);
            page.Title = _page.Title;
            page.Update();
        }
    }
}

You should note that our tests against SharePoint are actually small and neat. In this case, it tests with one dependency (SharePoint) and one interaction (Add). The tests should be that simple. Setup and teardown are where it gets a little harder. Below requires a Setup which swaps out the original page so that the new one can be added and that we know that it is different. Teardown cleans up the temp publishing page. Note: at this stage the code in the Setup has not been abstracted into its own helper – we could do this later.

namespace Test.Integration.Provisioning
{
    [TestFixture]
    public class AddPagePublisherTest
    {
        private Page _publishedPage;
        private const readonly string HomeAspx = "http://mysites/";
        readonly string _homePage = a.TestUser.HomePage;
        private PublishingPage _tempPublishingPage;

        [SetUp]
        public void SetUp()
        {
            _publishedPage = new Page("Home.aspx", "Home Page", "HomePage.aspx");

            Site.OpenPublishingWeb(_homePage, x =>
            {
                var homePageLayout = x.GetPublishingPageLayout(_publishedPage.PageLayout);
                _tempPublishingPage = x.AddPublishingPage("TempPage.aspx", homePageLayout);
                x.SetDefaultPage(_tempPublishingPage.ListItem.File);
                x.Update();
                x.DeletePublishingPage(_publishedPage.Name);
            });
        }

        [Test]
        public void CanAddPage()
        {
            Site.OpenWeb(_homePage, x => new PagePublisher(x.Web, _publishedPage).Add());
            SiteExpectations.ExpectListItem(_homePage, x => x.Name == HomeAspx);
        }

        [Teardown]
        public void Teardown()
        {
            Site.OpenWeb(_homePage, x => x.DeletePublishingPage(_tempPublishingPage.Name));
        }
    }
} 

System: check that it all works in SharePoint when deployed

TODO

  • have an attribute
  • be able to read the attributes from a class
  • return a publisher for a page rather than a page
  • create a service for processing a publisher
  • actually publish pages in the context of SharePoint (integration)
  • check that it all works in SharePoint when deployed (system)
  • add new types of publishers into the factory class

The final step in this cadence to create system tests by completing the acceptance tests. Theoretically, this step should not be needed because the code talks to SharePoint. In practice, this step finds problems and cleans up code at the same time. Let’s return to the test that we originally wrote and have been delivering to – but not coding against. We are now going to implement system test using test-last development. Here is the story:

Story is Solution Deployment

In order to create new pages for user
As a user
I want a MySites available

With scenario have a new feature
  Given I have a new wsp package mysites.wsp
  When site is deployed
    And I am on site http://mysites/personal/45678
  Then Publishing Site Feature F6924D36-2FA8-4f0b-B16D-06B7250180FA is site activated

You may notice in the code that above, we have actually added some more information from the original story. We now know that the personal site has an Id in it (http://mysites/personal/45678) and we also know the GUID of the site (F6924D36-2FA8-4f0b-B16D-06B7250180FA). Adding, changing and morphing system tests often happens, particularly as we learn more about our implementation – in our case, we have to run the analysts/product owner through these changes!

Now we need to find a library to provide an implementation. There are number of libraries: Cucumber, nBehave or StoryQ. We have chosen StoryQ. StoryQ has a GUI converter that can take the text input above and turn it into C# skeleton code as per below and then we fill out the code (Cucumber keeps the story separate from implementation). StoryQ will then output the results of the tests in the test runner.

  using StoryQ;
  using NUnit.Framework;

  namespace Test.System.Acceptance.Provisioning
  {
      [TestFixture]
      public class SolutionDeploymentTest
      {
          [Test]
          public void SolutionDeployment()
          {
              new Story("Solution Deployment")
                  .InOrderTo("create new pages for users")
                  .AsA("user")
                  .IWant("a MySites available")

                  .WithScenario("have a new feature")
                    .Given(IHaveANewWspPackage_, "mysites.wsp")
                    .When(SiteIsDeployed)
                      .And(IAmOnSite_, "http://mysites/personal/684945")
                    .Then(__IsSiteActivated, "Publishing Site Feature", "F6924D36-2FA8-4f0b-B16D-06B7250180FA")
                 
                  .Execute();
          }

          private void IHaveANewWspPackage_(string wsp)
          {
              throw new NotImplementedException();
          }

          private void SiteIsDeployed()
          {
              throw new NotImplementedException();
          }

          private void IAmOnSite_(string site)
          {
              throw new NotImplementedException();
          }    

          private void __IsSiteActivated(string title, string guid)
          {
              throw new NotImplementedException();
          }
      }
  }

Our implementation of the system is surprisingly simple. Because we are provisionin new features, the main test is that the deployment sequence has worked. So we are going to make two checks (once the code is deployed), is the solution deployed? is the feature activated? To do this we are going to need to write a couple of abstractions around the SharePoint API: Solution.IsDeployed & Feature.IsSiteActivated. We do this because we want the system tests to remain extremely clean.

  using StoryQ;
  using NUnit.Framework;

  namespace Test.System.Acceptance.Provisioning
  {
      [TestFixture]
      public class SolutionDeploymentTest
      {
          [Test]
          public void SolutionDeployment()
          {
              new Story("Solution Deployment")
                  .InOrderTo("create new pages for users")
                  .AsA("user")
                  .IWant("a MySites available")

                  .WithScenario("have a new feature")
                    .Given(IHaveANewWspPackage_, "mysites.wsp")
                    .When(SiteIsDeployed)
                      .And(IAmOnSite_, "http://mysites/personal/684945")
                    .Then(__IsSiteActivated, "Publishing Site Feature", "F6924D36-2FA8-4f0b-B16D-06B7250180FA")
                 
                  .Execute();
          }

          private string _site;
          private string _wsp;

          private void IHaveANewWspPackage_(string wsp)
          {
              _wsp = wsp;
          }

          private void SiteIsDeployed()
          {
              Assert.IsTrue(Solution.IsDeployed(_wsp));
          }

          private void IAmOnSite_(string site)
          {
              _site = site;
          }    

          private void __IsSiteActivated(string title, string guid)
          {
              Assert.IsTrue(Feature.IsSiteActivated(_httpMysitesPersonal, guid));
          }
      }
  }

Here are the wrappers around the SharePoint API that we are going to get reuse. Sometimes we wrap the current API SPFarm:

public static class Solution
{
    public static bool IsDeployed(string wsp)
    {
        return SPFarm.Local.Solutions
            .Where(x => x.Name == wsp)
            .Any();
    }
} 

Sometimes we are wrapping our on wrappers Site.Open.

public static class Feature
{
    public static bool IsSiteActivated(string webUrl, string feature)
    {
        return Site.Open(webUrl, site => 
             site.Features
                .Where(x =>
                    x.Definition.Id == new Guid(feature) &&
                    x.Definition.Status == SPObjectStatus.Online).Any()
             );
    }
} 

Starting to wrap up the cadence for adding a feature

Below is the TODO list that we started with as we layered our code with a test automation pyramid straegy and are now up to adding new publishers

  • have an attribute
  • be able to read the attributes from a class
  • return a publisher for a page rather than a page
  • create a service for processing a publisher
  • actually publish pages in the context of SharePoint (integration)
  • check that it all works in SharePoint when deployed (system)
  • add new types of publishers into the factory class

So far we have only implemented NewPage and still have ActivateFeature, RemovePage and MasterPage to go:

public class SiteFeatureEventReceiver : SPFeatureReceiver
{
    [NewPage("Home.aspx", "Home Page", "HomePage.aspx")]
    [ActivateFeature(PackageCfg.PublishingWebFeature)]
    [RemovePage("Pages/default.aspx")]
    [MasterPage("CustomV4.master", MasterPage.MasterPageType.User)]
    [MasterPage("CustomMySite.master", MasterPage.MasterPageType.Host)]
    public override void FeatureActivated(SPFeatureReceiverProperties properties)
    {
        PersonalSiteProvisioner
          .Create(properties.Feature.Parent as SPWeb)
          .Process();
    }
}

To bring in the new pages, it would be a mistake to start implementing the attributes. Instead, we need to write a system test. This may merely be an extension of the current system test. Then we need to proceed through unit and integration tests. What we’ll find is that as we add a new page we are likely to find that we are going to need to add a new concept of a factory of some sort that will return our publishers for each provisioning attribute.

So let me finish with the new acceptance test for [ActivateFeature(PackageCfg.PublishingWebFeature)] and a TODO list.

Story is Solution Deployment

In order to create new pages for user
As a user
I want a MySites available

With scenario have a new feature
  Given I have a new wsp package mysites.wsp
  When site is deployed
    And I am on site http://mysites/personal/45678
  Then Publishing Site Feature F6924D36-2FA8-4f0b-B16D-06B7250180FA is site activated
    And Publishing Feature is web activated

And the TODO now becomes:

TODO

  • have a new attribute ActivateFeature (unit)
  • be able to read the attribute (unit)
  • return a publisher for a page rather than a page ActivateFeaturePublisher? (unit)
  • extend service for processing a publisher
  • actually activate feature in the context of SharePoint (integration)
  • complete acceptance test (system)

Convinced?

This is a lot of work. Layering tends to do this. But it provides the basis for scaling code base. Quite quickly we have found that there are patterns for reuse, we pick up SharePoint API problems in integration tests rather than post-deployment in system tests, that we can move around the code base easily-ish, we can refactor because code is under test. All of this provides us with the possibility for scale benefits in terms of speed and quality. A system that is easy to code tends not to provide these scale benefits.

Categories: Uncategorized Tags: , ,
  1. No comments yet.
  1. No trackbacks yet.