Load Test Modeling: How to Simulate User Logon and Execute Tests in a Test Mix
The following sample shows how to model the common scenario where a user logs on once to a Web site and performs a number of tests.
In the sample, the logon action is definded by a set of login requests. Each Web test contains the same set of login requests plus its own non-login requests.
A “new user” as defined by “Percentage of New Users” under the Load test scenario property always executes Login once and one iteration of a Web test (the non-login section) from the Test Mix. A “return user” always executes Logon once and a random number of Web tests (the non-login sections) from the Test Mix.
Modeling can be achieved by using plug-ins or coded web tests .
==============================================================================
A. To do this using plug-ins:
- Create a new test project.
- Add WebRequestPlugin.cs, WebTestPlugin.cs, LoadTestPlugin.cs, VUserInfo.cs and WebTestUserMonitor.cs to the project.
- Create the web tests. Each Web test contains the same set of login request(s). i.e
WebTest1
login requests
non-login requests
WebTest2
login requests
non-login requests
WebTest3
login requests
non-login requests
Suffix the set of login requests. i.e,
If the original set of login requests are
https://server/loginRequest1.aspx
https://server/loginRequest2.aspx
https://server/loginRequest3.aspx
...
https://server/loginRequestN.aspx
Login requests after appending suffixes will be
https://server/loginRequest1.aspxLoginRequestFirst
https://server/loginRequest2.aspxLoginRequest
https://server/loginRequest3.aspxLoginRequest
...
https://server/loginRequestN.aspxLoginRequestLast
- Add the Web test and request plug-ins to all of your Web tests
- Add the web tests to a load test scenario
- Optional. Add the load test plug-in to the load test if you want to output the number of login test executed. You can generate your own report by modifying the Stop() in WebTestUserMonitor class.
WebTestPlugin.cs
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.VisualStudio.TestTools.WebTesting;
namespace TestMixPlugins
{
public class TestPlugin : WebTestPlugin
{
public override void PreWebTest(object sender, PreWebTestEventArgs e)
{
WebTestUserMonitor.Instance.PreWebTest(sender, e);
}
public override void PostWebTest(object sender, PostWebTestEventArgs e)
{
WebTestUserMonitor.Instance.PostWebTest(sender, e);
}
}
}
WebRequestPlugin.cs
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.VisualStudio.TestTools.WebTesting;
namespace TestMixPlugins
{
public class RequestPlugin : WebTestRequestPlugin
{
public override void PreRequest(object sender, PreRequestEventArgs e)
{
WebTestUserMonitor.Instance.login_PreRequest(sender, e);
}
public override void PostRequest(object sender, PostRequestEventArgs e)
{
bool login = e.WebTest.Context.ContainsKey(WebTestUserMonitor.LOGIN) ? (bool)e.WebTest.Context[WebTestUserMonitor.LOGIN] : true;
bool isLastLoginRequest = e.WebTest.Context.ContainsKey(WebTestUserMonitor.IS_LAST_LOGIN_REQUEST) ? (bool)e.WebTest.Context[WebTestUserMonitor.IS_LAST_LOGIN_REQUEST] : false;
// if login = true && this is the last login request
if (login && isLastLoginRequest)
{
WebTestUserMonitor.Instance.login_PostRequest(sender, e);
return;
}
}
}
}
LoadTestPlugin.cs
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.VisualStudio.TestTools.LoadTesting;
namespace TestMixPlugins
{
public class LoadTestPlugin : ILoadTestPlugin
{
public void Initialize(LoadTest loadTest)
{
m_loadTest = loadTest;
m_loadTest.LoadTestStarting += new EventHandler(m_loadTest_LoadTestStarting);
m_loadTest.LoadTestFinished += new EventHandler(m_loadTest_LoadTestFinished);
}
void m_loadTest_LoadTestStarting(object sender, EventArgs e)
{
WebTestUserMonitor.Instance.Start();
}
void m_loadTest_LoadTestFinished(object sender, EventArgs e)
{
WebTestUserMonitor.Instance.Stop();
}
private static LoadTest m_loadTest;
}
}
VUserInfo.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace TestProject1
{
class VUserInfo
{
// This class stores a vuser's information during test execution
public VUserInfo(int userId)
{
m_userId = userId;
m_numTestExecuted = 0;
m_loginContext = new Dictionary<string, object>();
m_testList = new List<string>();
}
public int UserId
{
get { return m_userId; }
}
// Number of tests that have been completed by the user
public int NumTestExecuted
{
get { return m_numTestExecuted; }
set { m_numTestExecuted = value; }
}
// Web test context needed to issue non-login requests for a return user
public Dictionary<string, object> LoginContext
{
get { return m_loginContext; }
set { m_loginContext = value; }
}
public List<string> TestList
{
get { return m_testList; }
}
private int m_userId;
private int m_numTestExecuted;
private Dictionary<string, object> m_loginContext;
private List<string> m_testList;
}
}
WebTestUserMonitor.cs
using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.WebTesting;
namespace TestMixPlugins
{
public class WebTestUserMonitor
{
public static WebTestUserMonitor Instance
{
get { return s_instance; }
}
public void PreWebTest(object sender, PreWebTestEventArgs e)
{
int userId = e.WebTest.Context.WebTestUserId;
bool isNewUser = e.WebTest.Context.IsNewUser;
bool login = false;
lock (s_usersLock)
{
if (!s_users.ContainsKey(userId))
{
s_users.Add(userId, new VUserInfo(userId));
}
int numTestExecuted = s_users[userId].NumTestExecuted;
// if this is a new user, enable login
// if this is a returning user, add the login context needed for the test
if (isNewUser || numTestExecuted == 0)
{
login = true;
}
else
{
e.WebTest.Context.Add(LOGIN_CONTEXT, s_users[userId].LoginContext);
}
}
e.WebTest.Context.Add(LOGIN, login);
}
public void PostWebTest(object sender, PostWebTestEventArgs e)
{
int userId = e.WebTest.Context.WebTestUserId;
string testName = e.WebTest.Name;
lock (s_usersLock)
{
s_users[userId].NumTestExecuted++;
s_users[userId].TestList.Add(testName);
if (e.WebTest.Context.IsNewUser)
{
s_users.Remove(userId);
}
else
{
// save the login context for future usage
if ((bool)e.WebTest.Context[LOGIN])
{
s_users[userId].LoginContext = (Dictionary<string, object>)e.WebTest.Context[LOGIN_CONTEXT];
}
}
bool login = e.WebTest.Context.ContainsKey(LOGIN) ? (bool)e.WebTest.Context[LOGIN] : true;
if (login)
{
s_numOfLoginTest++;
}
}
}
public void login_PreRequest(object sender, PreRequestEventArgs e)
{
bool login = e.WebTest.Context.ContainsKey(LOGIN) ? (bool)e.WebTest.Context[LOGIN] : true;
bool isFirstLoginRequest = true;
bool isLastLoginRequest = true;
bool isLoginRequest = true;
isFirstLoginRequest = e.Request.Url.Contains("LoginRequestFirst");
isLastLoginRequest = e.Request.Url.Contains("LoginRequestLast");
isLoginRequest = e.Request.Url.Contains("LoginRequest");
// remove the suffix from the request
if (isLoginRequest)
{
int endPos = e.Request.Url.IndexOf("LoginRequest");
if (endPos > 0)
{
e.Request.Url = e.Request.Url.Substring(0, endPos);
}
}
if (login)
{
if (isFirstLoginRequest)
{
// copy the context before the first login request is issued
KeyValuePair<string, object>[] contextBeforeLogin = new KeyValuePair<string, object>[e.WebTest.Context.Count];
e.WebTest.Context.CopyTo(contextBeforeLogin, 0);
e.WebTest.Context.Add(CONTEXT_BEFORE_LOGIN, contextBeforeLogin);
}
if (isLastLoginRequest)
{
e.WebTest.Context.Add(IS_LAST_LOGIN_REQUEST, true);
}
return;
}
// login is not needed, skip the login requests
if (isLoginRequest)
{
e.Instruction = WebTestExecutionInstruction.Skip;
}
if (isFirstLoginRequest)
{
// Copy the Login context required for the rest of the test
int count = ((Dictionary<string, object>)e.WebTest.Context[LOGIN_CONTEXT]).Count;
KeyValuePair<string, object>[] loginContext = new KeyValuePair<string, object>[count];
((ICollection<KeyValuePair<string, object>>)e.WebTest.Context[LOGIN_CONTEXT]).CopyTo(loginContext, 0);
System.Collections.IEnumerator ienum = loginContext.GetEnumerator();
while (ienum.MoveNext())
{
KeyValuePair<string, object> keyValuePair = (KeyValuePair<string, object>)ienum.Current;
e.WebTest.Context.Add(keyValuePair.Key, keyValuePair.Value);
}
}
}
public void login_PostRequest(object sender, PostRequestEventArgs e)
{
// this is the last login request
Dictionary<string, object> contextBeforeLogin = new Dictionary<string, object>();
Dictionary<string, object> contextChangedByLogin = new Dictionary<string, object>();
System.Collections.IEnumerator ienum = ((KeyValuePair<string, object>[])e.WebTest.Context[CONTEXT_BEFORE_LOGIN]).GetEnumerator();
while (ienum.MoveNext())
{
KeyValuePair<string, object> keyValuePair = (KeyValuePair<string, object>)ienum.Current;
contextBeforeLogin.Add(keyValuePair.Key, keyValuePair.Value);
}
// compare the contexts before and after login, get the context changed by the login
foreach (KeyValuePair<string, object> keyValuePair in e.WebTest.Context)
{
string key = keyValuePair.Key;
object value1 = keyValuePair.Value;
if (contextBeforeLogin.ContainsKey(key))
{
object value2 = contextBeforeLogin[key];
if (value1.ToString().ToUpper().CompareTo(value2.ToString().ToUpper()) != 0)
{
contextChangedByLogin.Add(key, value1);
}
}
else
{
contextChangedByLogin.Add(key, value1);
}
}
e.WebTest.Context.Add(LOGIN_CONTEXT, contextChangedByLogin);
e.WebTest.Context.Remove(IS_LAST_LOGIN_REQUEST);
}
public void Start()
{
FileInfo file = new FileInfo(m_logFile);
if (file.Exists)
{
file.Delete();
}
}
public void Stop()
{
lock (s_usersLock)
{
File.AppendAllText(m_logFile, "Number of login tests executed: " + s_numOfLoginTest.ToString());
}
}
public static readonly string LOGIN = "Login";
public static readonly string IS_LAST_LOGIN_REQUEST = "IsLastLoginRequest";
private const string CONTEXT_BEFORE_LOGIN = "ContextBeforeLogin";
private const string LOGIN_CONTEXT = "LoginContext";
private readonly string m_logFile = "d:\\result.txt";
private static object s_usersLock = new object();
private static int s_numOfLoginTest = 0;
private static WebTestUserMonitor s_instance = new WebTestUserMonitor();
private static Dictionary<int, VUserInfo> s_users = new Dictionary<int, VUserInfo>();
}
}
=====================================================================
B. To do this using coded web tests:
Create a new test project.
Add VUserInfo.cs and WebTestUserMonitor.cs to the project.
Create the web tests. Each web test should include the same set of login request(s).
Generate coded Web tests from the existing ones.
Modify the coded Web tests as suggested in the following code sample.
Add the coded Web tests to a load test.
WebTestUserMonitor.cs
using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.WebTesting;
namespace TestProject1
{
public class WebTestUserMonitor
{
public WebTestUserMonitor()
{
}
public static WebTestUserMonitor Instance
{
get { return s_instance; }
}
public void PreWebTest(object sender, PreWebTestEventArgs e)
{
int userId = e.WebTest.Context.WebTestUserId;
// Is this a new user
bool isNewUser = e.WebTest.Context.IsNewUser;
bool login = false;
lock (s_usersLock)
{
if (!s_users.ContainsKey(userId))
{
s_users.Add(userId, new VUserInfo(userId));
}
int numTestExecuted = s_users[userId].NumTestExecuted;
// If this is a new user, enable login
// If this is a return user, add the login context needed for the test
if (isNewUser || numTestExecuted == 0)
{
login = true;
}
else
{
e.WebTest.Context.Add(LOGIN_CONTEXT, s_users[userId].LoginContext);
}
}
e.WebTest.Context.Add(LOGIN, login);
}
public void PostWebTest(object sender, PostWebTestEventArgs e)
{
int userId = e.WebTest.Context.WebTestUserId;
string testName = e.WebTest.Name;
lock (s_usersLock)
{
s_users[userId].NumTestExecuted++;
s_users[userId].TestList.Add(testName);
if (e.WebTest.Context.IsNewUser)
{
s_users.Remove(userId);
}
else
{
// save the login context for future usage
if ((bool)e.WebTest.Context[LOGIN])
{
s_users[userId].LoginContext = (Dictionary<string, object>)e.WebTest.Context[LOGIN_CONTEXT];
}
}
}
}
public static void login_PreRequest(object sender, PreRequestEventArgs e)
{
KeyValuePair<string, object>[] contextBeforeLogin =
new KeyValuePair<string, object>[e.WebTest.Context.Count];
// Get a copy of the context before the login request is issued
e.WebTest.Context.CopyTo(contextBeforeLogin, 0);
e.WebTest.Context.Add(CONTEXT_BEFORE_LOGIN, contextBeforeLogin);
}
public static void login_PostRequest(object sender, PostRequestEventArgs e)
{
Dictionary<string, object> contextBeforeLogin =
new Dictionary<string, object>();
Dictionary<string, object> contextChangedByLogin =
new Dictionary<string, object>();
System.Collections.IEnumerator ienum = ((KeyValuePair<string,object>[])e.WebTest.Context[CONTEXT_BEFORE_LOGIN]).GetEnumerator();
while (ienum.MoveNext())
{
KeyValuePair<string, object> keyValuePair =
(KeyValuePair<string, object>)ienum.Current;
contextBeforeLogin.Add(keyValuePair.Key, keyValuePair.Value);
}
// Compare the contexts before and after login, and
// get the context changed by the login
foreach (KeyValuePair<string, object> keyValuePair in e.WebTest.Context)
{
string key = keyValuePair.Key;
object value1 = keyValuePair.Value;
if (contextBeforeLogin.ContainsKey(key))
{
object value2 = contextBeforeLogin[key];
if (value1.ToString().ToUpper().CompareTo(value2.ToString().ToUpper()) != 0)
{
contextChangedByLogin.Add(key, value1);
}
}
else
{
contextChangedByLogin.Add(key, value1);
}
}
e.WebTest.Context.Add(LOGIN_CONTEXT, contextChangedByLogin);
}
private const string CONTEXT_BEFORE_LOGIN = "ContextBeforeLogin";
private const string LOGIN_CONTEXT = "LoginContext";
private const string LOGIN = "Login";
private static object s_usersLock = new object();
private static WebTestUserMonitor s_instance = new WebTestUserMonitor();
private static Dictionary<int, VUserInfo> s_users =
new Dictionary<int, VUserInfo>();
}
}
WebTestCoded.cs
namespace TestProject1
{
using System;
using System.Collections.Generic;
using System.Text;
using TestMixPlugins;
using Microsoft.VisualStudio.TestTools.WebTesting;
using Microsoft.VisualStudio.TestTools.WebTesting.Rules;
public class WebTest2Coded : WebTest
{
public WebTest2Coded()
{
this.PreAuthenticate = true;
//---------------------------------------
// Modification 1
//---------------------------------------
// Add the following two lines to your coded web test
this.PreWebTest += new EventHandler<PreWebTestEventArgs>(WebTestUserMonitor.Instance.PreWebTest);
this.PostWebTest += new EventHandler<PostWebTestEventArgs>(WebTestUserMonitor.Instance.PostWebTest);
}
public override IEnumerator<WebTestRequest> GetRequestEnumerator()
{
//---------------------------------------
// Modification 2 - Login Section
//---------------------------------------
bool login = Context.ContainsKey("Login") ? (bool)Context["Login"] : true;
// Incorporate the following if-else block into the login section
if (login)
{
WebTestRequest loginRequest = new WebTestRequest("https://server/site/login.aspx");
ExtractHiddenFields rule1 = new ExtractHiddenFields();
rule1.ContextParameterName = "1";
loginRequest.ExtractValues += new EventHandler<ExtractionEventArgs>(rule1.Extract);
// Insert the PreRequest and PostRequst handlers for the login request
loginRequest.PreRequest += new EventHandler<PreRequestEventArgs>(WebTestUserMonitor.login_PreRequest);
loginRequest.PostRequest += new EventHandler<PostRequestEventArgs>(WebTestUserMonitor.login_PostRequest);
yield return loginRequest;
}
else
{
// Copy the Login context required for the test
int count = ((Dictionary<string, object>)Context["LoginContext"]).Count;
KeyValuePair<string, object>[] loginContext = new KeyValuePair<string,object>[count];
((ICollection<KeyValuePair<string, object>>)Context["LoginContext"]).CopyTo(loginContext, 0);
System.Collections.IEnumerator ienum = loginContext.GetEnumerator();
while (ienum.MoveNext())
{
KeyValuePair<string, object> keyValuePair = (KeyValuePair<string, object>)ienum.Current;
Context.Add(keyValuePair.Key, keyValuePair.Value);
}
}
// End of Modification 2
WebTestRequest request1 = new WebTestRequest("https://server/site/page1.aspx");
yield return request1;
WebTestRequest request2 = new WebTestRequest("https://server/site/page2.aspx");
yield return request2;
WebTestRequest request3 = new WebTestRequest("https://server/site/page3.aspx");
yield return request3;
}
}
}
Comments
- Anonymous
June 17, 2006
Could you provide more detailed explanation for this code?