After my test script found errors with my previous median function, I’ve gone ahead and made some improvements. Again, the goal here isn’t to be thorough but is to demonstrate a boundary-testing technique that is easy to conceive, implement, and build-upon. Like I mentioned in my previous post, this single test script can be the foundation for a testing class. I would also go further by wrapping all of this into a table-driven engine, which would call the function, define its parameters, and identify its expected return value. With VFP9, structured error handling will also help.

I imagine that a revisit of this topic at some point will be in order. Until then, enjoy the much improved get_median function:

*--The following code is an improved get_median
FUNCTION get_median_clean
LPARAMETERS tnType, teName, teColDel
 
    *-- boundary testing
    IF !BETWEEN(tnType,1,3)
        RETURN 0
    ENDIF   
 
    DO CASE
    CASE tnType = 1 .and. VARTYPE(teName)<>"C"
        RETURN 0
    CASE tnType = 1 .and. (!ISDIGIT(teName) .or.;
                VARTYPE(teColDel)<>"C")
        RETURN 0
    CASE INLIST(tnType,2,3) .and. TYPE(teColDel)<>"N"
        RETURN 0
    CASE tnType = 2 .and. (!USED(teName) .or. ;
                 EMPTY(FIELD(teColDel,teName)))
        RETURN 0
    CASE tnType = 2 .and. ;
                 VARTYPE(EVALUATE(FIELD(teColDel,teName))) <> "N"
        RETURN 0
    CASE tnType = 3 .and. ;
                 VARTYPE(EVALUATE(teColDel[1,teColDel])) <> "N"
        RETURN 0
    ENDCASE
 
    *-- local declerations
    LOCAL lcSelCol AS String
    LOCAL lnItems, lnHalf, lnInc AS Integer
    LOCAL lnMedian AS Number
 
    *-- create the aMedian Array
    DO CASE
    CASE tnType = 1        && use a delimted string
        lnItems = ALINES( aMedian , STRTRAN(teName ,teColDel,CHR(13)) )
        FOR lnInc = 1 TO lnItems
            aMedian[lnInc] = VAL(aMedian[lnInc])
        NEXT    CASE tnType = 2        && calculate using an alias
        lcSelCol = FIELD(teColDel,teName)
        lcSQL = "SELECT " + lcSelCol + " FROM " + teName + ;
                " ORDER BY " + lcSelCol + " INTO ARRAY aMedian"
        &lcSQL
        lnItems= _TALLY
    CASE tnType = 3        && calculate using an array
        lnItems= ALEN(&teName,1)
        DIMENSION aMedian[lnItems]
        FOR lnInc = 1 TO lnItems
            aMedian[lnInc] = &teName.[lnInc,teColDel]
        NEXT
    ENDCASE
 
    *-- find the median
    ASORT(aMedian)
    lnHalf = CEILING( lnItems / 2)
    IF MOD(lnHalf,2) = 0
        lnMedian = ( aMedian[lnHalf] + aMedian[lnHalf+1] ) / 2
    ELSE
        lnMedian = aMedian[lnHalf]
    ENDIF
    RETURN lnMedian
 
ENDFUNC