This article is the fourth in a series on what Clarion developers (yours truly included) do wrong with embeds, and how to do embeds right. In a nutshell, Clarion's application architecture encourages an all-in-one approach, where the user interface, the business logic, and the data access code are all lumped together.
This is a problem.
Actually it's not a huge problem for the generated code, inasmuch as that code tends to actually work as expected. It's tried and true, and if the architecture leaves something to be desired, well, so what?
It's when we start throwing our own code into embed points that things tend to get really ugly.
In Part 1 of this series I examined the architecture of Clarion applications; in Part 2 I talked in general terms about extracting code from embed points and making it testable. In Part 3 I tackled the Invoice example application, the one you can find in your Clarion examples directory (I used the C6 version).
In this article I'll take some of the Invoice app's buggy embed code, I'll show how to recreate it as a class, I'll run a series of unit tests on that class, and I'll plug the new code back into the embed point. Most importantly, the embed point will no longer contain any business logic; it'll just have calls to the class, and the class will contain the business logic.
Note: I'm scheduled to do a ClarionLive Webinar on Friday, December 18, 2009 covering many of the points discussed in this article. Please join me, and if you miss the webinar you can always download the recording.
The Invoice app
The specific code I'm interested in is in the UpdateDetail procedure in the Invoice app. It's a routine that's used to calculate various values for each line item that's added to an invoice. As I'm sure you'll agree, this is critical business logic. And as I showed in Part 3, it's buggy code.
Let's take a look at what this embed code is actually trying to accomplish. Here again is the code:
CalcValues ROUTINE IF DTL:TaxRate = 0 THEN IF DTL:DiscountRate = 0 THEN DTL:TotalCost = DTL:Price * DTL:QuantityOrdered ELSE LOC:RegTotalPrice = DTL:Price * DTL:QuantityOrdered DTL:Discount = LOC:RegTotalPrice * DTL:DiscountRate / 100 DTL:TotalCost = LOC:RegTotalPrice - DTL:Discount DTL:Savings = LOC:RegTotalPrice - DTL:TotalCost END ELSE IF DTL:DiscountRate = 0 THEN LOC:RegTotalPrice = DTL:Price * DTL:QuantityOrdered DTL:TaxPaid = LOC:RegTotalPrice * DTL:TaxRate / 100 DTL:TotalCost = LOC:RegTotalPrice + DTL:TaxPaid ELSE LOC:RegTotalPrice = DTL:Price * DTL:QuantityOrdered DTL:Discount = LOC:RegTotalPrice * DTL:DiscountRate / 100 LOC:DiscTotalPrice = LOC:RegTotalPrice - DTL:Discount DTL:TaxPaid = LOC:DiscTotalPrice * DTL:TaxRate / 100 DTL:TotalCost = LOC:DiscTotalPrice + DTL:TaxPaid DTL:Savings = LOC:RegTotalPrice - DTL:TotalCost END END
First, there are a number of different states, which can be expressed as a matrix:
Not Discounted | Discounted | |
Not Taxed | price * qty | price * qty - discount |
Taxed | price * qty + tax | price * qty - discount + tax |
Any item can have a discount, and it can have a tax, and it can have both a discount and a tax. If a taxed item is discount, we'll assume that the tax is applied after the discount (although in real life this isn't always the case, as with manufacturer's rebates in which the tax may apply to the non-discounted total - but beyond the scope of this article).
One of the problems with the embedded code is terminology. It isn't especially clear on the concept of an extended price, which is the unit price (price for one item) times the quantity. Instead, there's a local variable called LOC:RegTotalPrice
, which is calculated in three of the four matrix elements. For the simplest case (no taxes, no discount) it isn't used at all; instead the calculation just sets the total price to the price times the quantity.
There are four values that care calculated:
- Total Cost - the actual amount paid by the customer
- Discount - the dollar value of the discount
- Tax Paid - the dollar value of the tax
- Savings - this one is a little unclear, but it appears to be the extended price minus the total cost (yielding a negative number, with a lower number meaning greater savings). That doesn't make a whole lot of sense to me when taxes are involved, since the higher the tax the greater the perceived savings! I can sooner see the savings as including any tax savings realized by the application of the discount (and so being the same as the discount amount if the tax rate is zero), but as written the code isn't going to produce that value.
So to start with let's use a formal definition of an extended price:
extended = price * qty
Now, how about discount amount? That's simply the extended price times the discount rate:
discount = extended * discountrate
Similarly, the tax amount (assuming no discount) is the extended price times the tax rate:
tax = extended * taxrate
And if there is a tax and a discount, the tax is the discounted amount times the tax rate:
tax = (extended - discount) * taxrate
And the grand total is simply the extended amount, less any discount, plus the tax:
total = extended - discount + tax
Here's the matrix with the updated pseudocode:
Not Discounted | Discounted | |
Not Taxed | extended = price * qty total = extended | extended = price * qty discount = extended * discountrate total = extended - discount |
Taxed | extended = price * qty | extended = price * qty discount = extended * discountrate tax = (extended - discount) * taxrate total = extended - discount + tax |
So far, so good. But how do you translate this logic into a class that can be tested and reused?
Class design and test driven development
Back when I first started writing object-oriented code, I often found the most difficult part of the job was imagining what the class would look like. Which methods should the class have? What properties? Would I need one class, or multiple classes?
And at some point I realized that it was a whole lot easier if I approached class design, not from the perspective of what I wanted to include in the class (the methods, properties), but how I wanted to use that class. So I'd imagine some code that called the class. And that often made the class design task much, much easier.
And then I came across Test Driven Development, or TDD. And it all looked very familiar to me, because in a way that's what I'd been trying to with my "how will I use this class" visualizations.
TDD isn't something you hear about a lot on the Win32 platform; I came across it while doing .NET development (Clarion# and C#). The basic idea is you begin writing code by writing the tests first. For instance, I might have a test that looks something like this:
! In the data section detail InvoiceDetail ! In the code sectin detail.Init(12.50,3) ! 3 items at $12.50 each extended = detail.GetExtended() if extended <> 37.50 ! Report an error end
Here I have an instance of an InvoiceDetail
class that models one invoice line item, and I'm initializing it with a price (12.50) and a quantity (3). I'm then calling a GetExtended()
method on the class to get the extended price, and if that price isn't what I think it should be I'm going to report an error.
Note that at this point my code will not compile. That's critical, as it is due to test-driven aspect of TDD. I'm using the test as my starting point. I haven't written the class yet. So the next step will be to write a stub class so my test compiles, but of course the test will fail because I won't have any logic in the class yet. It's only once I have a failing test that I actually start writing the class code.
Note: You don't have to use a test-driven approach to get the benefit of unit testing. You can always write your tests after you've written the code. But I think you'll find that the more you use unit tests, the more you use the tests as a starting point.
But even before I start writing any code I have to back up a bit further, because the one thing still missing from this picture is a unit testing framework.
A unit testing framework
A unit testing framework is a software tool that makes it easy to unit test your code. But what's a unit test?
Wikipedia defines unit testing as
a software verification and validation method in which a programmer tests if individual units of source code are fit for use. A unit is the smallest testable part of an application.
In practice, unit testing often involves fairly complex processes, but each individual test should only test one thing. And if you start with the smallest, simplest testable units and work your way up, you can achieve a high degree of confidence that your code is working as expected. In the above example I'm testing the ability of the class to return a correct extended price; I'll go on to write tests that verify the class's ability to calculate taxes, discounts and totals.
The entry goes on to explain that unit testing is commonly automated and run in the context of a framework:
Under the automated approach, to fully realize the effect of isolation, the unit or code body subjected to the unit test is executed within a framework outside of its natural environment, that is, outside of the product or calling context for which it was originally created. Testing in an isolated manner has the benefit of revealing unnecessary dependencies between the code being tested and other units or data spaces in the product. These dependencies can then be eliminated.
Using an automation framework, the developer codes criteria into the test to verify the correctness of the unit. During execution of the test cases, the framework logs those that fail any criterion. Many frameworks will also automatically flag and report in a summary these failed test cases. Depending upon the severity of a failure, the framework may halt subsequent testing.
As a consequence, unit testing is traditionally a motivator for programmers to create decoupled and cohesive code bodies. This practice promotes healthy habits in software development. Design patterns, unit testing, and refactoring often work together so that the best solution may emerge.
Unit testing under .NET is a treat - there are some terrific tools out there that make it easy to create, run, and log tests. You can run tests manually or have them run automatically as part of your build process (typically via a continuous integration server, if you take that approach).
But .NET unit testing tools benefit from some .NET language features, particularly reflection. A .NET unit test framework can inspect an assembly, locate test methods inside test classess, run those test methods and report on the results. That's a lot more difficult to accomplish in Win32.
Unit testing in Clarion Win32
I wasn't aware of any third party unit testing tools for Clarion, so I went ahead and wrote one. Actually this tool is in two parts: an executable application (called CTest) that will search a DLL looking for unit tests to run, and a procedure template you use to create test procedures, in your test DLL, that are compatible with CTest.
Figure 1 shows the CTest application with a test DLL loaded and several tests having been run (this screen shot is from Part 2). As you can see, CTest is just a single window application that lists the available tests, lets you run those tests, and shows you the results.
Figure 1. The CTest test runner
The Test_
prefix is necessary for CTest to identify the procedure as a test procedure (this way you can mix test and non-test procedures in the DLL).
I'll get into the specifics of how to use CTest and the test procedure template shortly (and I'll have an article on CTest later this month).
To recap, here are the stages I've completed:
- I've analyzed the embed code enough to figure out more or less what it's supposed to do
- I've come up with a rough idea for my first unit test
Now I'm about to:
- Create a test DLL
- Write my first unit test procedure
- Create a skeleton class so the unit test will compile
- Run the unit test using the CTest framework
- Fix the class so it returns the expected result
I realize that this seems like a lot of work for just one wee bit of embedded code. But a lot of this work is also a one-time effort which you can leverage when you write additional tests for other code/classes. Once you understand the basic concepts of unit testing, creating new tests will come very easily.
Having said that, even after you become proficient in creating unit tests you almost certainly will spend more time up front creating your code than you did writing embed code. It takes time to write good tests. But there's also a huge payback, in fewer bugs, more confidence and better reusability.
Creating a test DLL
I'm using Clarion 7 in the following explanation, mainly because it lets me create a single solution containing both the original application as well as my test DLL.
First, copy the ClarionTest.TPL file from the downloadable source into your Clarion template directory and register it. This template contains the test procedure template as well as a global extension you'll need for the test application.
The downloadable source zip contains all the projects you need (including the CTest project). Or if you're following along you can work directly with the Clarion Invoice application. I'm using the C6 version which I've converted to C7.
If you're using your own solution, right-click on the solution in the Solution Explorer and choose Add | New Application or Project. Choose a type Application (not DLL) from the dialog (Figure 2).
Figure 2. Adding a test DLL to the solution
In the Application Properties dialog set the Destination Type to DLL (Figure 3).
Figure 3. Creating the test DLL
You don't need the Application Wizard since you'll be creating all your procedures manually.
You do however need to add a global extension to the DLL to enable the unit test support code. On the Global Extensions tab click on Insert, and choose Class Clarion Test | TestSupportIncludes (Figure 4).
Figure 4. Adding the TestSupportIncludes global extension
You're now ready to create your first test procedure, and I'll cover that topic next time.