The Problem with Embeds, Part 3: Testing the TXA Parser

by Unknown user

In Part 2 of this series I extracted my TXA parsing code into a class for easier reuse. 

What I find more interesting than simple reuse is that by moving my code into a class I've made it testable. But what does testing really mean in the Clarion world? Usually it means writing some code in an embed point, running the app, interacting like a user would (theoretically) and watching what happens. That's useful, but it doesn't necessarily tell you what you need to know about the reliability of your core code. And it's tedious.

You can take away some of the tedium with automated UI testing tools, except that historically Clarion hasn't played well with those tools because Clarion apps make use custom Windows controls.

Perhaps UI testing is easier now - I haven't look at this area in years. If you have current information please post a comment below. But even if you can use your apps reliably with UI testing tools, you still won't be testing your app's core functionality in an optimal way.

Testing is a complex subject, and there are lots of different ways to test application code, but certainly one of the most useful is unit testing. The core idea is that you reduce your code to the smallest testable units, and then you run tests on a regular basis. Those tests must be automated; they have to run without user intervention (other than to kick off a series of tests, although you might want a test suite to run automatically on a regular basis, or as part of a build).

Unit testing is commonplace in the .NET world, in part because certain features of the CLR (in particular, reflection) make it fairly simple for testing tools to examine assemblies (DLLs), locate classes and methods marked as test code, run those tests and report on the results.

Automated testing is a bit more awkward in a language like Clarion, but still possible.

Using ClarionTest

A few years ago I wrote an application that became ClarionTest, which is an automated test runner loosely patterned on .NET unit testing tools like nUnit. ClarionTest's job is to load up a specified DLL, search it for test procedures, run those test procedures and report on the results. 

ClarionTest is included in the freely available DevRoadmaps Clarion Library, as is the TXA parser code.

I normally create one test .APP per class. Each app is compiled into a DLL containing one or (usually) more test procedures. 

By convention, since I usually have one test app (DLL) per class, I call the app classname_Tests.app, so in this case the app is DCL_Clarion_TXAParser_Tests. 

I can't emphasize enough how important a good naming convention is for managing Clarion class libraries. All too often I see classes buried in files with cryptic 8.3 filenames. We've had long filenames since Windows '95, people. I also strongly recommend sticking with one class per INC/CLW file pair and using the same name for the class as for the file.

I've included DCL_Clarion_TXAParser_Tests.app in the DevRoadmaps Clarion Library on GitHub. For information on creating your own test apps see Creating a ClarionTest test DLL (DCL).

Testing the parser

In Part 2 I proposed the following test code:

txa       DCL_Clarion_TXAParser
 
  code
  txa.Parse('test.txa')
  AssertThat(txa.GetEmbedCount(),IsEqualTo(6),'Wrong number of embeds found in TXA')

I made two minor changes in my test code, adding a second check for the number of procedures and an error check. Here's how the code looks in the embeditor:

This single test procedure with three AssertThat statements is by no means an exhaustive test of the parser class; much more should be done, including verifying that the embed content is what is expected.

For starters, I could split this one test up into three tests: one to verify that parsing doesn't generate errors, another to get the embed count and a third to get the procedure count. The benefit there would be that if I had multiple failures I'd get multiple error messages; as it is only the first AssertThat failure will be reported. The downside is that I'll need to parse the TXA for each test, but this is a pretty short TXA so that isn't a big problem. 

I have a fairly high level of confidence that my code is working because I compared a log of the parser's embed data with the app I used to create Embeds.txa. But I don't want to have to manually check the embed data each time I make a change to the class library. 

So another way to improve the testing is to get a log of all of the data, save it, and then compare the good log with the test result. 

I added a method called GetEmbedData():

