Showing posts with label Unit Testing. Show all posts
Showing posts with label Unit Testing. Show all posts

Sunday, June 13, 2010

Silverlight RIA Services Load and Performance Testing

Introduction

I was able to do some initial unit testing of my RIA Service methods using Silverlight unit test projects. Now I needed to do some performance/load testing of the asynchronous RIA Services methods. After doing some research I quickly found out that I could not use the Enqueue* methods that come with the Silverlight Unit Testing frame; my solution using multithreading.

The goal of the performance/load testing is to ensure that the new RIA Services do not become a bottleneck. The system we are writing needs to be able to support lots of transactions and some transactions take a long time to calculate in the database end. Even though the user experience is acceptable because RIA easily supports asynchronous transactions, we have to be concerned about the amount of resources being consumed on the IIS machine which is hosting the RIA Services.

Some colleagues had suggested to me to write unit tests outside of the Silverlight and call the RIA Service methods using WCF directly. However I believe that is not a good solution because:

  • I cannot use the generated code that call the WCF RIA methods and I would have to subsequently write a lot of new code to call those services. Plus if there are inefficiencies in the generated code, they would not be discovered.
  • I cannot use my ViewModels which we had spent a lot time developing so they would be testable.

The Solution

I was able to come up with a simple solution that allows me to use Silverlight Unit Test project to call my RIA Services. The solution is to:

  • Initiate threads from the test method to call the RIA Service methods.
  • Write logs out to a log file that captures the duration of each RIA Service call.

Another nice thing about this solution is I have the ability to install the Unit Test Silverlight page onto the eventual server and run the unit tests from a client machine.

I basically had to relearn System.Threading and I was able to get things to work. Below I will show you the code on how to do some basic load testing of your RIA Services. I still like run the load/performance tests off a development environment. This is because production machines are more powerful and will hide performance errors. It is better to discover them on a machine that less resources.

The Code

I highly recommend reading the last section this blog so you understand the design, issues and challenges so you understand why I did what I did. I know many just look for code to copy – I know I do <g>

This is the test class. In it you can see that all I do is create a log file that will be used by all the threads to write to. I then initiate the search threads by calling a class called SearchTestScenario.

[TestClass]
public class Tests : SilverlightTest
{
MyBusinessContext context;
bool blnSearch1Complete = false;
bool blnSearch2Complete = false;
bool blnSearch3Complete = false;

[TestMethod]
public void TestMethod()
{
string fileName = CreateLogFile();

SearchTestScenario searchTest1 = new SearchTestScenario(fileName);
Thread thread1 = new Thread(new ThreadStart(searchTest1.Search1));
thread1.Start();

SearchTestScenario searchTest2 = new SearchTestScenario(fileName);
Thread thread2 = new Thread(new ThreadStart(searchTest2.Search2));
thread2.Start();

SearchTestScenario searchTest3 = new SearchTestScenario(fileName);
Thread thread3 = new Thread(new ThreadStart(searchTest3.Search3));
thread3.Start();
}

private string CreateLogFile()
{
using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
{
string strLogFileName = System.DateTime.Now.ToString().Replace("/", "_").Replace(":", "_").Replace(" ", "_") + "_Search.txt";
IsolatedStorageFileStream rootFile = store.CreateFile(strLogFileName);
rootFile.Close();

return strLogFileName;
}
}

}

Here is SearchTestScenario. In this class I write methods that will execute searches and do some logs. I have not added anything into the call backs but that would be a good place to do some Asserts.

public class SearchTestScenario
{

private MyBusinessContext context;
private System.DateTime duration;
private string fileName;

public SearchTestScenario(string fileName)
{
context = new MyBusinessContext();
this.fileName = fileName;
}

public void Search1()
{
duration = System.DateTime.Now;
context.Load(context.SearchQuery("foo", "bar", "blah"), Search1Complete, null);

Log("Search1 Started - " + duration.ToString());
}

public void Search2()
{
duration = System.DateTime.Now;
context.Load(context.SearchQuery("foo", "bar", "blah"), Search2Complete, null);

Log("Search2 started - " + duration.ToString());
}

public void Search3()
{
duration = System.DateTime.Now;
context.Load(context.SearchQuery("foo", "bar", "blah"), Search3Complete, null);

Log("Search3 Search - " + duration.ToString());
}

private void Search1Complete(LoadOperation<SearchResult> oLoadOperation)
{
TimeSpan span = System.DateTime.Now.Subtract(duration);
Log("Search1Complete Duration - " + span.Seconds.ToString());
}

private void Search2Complete(LoadOperation<SearchResult> oLoadOperation)
{
TimeSpan span = System.DateTime.Now.Subtract(duration);
Log("Search2Complete Duration - " + span.Seconds.ToString());
}

private void Search3Complete(LoadOperation<SearchResult> oLoadOperation)
{
TimeSpan span = System.DateTime.Now.Subtract(duration);
Log("Search3Complete Duration - " + span.Seconds.ToString());
}

static readonly object lockObject = new object();
private void Log(string message)
{
lock (lockObject)
{
using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
{
using (IsolatedStorageFileStream rootFile = store.OpenFile(fileName, FileMode.Append, FileAccess.Write))
{
using (StreamWriter sw = new StreamWriter(rootFile))
{
sw.WriteLine(message);
sw.Close();
}
rootFile.Close();
}
}
}
}

}

Design, Issues and Challenges

