Intro To The VFPUnit Workbench.  Part 3

Packaging
So far, we've accumulated a set of tests that reflect check points in the specs.
These tests lie closer to the problem domain (the analysis) than to the solution domain (the design).

The next phase is to start solving the problem.  The test results have been telling us that InterestCalc.prg does not exist.
We know that the other functions do not exist either.  So, best if we create them.
Immediately, the question arises of Where?.  The answer to that question brings us to the issue
of packaging, aka. the structural design.

I use the term 'packaging' loosely to mean where the implementation code resides.
more specifically, where the functional code resides.
I call it functional, rather than behavioral, even though I may have used the term behavior in the past.
Behavior implies that there is something that 'behaves'.
Packaging will determine what that something is.

I'll show a series of code rearrangements, more for the sake of demonstrating how to perform each in this program,
that to expect you to follow the progression step by step.  A well considered design will make some of the basic
rearrangements trivial.  This is also where Refactoring comes in, but we'll leave that for later.
We really have 2 options of where to put the code.
1) Global, Public, or 'Free' space.
2) inside a class.

Since I haven't done any serious design (I'm making up the problem as I go), I'll start with public functions.
User defined functions are loaded into public space by placing the function definition(s) in a .prg file,
and then issuing a 'Set Proc to' command with that file name.  This program will do that automatically
if there is a Source file entered on the experiment form.
We'll come back to other packaging later, but by just choosing option 1), we can start implementing,
and always come back to re-package the functionality.

Enter a fictitious source file name (I choose the name: c:\tmpexperimentb.prg),
 and press the 'Modify' button next to it.

You should see something like this:
Place the basic function signatures in the source file
********************
FUNCTION InterestCalc

RETURN

********************
FUNCTION BizMonths

RETURN
and press the 'Run' button on the experiment form.
(or the 'apply and run' button on the test code form).


You should see the test results window telling your what is wrong:
Since our goal is to make the failures go away,
add the PARAMETERS keyword, and return the expected data type.
********************
FUNCTION InterestCalc
PARAMETERS luPara

RETURN 0

********************
FUNCTION BizMonths
PARAMETERS luPara

RETURN 0
and run.
Some of the failures have gone away, and a few new ones appeared.


I'm sure you saw this coming, and would have fleshed out the signatures correctly,
But I wanted you to see how the feedback from the results window will guide you through
the process by telling you what is wrong.
At this point, what is wrong is structural, but we will soon get to logical.
Finishing the signatures and running lead to something like this:
There are no more 'FAILURE's, just 'Fail's.  Now we can focus on the logic.
So, I added some code,
********************
FUNCTION InterestCalc
PARAMETERS tnRate, tnAmount, tnMonths

RETURN tnRate * tnAmount * tnMonths

********************
FUNCTION BizMonths
PARAMETERS tdStart, tdEnd
*- begin at the first of the month, and count backward
*- until the first of that month is less than the start date.
ldBegin = CTOD( '1/' +ALLTRIM(STR(MONTH(tdEnd))) +'/' +ALLTRIM(STR(YEAR(tdEnd))) )
lnMonths = 0
DO WHILE .T.
	ldFirstOfMonth = GOMONTH( ldBegin, -1)
	IF tdStart > ldFirstOfMonth
		EXIT
	ELSE
		ldBegin = ldFirstOfMonth
		lnMonths = lnMonths +1
	ENDIF	
ENDDO
RETURN lnMonths
ran it, and got these results.
I'm pleasantly surprised by the month calculation, it ain't pretty, and probably not very efficient.
But that a different problem.  One that hasn't been decided on yet.

However, I am a little confused about the first result.
"interest not rate * amount * duration". after all, that's the expression inside the function.

And, I'm suspect about whether the message "Incorrect Month Calculation" really is indicative of the test.
If it goes away when the interest tests succeed, I may want to change it.

So, it makes sense to have the test return more information in the message in the test of
rate*amount*duration.

