Title: Windows Live Agents Testing: Building the Framework
Slug: windows-live-agents-testing-building-the-framework
Date: 2008-05-24 12:47:00
Author: Kartones
Lang: en
Tags: Testing, Development, Windows Live Agents
Description: A framework for testing Windows Live Agents.

 <p><b style="line-height: 1.428571429;">Update</b><span style="line-height: 1.428571429;">: Part </span><a href="https://blog.kartones.net/post/windows-live-agents-testing-improving-the-framework" style="line-height: 1.428571429;">III</a><span style="line-height: 1.428571429;"> of these posts available.  </span></p><p>In <a href="https://blog.kartones.net/post/windows-live-agents-testing-introduction">the previous WLA testing post</a> I gave an introduction to what the platform provides for unit testing.</p> <p>We also mentioned that it was lacking some features and that I rebuilt the testing framework.</p> <p>So, I'll just list what the current (rebuilt) framework supports and then start showing code of how to make your own.</p> <p>Here is the list of procedures available for testing:</p> <p><font face="Courier New">procedure TestExInitializeBattery (TEST_NAME, TEST_SUMMARY)<br>procedure TestExGetBatteryResults ()<br><font color="#ff0000">procedure TestExErrorIfDoesNotContainArray (STRING, REGEXARRAY)</font><br>procedure TestExCompareString (EXPECTEDSTRING, ACTUALSTRING)<br>procedure TestExCompareStringNotEqual (EXPECTEDSTRING, ACTUALSTRING)<br>procedure TestExCompareObjects (EXPECTEDOBJECT, ACTUALOBJECT)<br>procedure TestExCompareObjectsNotEqual (EXPECTEDOBJECT, ACTUALOBJECT)<br>procedure TestExCompareQuery (REGEX, QUERY)<br>procedure TestExCompareQueryNotEqual (REGEX, QUERY)<br>procedure TestExCompareQueryArray (REGEXARRAY, QUERY)<br>procedure TestExCompareQueryArrayNotEqual (REGEXARRAY, QUERY)<br>procedure TestExMatch (DOMAIN, QUERY)<br>procedure TestExMatchNotEqual (DOMAIN, QUERY)</font>  </p><p>The first two procedures define a new test battery and stops it (and shows the results in the conversation window). a simple template for defining a test battery could be this:</p> <p><font face="Courier New">+ test_name_procedures<br>call TestExInitializeBattery("TESTNAME", "TESTSUMMARY")<br><font color="#008000">// TODO: Fill with tests</font><br>call TestExGetBatteryResults()</font>  </p><p>So we could just launch it by typing to the agent "test_name_procedures".</p> <p>The procedure highlighted in red should be ignored, as it is used internally most times.</p> <p>We have procedures to assert equal or not equal strings, objects, queries (conversational patterns and their responses), queries with response arrays (<b>very very useful</b> for multi-response or random-response patterns!), and finally for testing the matching domain (under which language domain the query/pattern it is executed).</p> <p>Like in NUnit, everything follows the <i>(Expected,Actual)</i> pattern for parameters.</p> <p>To develop a basic framework, we will need three variables to hold the current test battery data:</p> <p><font face="Courier New">variable TESTNAME = ""<br>variable TESTSUMMARY = ""<br>variable TESTRESULTS = ""</font>  </p><p>Next, we write the initialize battery procedure:</p> <p><font face="Courier New">procedure TestExInitializeBattery(TEST_NAME, TEST_SUMMARY<font color="#ff0000">, WRITE_TO_XML</font>)<br>  TESTNAME = TEST_NAME<br>  TESTSUMMARY = TEST_SUMMARY<br>  - -----------------------<br>  if (TESTSUMMARY == "")<br>    - Begin testing: TEST_NAME<br>  else<br>    - TESTSUMMARY<br><font color="#ff0000">  TESTRESULTS.xml    = (WRITE_TO_XML eq true)<br></font>  TESTRESULTS.Summary  = TESTSUMMARY<br>  TESTRESULTS.TestName = TESTNAME<br>  TESTRESULTS.Ellipsis = 500<br>  TESTRESULTS.NbTests  = 0<br>  TESTRESULTS.NbErr    = 0</font>  </p><p>The fragments in red could be deleted as they are for a future XML output feature unused right now. Nothing complex here.</p> <p>Here it is the end battery procedure:</p> <p><font face="Courier New">procedure TestExGetBatteryResults()<br>  PASSEDTESTS = TESTRESULTS.NbTests - TESTRESULTS.NbErr<br><font color="#ff0000">  if (TESTRESULTS.xml)<br>    - &lt;unfiltered&gt;&lt;tests&gt;<br>        &lt;name&gt;TESTRESULTS.TestName&lt;/name&gt;<br>        &lt;count&gt;TESTRESULTS.NbTests&lt;/count&gt;<br>        &lt;passed&gt;PASSEDTESTS&lt;/passed&gt;<br>        &lt;failed&gt;TESTRESULTS.NbErr&lt;/failed&gt;<br>      &lt;/tests&gt;&lt;/unfiltered&gt;<br>  else</font><br>    - Done testing TESTRESULTS.TestName<br>    nop<br>    - Total tests: TESTRESULTS.NbTests   Passed tests: PASSEDTESTS   Failed tests: TESTRESULTS.NbErr<br>    if (TESTRESULTS.NbErr)<br>      - Test battery failed<br>    else<br>      - Test battery passed<br>    - -----------------------</font>  </p><p>Nothing complex here neither, just writing the results.</p> <p>We will have a simple counter for keeping track of the tests:<br><br>  <font face="Courier New">TESTRESULTS.NbTests = TESTRESULTS.NbTests + 1 </font></p> <p>A HTML escape function:</p> <p><font face="Courier New">function EscapeHtmlChars(HTML_STRING)<br>  ESCAPED = StringSubstitute(HTML_STRING, "&lt;", "&lt;")<br>  ESCAPED = StringSubstitute(ESCAPED,     "&amp;", "&amp;amp;")<br>  return ESCAPED</font>  </p><p>A base procedure to mark a test as failed:</p> <p><font face="Courier New">procedure TestExError(TESTMETHOD, ERRORDESCRIPTION, RETURNEDOUTPUT)<br>  TESTRESULTS.NbErr = TESTRESULTS.NbErr + 1<br>  - Test: \"TESTMETHOD\" failed.<br>  nop<br>  -  ERRORDESCRIPTION<br>  nop<br>  -  Returned: \"EscapeHtmlChars(ellipsis(RETURNEDOUTPUT, TESTRESULTS.Ellipsis))\"</font>  </p><p>And two more elaborate failure-checking procedures:</p> <p><font face="Courier New">procedure TestExErrorIfContains(TESTMETHOD, STRING, REGEX)<br>  if (Contains(REGEX, STRING))<br>    ERROR_DESC - Expected not to return: \"EscapeHtmlChars(REGEX)\"<br>    call TestExError(TESTMETHOD, STRING, ERROR_DESC) </font> </p><p><font face="Courier New">procedure TestExErrorIfDoesNotContain(TESTMETHOD, STRING, REGEX)<br>  STRING_NO_NEWLINES = StringSubstitute(STRING, "\n", "X")<br>  if (!Contains(REGEX, STRING)) &amp;&amp; ((STRING_NO_NEWLINES eq STRING) || (!Contains(REGEX, STRING_NO_NEWLINES)))<br>    ERROR_DESC - Expected: \"EscapeHtmlChars(REGEX)\"<br>    call TestExError(TESTMETHOD, ERROR_DESC, STRING)</font>  </p><p>Quite simple too, they just stores the calling method name (TESTMETHOD) and search for a given string (STRING) inside another variable (REGEX), calling <font face="Courier New">TestExError()</font> if the condition is met.</p> <p> </p> <p>Having this, we can actually build some basic string-check tests:</p> <p><font face="Courier New">procedure TestExCompareString (EXPECTEDSTRING, ACTUALSTRING)<br>  - Test: \"TestExCompareString\"<br>  call IncrementTotalTests()<br>  if (StringLowercase(EXPECTEDSTRING) != StringLowercase(ACTUALSTRING))<br>    ERROR_DESC - Expected string: "EXPECTEDSTRING". Actual string: "ACTUALSTRING"<br>    call TestExError("TestExCompareString", ERROR_DESC, ACTUALSTRING) </font> </p><p><font face="Courier New">procedure TestExCompareStringNotEqual (EXPECTEDSTRING, ACTUALSTRING)<br>  - Test: \"TestExCompareStringNotEqual\"<br>  call IncrementTotalTests()<br>  if (StringLowercase(EXPECTEDSTRING) == StringLowercase(ACTUALSTRING))<br>    ERROR_DESC - Expected not equal string "ACTUALSTRING".<br>    call TestExError("TestExCompareStringNotEqual", ERROR_DESC, ACTUALSTRING)</font>  </p><p>We output the test name, increment the test counter, check whenever the string is contained or not, and setup the error description and call <font face="Courier New">TestExError()</font> if the condition is not met. We could have used <font face="Courier New">TestExErrorIfContains()</font> and <font face="Courier New">TestExErrorIfDoesNotContain()</font>, but notice that we're not searching for a partial match (contains, "AAB" contains "AB") but for a full match ("AAB" is not equal to"AB").</p> <p> </p> <p>Instead of a dummy sample, I'll create another test assertion, comparing objects, which can look hard but actually is easy to do using a small trick:</p> <p><font face="Courier New">procedure TestExCompareObjects(EXPECTEDOBJECT, ACTUALOBJECT)<br>  - Test: \"TestExCompareObjects\"<br>  call IncrementTotalTests()<br>  if (!IsObject(EXPECTEDOBJECT))<br>    ERROR_DESC - "EXPECTEDOBJECT" not an object<br>    call TestExError("TestExCompareObjects", ERROR_DESC, EXPECTEDOBJECT)<br>    exit<br>  S_EXPECTEDOBJECT = <font color="#008000">ObjectToString(EXPECTEDOBJECT)<br></font>  if (!IsObject(ACTUALOBJECT))<br>    ERROR_DESC - "ACTUALOBJECT" not an object<br>    call TestExError("TestExCompareObjects", ERROR_DESC, ACTUALOBJECT)<br>    exit<br>  S_ACTUALOBJECT = ObjectToString(ACTUALOBJECT)<br>  if (S_EXPECTEDOBJECT != S_ACTUALOBJECT)<br>    ERROR_DESC - Expected equal objects. Actual: "S_ACTUALOBJECT"<br>    call TestExError("TestExCompareObjects", ERROR_DESC, S_EXPECTEDOBJECT) <br><br>procedure TestExCompareObjectsNotEqual(EXPECTEDOBJECT, ACTUALOBJECT)<br>  - Test: \"TestExCompareObjectsNotEqual\"<br>  call IncrementTotalTests()<br>  if !IsObject(EXPECTEDOBJECT)<br>    ERROR_DESC - "EXPECTEDOBJECT" not an object<br>    call TestExError("TestExCompareObjectsNotEqual", ERROR_DESC, EXPECTEDOBJECT)<br>    exit<br>  S_EXPECTEDOBJECT = ObjectToString(EXPECTEDOBJECT)<br>  if !IsObject(ACTUALOBJECT) <br>    ERROR_DESC - "ACTUALOBJECT" not an object<br>    call TestExError("TestExCompareObjectsNotEqual", ERROR_DESC, ACTUALOBJECT)<br>    exit<br>  S_ACTUALOBJECT = ObjectToString(ACTUALOBJECT)<br>  if (S_EXPECTEDOBJECT == S_ACTUALOBJECT)<br>    ERROR_DESC - Expected not equal objects. Actual: "S_ACTUALOBJECT"<br>    call TestExError("TestExCompareObjectsNotEqual", ERROR_DESC, S_EXPECTEDOBJECT)</font>  </p><p>I've painted in green the first appearance of the trick. If we "serialize" the object as a string, we can then check if two objects are equal (they might not be the same, though, just have the same properties and values ;)<br>The restriction/limitation here is that you have to be very strict and have the properties always in the same order, or the serialization (which is secuential) will create "different objects" (same properties in different order, that will mean strings not equal).</p> <p> </p> <p>The post has a lot of code, so I'll stop here and leave for further posts adding more functionality to our testing framework. </p> <p>Here it is a sample of testing the framework current assert procedures:</p> <p><font face="Courier New">package TestUtilitiesAddon.pkg</font>  </p><p><font face="Courier New">+ test_testexcomparestring_procedures<br>  call TestExInitializeBattery("TestExCompareString() Test Battery", "")<br>  STRING1 = "string 1"<br>  STRING2 = "string 2"<br>  STRING1_1 = "string 1"<br>  call TestExCompareString(STRING1, STRING1)<br>  call TestExCompareString(STRING1, STRING1_1)<br>  call TestExCompareStringNotEqual(STRING1, STRING2)<br>  call TestExGetBatteryResults()</font>  </p><p><font face="Courier New">+ test_testexcompareobjects_procedures<br>  call TestExInitializeBattery("TestExCompareObjects() Test Battery", "") <br>  NONOBJ1 = "not object #1"<br>  NONOBJ2 = "not object #2"<br>  OBJ1 = ""<br>  OBJ1.ID = 1<br>  OBJ1.TEXT = "sample object 1"<br>  OBJ1COPY = ""<br>  OBJ1COPY.ID = 1<br>  OBJ1COPY.TEXT = "sample object 1"<br>  OBJ2 = ""<br>  OBJ2.ID = 2<br>  OBJ2COPY.TEXT = "sample object 1"<br>  OBJ3 = ""<br>  OBJ3.ID = 1<br>  OBJ3COPY.TEXT = "sample object 3"<br></font><font face="Courier New"><font color="#008000">  // This 6 commented tests were written to test detecting non-objects<br>  //call TestExCompareObjects(NONOBJ1, NONOBJ2)<br>  //call TestExCompareObjects(OBJ1, NONOBJ2)<br>  //call TestExCompareObjects(NONOBJ1, OBJ1COPY)<br>  //call TestExCompareObjectsNotEqual(NONOBJ1, NONOBJ2)<br>  //call TestExCompareObjectsNotEqual(OBJ1, NONOBJ2)<br>  //call TestExCompareObjectsNotEqual(NONOBJ1, OBJ1COPY)</font><br>  call TestExCompareObjects(OBJ1, OBJ1)<br>  call TestExCompareObjects(OBJ1, OBJ1COPY)<br>  call TestExCompareObjectsNotEqual(OBJ1, OBJ2)<br>  call TestExCompareObjectsNotEqual(OBJ1, OBJ3)<br>  call TestExGetBatteryResults()</font></p>
