Sunday, December 27, 2009

Creating A Fortran Unit Testing Framework (part 2)

Today we'll take a quick look at the existing open source frameworks for unit testing Fortran code. I'll briefly mention why none of these exactly fits the needs of our problem. Finally,we'll begin designing the skeleton of our framework.

Existing Possibilities for a Fortran Testing Library

There are a few open source alternatives for testing Fortran available at the time of this writing; the Wikipedia entry for unit testing frameworks lists four:

  1. FRUIT,
  2. FUnit,
  3. Ftnunit, and
  4. pfUnit.

The first two projects (FRUIT and FUnit), rely on the Ruby language. While Ruby is wonderful for many things, using a framework based on it requires that the language be installed and that the developer has at least basic familiarity how it works (installing Ruby Gems, etc.).

Ftnunit is closest in spirit to what we want. It is part of flibs. Unfortunately it is written in Fortran 90, which makes it less than ideal for our goal of testing old Fortran as we modernize it. Mixing old and modern code gives many compilers conniptions. Some features found in FORTRAN IV or FORTRAN 66 have been removed from the language. I know little of pfUnit. It appears to be under active development (the latest upload was 89 days ago from 12/27/2009). However, there are no files available for download thus far.


Beginning the Framework

I’ve decided to begin with Fortran 66. It was the first version to be standard (ANSI), contains most of the oldest and creakiest features. It is also weird in ways that make it a cool technical challenge from a coding perspective.


xUnit Frameworks

The vast majority of testing frameworks are based on a Java framework called JUnit[www.junit.org], which was in turn developed based on and older framework written in the SmallTalk language called SUnit. JUnit has been re-written (“ported”) to a huge variety of languages. Wikipedia contains one list; another can be found on Ron Jeffries site in the table titled “Unit Tests”. Collectively these implementations are termed “xUnit”, because of the tendency to prefix “unit” with some indication of the language that it is written in.

At its root, xUnit is built on the concept for an assertion; we assert that some property of the code is correct. The xUnit framework then evaluates that assertion. If it is true, the test passes. If it is false, the framework signals that it fails [1]. The traditional way of signaling that a test passed is to print a period (“.”); the way of signaling a failure is to print an “F” to the screen. Thus, a collection of 10 tests might display:

….F…..

In this case, the fifth test failed.

Unit tests are meant to evaluate very small parts of the code. Thus a unit test for the ABS intrinsic function might be

assert (1, ABS(-1))

The first number is the expected value, and the second number is what we are testing.

In object-oriented languages, each test typically evaluates a single function/subroutine (also termed “methods”). These assertions are wrapped in a function. Each function can contain one or more assertions (whether a function should contain more than one assertion is an issue that sparks near-religious debate). Each test function is called, and produces a single “.” or “F”. Taking into consideration the nature of older FORTRAN, we will write out a “.” or “F” based on the result of each assertion.


Assertions

The names of the assertions will follow the xUnit conventions as closely as possible. However, FORTRAN 66 only allows 6 characters per name, which limits our ability to choose expressive names. Furthermore, many programming languages are case sensitive, which allows us to be more expressive. We can combine multiple words, with the convention that a capital letter indicates a new word (called “camel case” because the capital letter in the middle pokes up like a camel’s hump). So, it we want to create a function that asserts that its logical argument is true, we would call it assertTrue(arg). Older FORTRAN is not case-sensitive, and is typically written in all capital letters, which limits us even further. We will adopt the convention that the root name for “assert” will be abbreviated “ASRT”. That leaves us two letters to describe the assertion. The first letter traditionally describes the kind of argument a function takes:

  • I - INTEGER
  • A - REAL
  • D - DOUBLE PRECISION
  • C - COMPLEX
  • L - LOGICAL

Our first two assertions will simply determine whether their argument is true or false. The final letter will convey our expectation, “T” for .TRUE., “F” for .FALSE.

  • ASRTT(arg) asserts that the LOGICAL arg is .TRUE.
  • ASRTF(arg) asserts that the LOGICAL arg is .FALSE.

The next set of assertions will compare an expected value with the actual value to see whether they two are equal. The final letter of these assertions will be “E”, to convey we are testing for equality. The first letter will reflect the type of the argument. So:

  • LASRTE(EXPECT , ACTUAL) asserts that the LOGIAL value ACTUAL is equal to the value that we EXPECT.
  • IASRTE(EXPECT , ACTUAL) asserts that the INTEGER value ACTUAL is equal to the EXPECT value.
  • AASRTE(EXPECT, ACTUAL, EPSILN) asserts that the REAL value ACTUAL is equal to the EXPECT value. Due to the inherent imprecision of floating point numbers, we do not actually compare the two values for equality. Instead we compute whether ABS(EXPECT – REAL) .LE. EPSILN, where EPSILN is a small positive number, such as 1.E-4 or 1.E-6.
  • DASRTE(EXPECT, ACTUAL, EPSILN) asserts that the DOUBLE PRECISION value ACTUAL is within EPSILN (DOUBLE PRECISION) of the EXPECTED value (see the description of AASRTE).
  • CASRTE(EXPECT, ACTUAL, EPSILN) asserts that the COMPLEX value ACTUAL is within EPSILN (COMPLEX) of the EXPECT value. Determining “within EPSILN” for COMPLEX numbers is a bit tricky. We let the user choose, comparing
    ABS(REAL(EXPECT) – REAL(ACTUAL)) .LE. REAL(EPSILN) and
    ABS(IMAG(EXPECT) – IMAG(ACTUAL)) .LE. IMAG(EPSILN)

  • If both are true, the assertion passes; if either is false, it fails.
In the next installment, we’ll look at implementing each of these assertions. I’ll also introduce test-driven development, a style of programming that leads to very robust code with a minimum of errors.



[1] Previous versions of JUnit (e.g., 3.8) contained a third outcome "E" for error/exception. This signaled a problem with the code, often a compilation problem or unexpected runtime error. The most recent version (4) of JUnit removed "E"; only "." and "F" are outcomes of tests.

No comments:

Post a Comment