In the test code, change:
*TEST( 5.25 = InterestCalc( 5.25, 1000, 12), 'Interest not rate * amount * duration')
TEST( 5.25 = InterestCalc( 5.25, 1000, 12), 'Interest not rate * amount * duration' +KCR +;
              "Expect  5.25, Returned " +STR( InterestCalc( .525, 1000, 12), 10, 2)  )
Note: the KCR is a built in constant.  You can access the constants (and more) by pressing the 'Statements'
button on the experiment form.  Highlight what you want, and drag it onto the code window.

All of the messages could have been created to give better feedback,
I just choose to keep it simple to start, and only augment the messages where needed.
When run,


Doh....
Boy do I feel dumb.
I didn't convert the rate from a percentage to a decimal, and,
duration is based on a year, and the calc uses months.
Glad I caught it now before someone sees it....

Changing the source code to:
********************
FUNCTION InterestCalc
PARAMETERS tnRate, tnAmount, tnMonths

RETURN (tnRate/100) * tnAmount * (tnMonths/12)

********************
FUNCTION BizMonths
PARAMETERS tdStart, tdEnd
*- begin at the first of the month, and count backward
*- until the first of that month is less than the start date.
ldBegin = CTOD( '1/' +ALLTRIM(STR(MONTH(tdEnd))) +'/' +ALLTRIM(STR(YEAR(tdEnd))) )
lnMonths = 0
DO WHILE .T.
	ldFirstOfMonth = GOMONTH( ldBegin, -1)
	IF tdStart > ldFirstOfMonth
		EXIT
	ELSE
		ldBegin = ldFirstOfMonth
		lnMonths = lnMonths +1
	ENDIF	
ENDDO
RETURN lnMonths
and running yields:


What gives?
After revisiting the tests, I come to the conclusion that I missed something in the spec. stage.
Either,
 1) I wrote down the wrong information, and 52.50 is truly the interest, not 5.25 or
 2) I missed something in the calculation.

After discussion with the user, turns out that the interest they care about,
 is related 10% of the rate, not the full rate, like I had assumed.

Adding and refining the code until all the fails disappear (and fixing a few test messages
along the way), leaves me this:
********************
FUNCTION InterestCalc
PARAMETERS tnRate, tnAmount, tnMonths
IF tnRate   > 0 .and. ;
   tnAmount > 0 .and. ;
   tnMonths > 0
	
	RETURN (tnRate/(100 *10) ) * tnAmount * (tnMonths/12)
ELSE
	RETURN 0
ENDIF


********************
FUNCTION BizMonths
PARAMETERS tdStart, tdEnd
*- begin at the first of the month, and count backward
*- until the first of that month is less than the start date.
ldBegin = CTOD( ALLTRIM(STR(MONTH(tdEnd))) +'/' +'1/' +ALLTRIM(STR(YEAR(tdEnd))) )
lnMonths = 0
DO WHILE .T.
	ldFirstOfMonth = GOMONTH( ldBegin, -1)
	IF tdStart > ldFirstOfMonth
		EXIT
	ELSE
		ldBegin = ldFirstOfMonth
		lnMonths = lnMonths +1
	ENDIF	
ENDDO
RETURN lnMonths
You may have noticed multiple errors in the code leading up to this.
I'll let you work through the final steps.
Are we done yet?
Some would say yes, some would say no..,
But as far as this program is concerned, yes, you are done.
You have satisfied all of the tests that you have created.
You have accomplished what you have defined.

Is the code you created useful?  -that's a different question.
And, I bet you have guessed, a question that can be answered by more tests.

But, there are a few minor things you may want to review at this stage. Like:
 Are the function names indicative of what they do?
 Are variables in the functions declared local, or private, to avoid name clash?
 Do the functions restore any changes to the environment that it made?
 Are there any more tests that could be added to exercise the functions?
Next Previous Main

  Generated 03/11/02 01:49:41 PM
Hosted by www.Geocities.ws

1