Something that irks me with HP Service Test is that you have to create each action one at a time. If you use the model I recommend for service test project configuration this can be an issue.
I finally resolved my irk with a little bit of code. I had built a web application to provide reporting information on how service testing is going. I extended this to support adding test cases to existing projects. We currently use a spreadsheet to document our test cases before we create them and then use the project itself as the master copy.
This extension provided an exciting text area that the user could paste in as many test case names as they wanted. A new line separated each test case name. This made sense for us as we would copy from a spreadsheet and then paste into the text area and hit submit. Excel provided new lines between each cell row.
I would split the list on new lines and carriage returns. This was followed by a trim front and back and we would drop anything that failed these rules
- Empty line
- Line length greater than 63 characters; a Service Test action name limit
- Can only contain alphanumeric values, spaces or underscores
- Must not start with a number
I also replaced all spaces with underscores.
The following code segments are C# with a little bit of LINQ.
string[] AllNewTests = TestsTextArea.Text.Split (new char[] {'\n', '\r'});
List<string> TestsToAdd = new List<string>();
foreach (string test in AllNewTests)
{
string trimmed = test.Trim ();
//skip empty strings
if (String.IsNullOrEmpty (trimmed))
{
continue;
}
//skip lines that are too long
if (trimmed.Length > 63)
{
continue;
}
//replace all spaces with underscores
trimmed = trimmed.Replace (' ', '_');
//skip invalid characters
if (!Regex.IsMatch (trimmed, @"^[a-zA-Z0-9_]+$"))
{
continue;
}
//starts with a number
if (Regex.IsMatch (trimmed, @"^[0-9]"))
{
continue;
}
TestsToAdd.Add (trimmed);
}
What we ended up with was a list of Tests to add to our service test project
I then get the path of the project. You will need access to the quality centre or service file system to do this. The trick to finding the path is to get the ID of the project; you can navigate to path/to/service/tests/ID and from there recursively search for *.usr to get the project file. You can navigate directly to it because projects copied via quality centre will create sub folders in between the id and *.usr file using the id of the project it was copied from.
This code should do it:
protected string FindProjectFileInPath (string path)
{
string[] files = Directory.GetFiles (path, "*.usr", SearchOption.AllDirectories);
var sortedFiles = from f in files
orderby f descending
select f;
foreach (string file in sortedFiles)
{
return file;
}
return string.Empty;
}
Back up the *.usr file and the default.usp file as both need to be changed. I just add the ticks to the filename:
string ticks = DateTime.Now.Ticks.ToString() ;
File.Copy (PathToProjectFile, PathToProjectFile.Replace (".usr", ".usr.backup." + ticks));
File.Copy (PathToUspFile, PathToUspFile.Replace (".usp", ".usp.backup." + ticks));
After that it is time to create each new action. We skip actions that already exist in the target file.
foreach (string test in TestsToAdd)
{
if (DoesActionExistInUsrFile (PathToProjectFile, test))
{
continue ;
}
AddActionToUsrFile (PathToProjectFile, test);
if (!AddActionToUspFile (PathToUspFile, test))
{
//An error occured. Configuration error in default.usp
//restore files
File.Delete (PathToProjectFile);
File.Delete (PathToUspFile);
File.Copy (PathToProjectFile.Replace (".usr", ".usr.backup." + ticks), PathToProjectFile);
File.Copy (PathToUspFile.Replace (".usp", ".usp.backup." + ticks), PathToUspFile);
return ;
}
//increment added count
added++;
//create .c file
string cfiledata =
(
test + "()" + "\n" +
"{" + "\n" +
"\t" + "return 0 ;" + "\n" +
"}"
);
CreateCFile (PathToSourceFiles + test + ".c", cfiledata);
}
Does Action Exist in Usr File
protected bool DoesActionExistInUsrFile (string path, string action)
{
return (Ini.IniFileHelper.GetIniFileString (path, "Actions", action, "") != String.Empty);
}
The USR and USP files are INI files. To access these files using .NET check out this article. I followed his instructions and created a library.
The only trick is to make sure your buffers are big enough. I used 65535 because I’m lazy and it is not ever that I’ll have 1,000 plus actions in a project file (65535 / 63 characters = 1040 approx)
Add Action to Usr File
Updating the Usr file isn’t too difficult; we just need to make multiple changes with the gotcha being that each new entry is added second to last. So remove the vuser_end value; add our own and then add vuser_end back in.
protected void AddActionToUsrFile (string path, string action)
{
//[Actions]
Ini.IniFileHelper.WritePrivateProfileString ("Actions", "vuser_end", null, path);
Ini.IniFileHelper.WritePrivateProfileString ("Actions", action, action + ".c", path);
Ini.IniFileHelper.WritePrivateProfileString ("Actions", "vuser_end", "vuser_end.c", path);
//[Recorded Actions]
Ini.IniFileHelper.WritePrivateProfileString ("Recorded Actions", "vuser_end", null, path);
Ini.IniFileHelper.WritePrivateProfileString ("Recorded Actions", action, "0", path);
Ini.IniFileHelper.WritePrivateProfileString ("Recorded Actions", "vuser_end", "0", path);
//[Replayed Actions]
Ini.IniFileHelper.WritePrivateProfileString ("Replayed Actions", "vuser_end", null, path);
Ini.IniFileHelper.WritePrivateProfileString ("Replayed Actions", action, "0", path);
Ini.IniFileHelper.WritePrivateProfileString ("Replayed Actions", "vuser_end", "1", path);
//[Modified Actions]
Ini.IniFileHelper.WritePrivateProfileString ("Modified Actions", "vuser_end", null, path);
Ini.IniFileHelper.WritePrivateProfileString ("Modified Actions", action, "0", path);
Ini.IniFileHelper.WritePrivateProfileString ("Modified Actions", "vuser_end", "1", path);
}
Add Action to Usp File
The USP file is a little more complicated. We need to append our test to run logic action order and another property called the mercury ini tree sons. I believe this property would be for nested action orders. I don’t use these and therefore don’t consider them. Be wary if you use this function without testing it on nested actions.
protected bool AddActionToUspFile (string path, string action)
{
//[RunLogicRunRoot]
string NewRunLogicActionOrder = Ini.IniFileHelper.GetIniFileString (path, "RunLogicRunRoot", "RunLogicActionOrder", "");
string NewMercIniTreeSons = Ini.IniFileHelper.GetIniFileString (path, "RunLogicRunRoot", "MercIniTreeSons", "");
if (NewRunLogicActionOrder == String.Empty || NewMercIniTreeSons == String.Empty)
{
return false;
}
NewRunLogicActionOrder += "," + action;
NewMercIniTreeSons += "," + action;
Ini.IniFileHelper.WritePrivateProfileString ("RunLogicRunRoot", "RunLogicActionOrder", NewRunLogicActionOrder, path);
Ini.IniFileHelper.WritePrivateProfileString ("RunLogicRunRoot", "MercIniTreeSons", NewMercIniTreeSons, path);
We then need to add our test into the profile actions name, in a second to last position.
//[Profile Actions]
string ProfileActionsName = Ini.IniFileHelper.GetIniFileString (path, "Profile Actions", "Profile Actions name", "");
if (ProfileActionsName == String.Empty)
{
return false;
}
ProfileActionsName = ProfileActionsName.Insert (ProfileActionsName.LastIndexOf (",vuser_end"), "," + action);
Ini.IniFileHelper.WritePrivateProfileString ("Profile Actions", "Profile Actions name", ProfileActionsName, path);
Finally we create a new section in the USP file. I use default values for test because as I said before I don’t support nested actions.
//New section for test
//[RunLogicRunRoot:added]
//MercIniTreeSectionName="added"
//RunLogicObjectKind="Action"
//Name="added"
//RunLogicActionType="VuserRun"
//MercIniTreeFather="RunLogicRunRoot"
Ini.IniFileHelper.WritePrivateProfileString ("RunLogicRunRoot:" + action, "MercIniTreeSectionName", "\"" + action + "\"", path);
Ini.IniFileHelper.WritePrivateProfileString ("RunLogicRunRoot:" + action, "RunLogicObjectKind", "\"Action\"", path);
Ini.IniFileHelper.WritePrivateProfileString ("RunLogicRunRoot:" + action, "Name", "\"" + action + "\"", path);
Ini.IniFileHelper.WritePrivateProfileString ("RunLogicRunRoot:" + action, "RunLogicActionType", "\"VuserRun\"", path);
Ini.IniFileHelper.WritePrivateProfileString ("RunLogicRunRoot:" + action, "MercIniTreeFather", "\"RunLogicRunRoot\"", path);
return true;
}
Create C File
The easy part; validation has been removed as it was tied to the UI that I haven’t provided.
protected void CreateCFile (string path, string content)
{
byte[] info = new UTF8Encoding (true).GetBytes (content);
FileStream cfile = File.Create (path);
if (cfile == null)
{
return ;
}
cfile.Write (info, 0, info.Length);
cfile.Flush ();
cfile.Close ();
}
There you have it; all you need now is a UI.