I have to admit I ran into a lot of problems trying to get this simple solution working. I had to relearn threading but there were tons of issues I had to address. Here is a short list of them.

  • I elected to not use a single instance of RIA Service context and pass that from the main thread into the running threads. This is because you get locking issues when access that object instance across threads. Even though I could lock it, I figured there was no value to it either.
  • For someone who is experienced with threading you will probably ask why did I not join all the running search threads back together when the searches were completed. Let me tell you I tried and burned lots of hours. I tried a solution where I used WaitHandle.WaitAll with ManualResetEvent for each running search thread. The problem was when a RIA Service method was called the entire unit test would freeze. I found this which was very similar to what I was experiencing. The net effect is the unit test will complete and if any assertions or errors occur, they will be treated as exceptions and not be picked up by the unit testing framework.
  • I do not create a single search method on SearchTestScenario because I may want to test for different thresholds for each search query.
  • I have to write the results to isolated storage because that was the easiest thing to get access to very quickly.

Relearn Threading Resources

Thursday, June 10, 2010

Silverlight Unit Test Project Templates Compile Error

Introduction

Silverlight Unit Test project templates work perfect fine Silverlight Application project templates for Silverlight 4 and Visual Studio 2010. They will compile right off the bat. However Silverlight Business Application project templates will not immediately compile with a Silverlight Unit Test project template.

If you were to:

  1. Create a Silverlight Business Application.
  2. Then add Silverlight Unit Test. Enable WCF RIA Services and select the Business Application project as the Host.
  3. Then compile without making any changes.

What will result is compile errors and you will get different compile errors based on using C# or VB.net

C# Template

The following are the errors you would see in C#. To resolve these errors all you need to do is add a reference to the Silverlight Business Application to the Silverlight Unit Test project.

The annoying thing about this bug is that when you add the Silverlight Unit Test project the expectation is that this reference would be created since you selected the Business Application as the hosting project. You do not have to do this with Silverlight Applications.

Error 1 The type or namespace name 'Resources' does not exist in the namespace 'BusinessApplication1.Web' (are you missing an assembly reference?) C:\BusinessApplication1\SilverlightTest1\Generated_Code\BusinessApplication1.Web.g.cs 299 101 SilverlightTest1

Error 2 The type or namespace name 'Resources' does not exist in the namespace 'BusinessApplication1.Web' (are you missing an assembly reference?) C:\BusinessApplication1\SilverlightTest1\Generated_Code\BusinessApplication1.Web.g.cs 334 92 SilverlightTest1

Error 3 The type or namespace name 'Resources' does not exist in the namespace 'BusinessApplication1.Web' (are you missing an assembly reference?) C:\BusinessApplication1\SilverlightTest1\Generated_Code\BusinessApplication1.Web.g.cs 367 138 SilverlightTest1

Error 4 The type or namespace name 'Resources' does not exist in the namespace 'BusinessApplication1.Web' (are you missing an assembly reference?) C:\BusinessApplication1\SilverlightTest1\Generated_Code\BusinessApplication1.Web.g.cs 398 103 SilverlightTest1

Error 5 The type or namespace name 'Resources' does not exist in the namespace 'BusinessApplication1.Web' (are you missing an assembly reference?) C:\BusinessApplication1\SilverlightTest1\Generated_Code\BusinessApplication1.Web.g.cs 437 95 SilverlightTest1

VB.net Template

You will get a couple more errors when doing VB.net. To resolve first add a reference to the Business Application project as we did for the C# solution. That will resolve errors 6, 8, and 9 however 1 to 5 will still remain. This is pretty confusing because doing that resolved the same issues for VB.net.

Next you need to either the following:

  1. Go to the Silverlight Unit Test Project properties. Change the Root Namespace to BusinessApplication1 in this case.
  2. Or go the BusinessAppllication1.Web project (the RIA Services project), go to the Models folder, and modify RegistrationData.vb. You will need to remove all reference to RegistrationDataResources in that class. What is happening is the RegistrationDataResources.resx is not projecting to the Silverlight Unit Test project and subsequently creating reference errors.

Error 1 Type 'BusinessApplication1.RegistrationDataResources' is not defined. C:\BusinessApplication1\SilverlightTest1\Generated_Code\BusinessApplication1.Web.g.vb 305 79 SilverlightTest1

Error 2 Type 'BusinessApplication1.RegistrationDataResources' is not defined. C:\BusinessApplication1\SilverlightTest1\Generated_Code\BusinessApplication1.Web.g.vb 336 70 SilverlightTest1

Error 3 Type 'BusinessApplication1.RegistrationDataResources' is not defined. C:\BusinessApplication1\SilverlightTest1\Generated_Code\BusinessApplication1.Web.g.vb 365 117 SilverlightTest1

Error 4 Type 'BusinessApplication1.RegistrationDataResources' is not defined. C:\BusinessApplication1\SilverlightTest1\Generated_Code\BusinessApplication1.Web.g.vb 392 81 SilverlightTest1

Error 5 Type 'BusinessApplication1.RegistrationDataResources' is not defined. C:\BusinessApplication1\SilverlightTest1\Generated_Code\BusinessApplication1.Web.g.vb 427 73 SilverlightTest1

Error 6 'FriendlyName' is not a member of 'SilverlightTest1.Web.User'. C:\BusinessApplication1\SilverlightTest1\Generated_Code\Models\Shared\User.shared.vb 10 45 SilverlightTest1

Error 7 'FriendlyName' is not a member of 'SilverlightTest1.Web.User'. C:\BusinessApplication1\SilverlightTest1\Generated_Code\Models\Shared\User.shared.vb 11 28 SilverlightTest1

Error 8 'Name' is not a member of 'SilverlightTest1.Web.User'. C:\BusinessApplication1\SilverlightTest1\Generated_Code\Models\Shared\User.shared.vb 13 28 SilverlightTest1