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).
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). |
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
}
}
}
}
}
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.
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 |
| Attached code There is a zip file attached |
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.
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
}
}
}
}
}
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)
This posting is provided "AS IS" with no warranties, and confers no rights.
浙公网安备 33010602011771号