DCL_Clarion_TXAParser.GetEmbedData      procedure(*Queue q,*cstring qField)
x                                           LONG
y                                           long
z                                           long
Count                                       long
str                                         DCL_System_String
	CODE
	free(q)
	if self.ProcedureQ &= null then return.
	loop x = 1 to records(self.ProcedureQ)
		get(self.ProcedureQ,x)
		qField = '** Procedure: ' & self.ProcedureQ.ProcedureName
		add(q)
		Count += records(self.ProcedureQ.EmbedQ)
		loop y = 1 to records(self.ProcedureQ.EmbedQ)
			get(self.ProcedureQ.EmbedQ,y)
			qField = '* Embed: ' & clip(self.ProcedureQ.EmbedQ.embedname)
			add(q)
			qField = '------------------------------------------------------------------------------------------'
			add(q)
			loop z = 1 to records(self.ProcedureQ.EmbedQ.EmbedLinesQ)
				get(self.ProcedureQ.EmbedQ.EmbedLinesQ,z)
				StringUtility.ReplaceSingleQuoteWithTilde(self.ProcedureQ.EmbedQ.EmbedLinesQ.line)
				qField = clip(self.ProcedureQ.EmbedQ.EmbedLinesQ.line)
				add(q)
			END
			qField = '------------------------------------------------------------------------------------------'
			add(q)
		END
	END

And I created a test procedure in my test APP with this code:

txa.Parse('embeds.txa')
txa.GetEmbedData(DataQ,dataq.Text)
gdbg.write('************** begin dump *******************')
loop x = 1 to records(dataq)
	get(dataq,x)
	if clip(dataq.Text) <> ''
		gdbg.write('get(dataq,' & x & ')')
		gdbg.write('StringUtility.ReplaceSingleQuoteWithTilde(dataq.Text)')
		gdbg.write('AssertThat(dataq.Text,IsEqualto(''' & quote(dataq.Text) & '''),''Comparison error on line ' & x & ''')')
	END
END
gdbg.write('************** end dump *******************')

gdbg is a logging object that writes text out to the system log where I can view it with Microsoft's SysInternals DbgView utility. Once I've generated the text into the system log I can comment out the above code. 

The generated text is test code I can paste back into my test procedure. Here's an excerpt:

get(dataq,1)
StringUtility.ReplaceSingleQuoteWithTilde(dataq.Text)
AssertThat(dataq.Text,IsEqualto('** Procedure: Global {34}'),'Comparison error on line 1')
get(dataq,2)
StringUtility.ReplaceSingleQuoteWithTilde(dataq.Text)
AssertThat(dataq.Text,IsEqualto('* Embed: %AfterGlobalIncludes 4000'),'Comparison error on line 2')
get(dataq,3)
StringUtility.ReplaceSingleQuoteWithTilde(dataq.Text)
AssertThat(dataq.Text,IsEqualto('-{90}'),'Comparison error on line 3')
get(dataq,4)
StringUtility.ReplaceSingleQuoteWithTilde(dataq.Text)
AssertThat(dataq.Text,IsEqualto('!INCLUDE(~CharFileIO.Inc~), ONCE'),'Comparison error on line 4')
get(dataq,5)
StringUtility.ReplaceSingleQuoteWithTilde(dataq.Text)
AssertThat(dataq.Text,IsEqualto('-{90}'),'Comparison error on line 5')

If you look closely at both sets of code you'll see that I had to do a bit of string manipulation at each end to account for single quotes in the text. At first I tried the Clarion Quote function but it didn't provide me with reliable text for comparison purposes. 

When I build the app I get the results shown below.

I can verify my embed data test by changing one line of one embed point in the TXA, which results in a failed AssertThat statement:

You know, there is a template that extracts embeds...

Even back when I wrote the original parser I was pretty sure there was a template that would extract embed points, and Steve Parker finally pointed it out to me: Bo Schmitz' free BoTpl utility can extract embed points from applications. Bo's excellent template does this by exporting a TXA and parsing the result. Sound familiar?

As useful as Bo's template is (and I highly recommend you get it and use it) I think it also points out the value of putting business logic into classes rather than into templates. Source code can be easily tested (with ClarionTest!) and even debugged with the rudimentary Clarion debugger; templates are much more difficult to test and there is no true template debugger. And this class provides data in a format ready for consumption by your own code. 

Or my code. And wasn't I going to create a utility to view embedded source? 

The embed viewing utility

The purpose of all of this refactoring and testing was to have a class that I could reuse elsewhere; for instance, in a standalone embed viewer. I'll get to that next time