This article is the fourth sixth 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.
...
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:
...
and, Part 3 I examined some of my own code for parsing TXAs to extract embed code and showed how to make that code into testable, reusable classes. In Part 4 I reused my classes to create an embed viewer utility app.
In Part 5 I reintroduced the Invoice.app that ships with the Clarion examples and I demonstrated the buggy nature of the invoice line item calculation code.
In this instalment I'm going to rework that buggy code into a testable, reusable class. And in the next instalment I'll plug that code back into the original Invoice.app.
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 5, 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
...
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 discountdiscounted, weI'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 that's beyond the scope of this article).
...
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 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:
...
Code Block |
---|
detail InvoiceDetail
|
...
code |
...
|
...
|
...
|
...
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. For the moment I'm leaving out how I report that error.
Note that at this point my code will not compile. That's critical, as it and 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 wasncouldn't aware of find 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 CTestClarionTest) that will search a DLL looking for unit tests to run, and a procedure template set of supporting templates you use to create test procedures , in your a test DLL , that are is compatible with CTestClarionTest.
Figure 1 shows the CTest ClarionTest application with a test DLL loaded and several tests having been run (this screen shot is from Part 2). As you can see, CTest ClarionTest is just a single window application that lists the available tests, lets you run those tests, and shows you the results.
...
Figure 1. The
...
ClarionTest 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 As I mentioned earlier in this series, ClarionTest is included in the freely available DevRoadmaps Clarion Library (DCL).
To recap, here are the stages I've completed:
...
- 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 ClarionTest 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.
Download the source/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 the form of fewer bugs, more confidence and better reusability.