|
Because easy example is enough here, following use-cases are
the exercise here.
-
Data input
On 2/12/2001, you go shopping, and find that gasoline remains
little. So, you refill at a gas station. It costs \3,412 to
fully refill by cash. The receipt tells unit price is \104
per liter and the amount of gasoline is 31.33 liter. The
consumer tax is \163. The trip meter shows exact 300
km. Then, you use Palm to input this information. Calculated
fuel efficiency is 9.58 km/liter.
-
Data reference
Nowadays fuel efficiency seems to be not good, so you check
recent fuel efficiency in a look. No volatility is observed.
Based on the above description, we need detail editing and list
display windows for the program. I always make a prototype
using text data in order to make it concrete. I think
text-based prototype is very convenient, because it is very
easy to move text back and forth, and add some comments. Sounds
good?
[Fuel display window]
+------+
|Refill| ▼ My Car <- Select car type
+------+----------------------+
| Date: [2/12/01] | <- Select date
| Mileage: [0][0][0][3][0][0] | <- Count-up buttons to add number
| Vendor: ▼xxxx stand______ | <- Save 10 recently visited gas stations
| Grade: ▼Regular | <- Select gasoline grade (e.g. regular, plus) from pre-registered list
| Amount:__31.33 ▼Liter | <- Enable to select liter/gallon (2 unit decimal)
| Cost:___3412 | <- Input total cost
| Note:___________________ |
| ___________________ |
| ___________________ |
| ___________________ |
| ___________________ |
| ___________________ |
| |
|(Done) | <- Move to list display window
+-----------------------------+
[List window]
+---+
|MPL| ▼ My Car <- Select car type
+---+-------------------------+
|Date Vendor ▼MPL | <- Enable to select various data types
|2/12 xxxx sta... 9.58 ↑| (e.g. MPL, MPG, cost, unit price, etc.)
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| ↓|
|(Refill) | <- Move to refill window
+-----------------------------+
[Configuration window]
+-----------------------------+
| Preferences |
+-----------------------------+
|Currency Decimal Places: ▼2 | <- Input currency decimal places
|Default Unit: ▼Liter | <- Select default unit
|Sort by : ▼Date Des. | <- Select sort sequence
| |
|(OK)(Cancel) |
+-----------------------------+
By the way, let us call our program as MPL, meaning Mileage Per
Liter.
|
|
Here, let's prepare for our implementation. There are so many
things to do, but please stay with me. First, we need to make
working directory. Following shows the directory structure
holding PalmUnit header files and libraries.
~/ home directory
|
+-- prog/ directory for programs
|
+-- Palm directory for shared programs
| |
| +-- include directory for header files (CatooCraftLibrary heade files)
| |
| +-- lib directory for libraries (CatooCraftLibrary library file)
|
+-- PalmUnit directory for PalmUnit
| |
| +-- doc
| |
| +-- framework
| |
| +-- src
|
+-- MPL directory for fuel efficiency program
|
+-- Palm directory for Palm (Conduit directory may be created in the future extension)
|
+-- test directory for Unit Test
|
Next, let's install PalmUnit header files and libraries! Please
modify INSTALLDIR macro in
~/prog/PalmUnit/framework/Makefile
as follows. After that, just make it.
## Makefile for PalmUnit Framework
TARGET = PalmUnit.a
SDK = -palmos3.5
INSTALLDIR = ../../Palm # <- relative directory pass of ~/prog/Palm
|
After editing, please install PalmUnit as follows:
hoge [~] $ cd prog/PalmUnit/framework
hoge [~/prog/PalmUnit/framework] $ make
hoge [~/prog/PalmUnit/framework] $ make install
Now, necessary files for Unit Test are saved in
~/prog/Palm/include and
~/prog/Palm/lib. Then, let's move on to
preparation of Unit Test for MPL.
Please copy all files in ~/prog/PalmUnit/src to
~/prog/MPL/Palm/test. Since sample files are
unnecessary, please delete them. Moreover, please delete
AllTests.*, because we will create them again.
hoge [~] $ cd prog/PalmUnit/src
hoge [~/prog/PalmUnit/src] $ cp * ~/prog/MPL/Palm/test
hoge [~/prog/PalmUnit/src] $ cd ~/prog/MPL/Palm/test
hoge [~/prog/MPL/Palm/test] $ rm Sample*
hoge [~/prog/MPL/Palm/test] $ rm AllTests.*
Next, we need to edit Makefile. OBJ and
PALMHOME macro should be edited as follows:
OBJS = \
$(TARGET).o \
PalmTestResult.o \
TestRunner.o \
PalmUnitStopWatch.o \
../CurrencyTest.o \
../AllTests.o
PALMHOME = ../../../Palm
|
Finally, please edit PalmUnit.cc. Please delete such
lines calling sample test files, which were deleted before.
<<Top of file>>
#include "PalmUnit.h"
#include "PalmUnitRsc.h"
//#include "SampleTest.h"
//#include "SampleTest2.h"
#include "../AllTests.h"
//static SampleTest testSample1("Sample Test");
//static SampleTest2 testSample2("Sample Test 2");
<<Around line #110>>
_Runner.addTest("All Tests", new AllTests);
// _Runner.addTest("Sample Test 1", testSample1.suite());
// _Runner.addTest("Sample Test 2", testSample2.suite());
|
We have finished preparation. Good job!
|
|
Since preparation is over, let's make unit test cases. First of
all, Currency class will be tested. This class
parses the cost from strings, and formats the cost into
strings. Please prepare following seven files in
~/prog/MPL/Palm. These files(currency1.tar.gz) can
be downloaded here
- Makefile
- AllTests.cc
- AllTests.h
- Currency.h
- Currency.cc
- CurrencyTest.h
- CurrencyTest.cc
Let's compile it! Ops! Before compilation, please set
POSEAUTOLOADPATH as directory of
POSE. If you want more detail information, please refer to
here.
hoge [~] $ cd prog/MPL/Palm
hoge [~/prog/MPL/Palm] make test
Can you successfully compile it? If the answer is "yes", let's
start POSE. If following window is displayed, you have
successfully finished. Now, tap PalmUnit icon, and start unit
test by selecting AllTests and tapping run button.
Because this test case should cause failure, you will see the
above display.
|
|
Now, let's write-up Unit Test. It was a long distance up to here. :-)
Currency is supposed to be used as follows:
// Transformation from numeric to text
Int32 amount = Currency::Parse("1,980");
Char buff[16];
// Transformation from text to numerics
Char* text = Currency::Format(buff, 2980);
|
This can be specified in Unit Test as follows: Do not forget to
make testParse() and testFormat() in
the place of testFail() in
CurrencyTest.cc. Please do not forget to add
declaration of method in CurrencyTest.h.
#include <TestSuite.h>
#include <TestCaller.h>
#include "CurrencyTest.h"
Test* CurrencyTest::suite() {
TestSuite* suite = new TestSuite("CurrencyTest");
suite->addTest(new TestCaller("parse", &CurrencyTest::testParse));
suite->addTest(new TestCaller("format", &CurrencyTest::testFormat));
return suite;
}
void CurrencyTest::testParse() {
AssertEquals(1980, Currency::Parse("1,980"));
}
void CurrencyTest::testFormat() {
Char buff[16];
AssertEquals("2,980", Currency::Format(buff, 2980));
}
|
Let's compile now! However, you would say compilation could
fail. That's my intention. In XP, we begin with describing test
cases before testing. Then, encountering failures by executing
program is our starting point. Failures let us to remove the
cause. This simplest style is the implementation of XP.
Here, compilation error happens, because Parse()
and Format() are missing in
Currency. Therefore, let's add necessary methods
in Currency.h. We need to solve in simplest way,
so the methods only return 0 or buffer given as
arguments.
#ifndef __CURRENCY_H__
#define __CURRENCY_H__
#include <PalmOS.h>
class Currency
{
public:
static Int32 Parse(const Char *str) {
return 0;
}
static Char *Format(Char *buff, Int32 amount) {
return buff;
}
};
#endif __CURRENCY_H__
|
What happens this time? Compilation should successfully
finish. How about executing POSE? Please reset POSE. If you
compile by executing make test, then new
PalmUnit.prc should be copied in
POSEAUTOLOADPATH. Thus, reset will load program
again.
This time, PalmUnit will produce two failures. Please tap
list. You will see failure results as follows:
Like this, PalmUnit can report the name of the
test(parse), the expected value
(expected:<1980>), the actual value (but
was:<0>), and the place
(CurrencyTest.cc:14) of source.
Cool!? Do you want to try it? Or, not sure?
Anyway, let's move on. We need to modify them in order to
succeed in these testing. Thus, we shall implement
Currency class. As simple as possible...
#include "Currency.h"
Int32 Currency::Parse(const Char *str) {
Int16 l = ::StrLen(str);
Char *p = (Char *)::MemPtrNew(l + 1);
Int16 j = 0;
for (Int16 i = 0; i < l; i++) {
if (str[i] >= '0' && str[i] <= '9') {
p[j] = str[i];
j++;
}
}
p[j] = '\0';
Int32 amount = ::StrAToI(p);
::MemPtrFree(p);
return amount;
}
Char *Currency::Format(Char *buff, Int32 amount) {
::StrIToA(buff, amount);
Int16 i = ::StrLen(buff);
while (i > 3) {
Int16 c = i;
i -= 3;
::MemMove(&buff[i+1], &buff[i], c);
buff[i] = ',';
}
return buff;
}
|
Currency.cc will be implemented like this. (looks
complicated?) The compressed file (currency2.tar.gz) can be
downloaded here. Note that
Makefile in test directory is also changed.
After compiling again, please start POSE. Now, you should be
able to pass all tests. We make certain that the current
program can operate under the test cases of Unit Test. Then,
how about other test cases? Why not add more test cases!
void CurrencyTest::testParse() {
AssertEquals(1980, Currency::Parse("1,980"));
AssertEquals(0, Currency::Parse("0"));
AssertEquals(10, Currency::Parse("10"));
AssertEquals(1980, Currency::Parse("1980"));
AssertEquals(10000000, Currency::Parse("10,000,000"));
}
void CurrencyTest::testFormat() {
Char buff[16];
AssertEquals("2,980", Currency::Format(buff, 2980));
AssertEquals("0", Currency::Format(buff, 0));
AssertEquals("10", Currency::Format(buff, 10));
AssertEquals("10,000,000", Currency::Format(buff, 10000000));
}
|
How about the results of test cases? Last test case of
testFormat should be failed. It seems that
::MemMove() cannot correctly process the number of
bytes moved. Please modify it by yourself. If you can correct
it, please modify source code in order to calculate negative
cost.
Are you keeping up with us? By iterating these "Implement and
test" cycles step by step, you can easily follow "where is the
problem", and "what is wrong."
Correct Currency source code and test cases (currency3.tar.gz)
can be downloaded here.
|
|
Palm can specify numeric format at Formats in
Prefs. Following shows a list of possible formats.
- 1,000.00
- 1.000,00
- 1 000,00
- 1'000.00
- 1'000,00
These formats are defined in the SDK header files as follows:
typedef enum {
nfCommaPeriod,
nfPeriodComma,
nfSpaceComma,
nfApostrophePeriod,
nfApostropheComma
} NumberFormatType;
|
In order to enable MPL to be used all over the world, Currency class should correspond with these all formats. However, I hesitate to change my completed source codes... No problem! Unit Test ensures that no bug will be injected, because source codes will be continuously checked against the existing specifications. Then, let's go on to extend them.
First of all, let's consider test cases. Because
Parse() and Format() are
Currency class's methods, numerical format and
currency decimal places can be set through methods. Since we do
not have to know the current value, only methods to set will be
necessary. Necessary test cases should as follows: Without
doubt, boundary conditions should be covered, but further test
cases may be added in the future, not now. (It depends on you)
#include <TestSuite.h>
#include <TestCaller.h>
#include "CurrencyTest.h"
Test* CurrencyTest::suite() {
TestSuite* suite = new TestSuite("CurrencyTest");
suite->addTest(new TestCaller("parse", &CurrencyTest::testParse));
suite->addTest(new TestCaller("format", &CurrencyTest::testFormat));
suite->addTest(new TestCaller("parse2", &CurrencyTest::testParse_periodComma));
suite->addTest(new TestCaller("format2", &CurrencyTest::testFormat_periodComma));
return suite;
}
// ... snip ...
void CurrencyTest::testParse_periodComma() {
Currency::SetNumberFormatType(nfPeriodComma);
Currency::SetDecimalPlaces(2);
AssertEquals(1980, Currency::Parse("19,80"));
AssertEquals(0, Currency::Parse("0"));
AssertEquals(1000, Currency::Parse("10"));
AssertEquals(198000, Currency::Parse("1980"));
AssertEquals(10000000, Currency::Parse("100.000,00"));
AssertEquals(-1000, Currency::Parse("-10"));
AssertEquals(-1980, Currency::Parse("-19,80"));
AssertEquals(-10000000, Currency::Parse("-100.000,00"));
}
void CurrencyTest::testFormat_periodComma() {
Char buff[16];
Currency::SetNumberFormatType(nfPeriodComma);
Currency::SetDecimalPlaces(2);
AssertEquals("29,80", Currency::Format(buff, 2980));
AssertEquals("0,00", Currency::Format(buff, 0));
AssertEquals("0,10", Currency::Format(buff, 10));
AssertEquals("100.000,00", Currency::Format(buff, 10000000));
AssertEquals("-0,10", Currency::Format(buff, -10));
AssertEquals("-29,80", Currency::Format(buff, -2980));
AssertEquals("-100.000,00", Currency::Format(buff, -10000000));
}
|
Let's compile it! It should fail. Then, modify source codes,
compile again, and test it, and so on. Please modify a little
and test it soon. By doing so, it will be easy for us to
understand when errors happen.
My source codes and test cases (currency4.tar.gz) can be
downloaded here . If it is
helpful, it will be happy. Please let me know further
improvements. Thanks.
|