How to test SharePoint development code

原文链接:http://www.sharepointdevwiki.com/display/SharePointPlaybook/How+to+test+SharePoint+development+code

 

 

Most instances of development you will find will be hooked off of a Feature Receiver, Event Receiver, Application Pages (code behind) or in Web Parts.

SharePoint objects are needed in context of various actions

More often than not they will be using SharePoint objects in context of the object they are in, for example:

  • a Feature Receiver may get the site (SPWeb) object that it has been activated from and check that the list already exist before it provisions an extra column to it if certain business logic is met
  • a Event Receiver most likely get the list item (SPListItem) object when the Event is triggered () and check that a column has been set properly or validate it against some business logic
  • a Application Page hosted in SharePoint may get the site (SPWeb) object that it is hosted in to check whether a particular property is set in the property bag and then make some business logic decisions based on this and modify the site object.
  • a Web Part may get the site (SPWeb) object to get a list (SPList) object to query it to render a message based on some business logic.

Extract Business Logic

Most of the sample code you see will have these objects inline in the method within, for example, the Feature Receiver|FeatureActivated method.

Simple Example

The below example shows how there is business logic that will only provision a blog in a site if the site Title contains "Department". Obviously this is a very simple Feature Receiver, this is just for the purposes of this example (and yes there are other approaches to this functionality).

FeatureReceiver.cs inline
using Microsoft.SharePoint;
using SampleSolutionLogic;

namespace SampleSolution {
  public class FeatureReceiver : SPFeatureReceiver {
    public override void FeatureActivated(SPFeatureReceiverProperties properties) {
        SPSite site = properties.Feature.Parent as SPSite;
        using (SPWeb web = site.OpenWeb())
        {
           if (web.Title.Contains("Department"))
           {
              //TODO: provision blog
           }
        }
    }
  }
}

The issue with this approach is that it is hard to isolate the business logic to call it in a unit test. So the first thing you have to do is extract this logic in another method. This method is usually in a separate class and most developers put this in a separate Visual Studio project and namespace.

Complexities of separate projects
The issue with having it in a separate project is that you will be building a separate DLL and therefore adding complexities with signing and also in trusting this DLL in your farm by adding SafeControl entries (this can be done using Solution Packages .wsp files).
FeatureReceiver.cs with call to WebHelper class
using Microsoft.SharePoint;
using SampleSolutionLogic;

namespace SampleSolution {
  public class FeatureReceiver : SPFeatureReceiver {
    public override void FeatureActivated(SPFeatureReceiverProperties properties) {
        SPSite site = properties.Feature.Parent as SPSite;
        using (SPWeb web = site.OpenWeb())
        {
           if (WebHelper.IsBlogNeeded(web))
           {
              //TODO: provision blog
           }
        }
    }
  }
}
WebHelper.cs with new encapsulated method
using Microsoft.SharePoint;

namespace SampleSolutionLogic
{
    public class WebHelper
    {
        public static bool IsBlogNeeded(SPWeb web)
        {
            return web.Title.Contains("Department");
        }
    }
}

Testing the extracted method

Now that the method is extracted you can test it. The example below is extremely simple but shows how you can test the criteria passes and fails. For more information on how the SPSite object is mocked please read How to mock the SharePoint objects.

WebHelperTest.cs
using Microsoft.SharePoint;
using NUnit.Framework;
using SampleSolutionLogic;
using TypeMock.ArrangeActAssert;

namespace SampleSolution.Test
{
    [TestFixture]
    public class WebHelperTest
    {
        [Test]
        public void IsBlogNeeded_CriteriaMet()
        {
            //Arrange
            SPWeb web = Isolate.Fake.Instance<SPWeb>(Members.ReturnRecursiveFakes);
            Isolate.Swap.NextInstance<SPWeb>().With(web);

            Isolate.WhenCalled(() => web.Title).WillReturn("Human Resource Department");

            //Act
            bool isBlogNeeded = WebHelper.IsBlogNeeded(web);

            //Assert
            Assert.IsTrue(isBlogNeeded);
        }

        [Test]
        public void IsBlogNeeded_CriteriaFailed()
        {
            //Arrange
            SPWeb web = Isolate.Fake.Instance<SPWeb>(Members.ReturnRecursiveFakes);
            Isolate.Swap.NextInstance<SPWeb>().With(web);

            Isolate.WhenCalled(() => web.Title).WillReturn("Jeremys Workspace");

            //Act
            bool isBlogNeeded = WebHelper.IsBlogNeeded(web);

            //Assert
            Assert.IsFalse(isBlogNeeded);
        }
    }
}
You have to mock every call
@theTrainNDT pointed out that if he adds a call to the TaskProperties within the WebHelper, it was given him errors. This is because in the [Test] we are not mocking the call to TaskProperties. You have to mock every object to get it to return values. This obviously will incur a lot of work as the WebHelper class grows. For more information on mocking each call see How to mock the SharePoint objects page.
Attached code
There is a zip file attached to this page that has all of the code for the final end to end test.

There are other ways to test the Feature Receiver works

In the example where it was inline it would have been very hard to test both paths worked without having two sites with different titles created by automated scripts (or manually sigh) and activating the Feature in both and then testing whether the Blog was created or not under each site.
Again this will not scale up if you wanted to have tens to hundreds of different combinations of criteria to test...especially as the methods go from being as simple as this solution to large scale solutions!

"Don't mock where you don't have to"

There has been some debate about not mocking too many SharePoint objects. The only problem with this, is to extract the business logic without the SharePoint objects would mean only accepting simple data types as parameters in your business logic classes. In this example it would be fairly straight forward.

FeatureReceiver.cs with call to WebHelper class
using Microsoft.SharePoint;
using SampleSolutionLogic;

namespace SampleSolution {
  public class FeatureReceiver : SPFeatureReceiver {
    public override void FeatureActivated(SPFeatureReceiverProperties properties) {
        SPSite site = properties.Feature.Parent as SPSite;
        using (SPWeb web = site.OpenWeb())
        {
          if (WebHelper.IsBlogNeeded(web.Title))
          {
              //TODO: provision blog
          }
        }
    }
  }
}
WebHelper.cs with new encapsulated method
using Microsoft.SharePoint;

namespace SampleSolutionLogic
{
    public class WebHelper
    {
        public static bool IsBlogNeeded(string webTitle)
        {
            return webTitle.Contains("Department");
        }
    }
}

Immediately you can see that rather than passing the site (SPSite) object through we are passing the string of the Title property of the RootWeb. In this simple example it is straight forward, but imagine if you then wanted to recurse through an SPList in your business logic to check if a particular list item (SPListItem) existed...would you pass the List Item Collection as a simple data type array? Is this too much complexity and abstraction? This is a similar approach to what the Patterns & Practices team have done with their Unit Testing (see the Unit Testing SharePoint Guidance on MSDN for more details)

 

 

posted on 2010-04-27 18:24  王丹小筑  阅读(328)  评论(0)    收藏  举报

导航