Montag, 24. Oktober 2011

Differenzenermittlung - Problem ist gelöst

Das Problem mit den Differenzen ist nun gelöst. Das folgende Template kann eine beliebige einzelne, benannte Abweichung festhalten und formatiert ausgeben.

Aus Clientsicht vereinfacht sich der Aufruf erheblich: Hier ein Beispiel, der Aufruf einer Planetenberechnung via swe_calc(). Beim Aufruf gibt man die Argumente sowie die erwarteten Rückgabewerte an. Wenn die Methode calcsAsExpected feststellt, dass einige Werte abweichen, gibt sie false zurück. Man kann dann das Member diffs verwenden, um alle festgestellten Differenzen auszugeben.
class TestSwissEphemeris : public Test {
public :
virtual void run() {
SweCalc se;
if (!se.calcsAsExpected(
2451545.0,
0, // <-- SE_SUN
0, // <-- Flags
(double[6]) { 280.368, 0.000227827, 0.983328, 0.,0.,0. })
) {
fail( "Differences for Sun", se.diffs );
}
}
};


Dieser Teil des Programms wird automatisch generiert werden, wobei dann die Erwartungswerte mit hineinkommen. Auch der Text (hier Differences for Sun kann vom Automaten so produziert werden, dass man nachher bei Betrachtung des Logs den Fall nachvollziehen kann. Es könnte z.B. eine swetest-Kommando sein, mit dem man den Unterschied reproduzieren kann.

Hier die oben verwendete Klasse, die einen Aufruf von swe_calc() ausführt und die Differenzen sammelt:

class SweCalc : public TestCalculation {
public :
bool calcsAsExpected(
double juldate,
int planet,
int flags,
const double expectedResult[],
const string expectedMessage = "" ) {

double actualResult[6];
char actualMessage[255];

int return_flags = swe_calc(
juldate,
planet,
flags,
actualResult,
actualMessage );

diffs.check( "xx", 6, actualResult, expectedResult );
diffs.check( "msg", actualMessage, expectedMessage );
diffs.check( "flags", return_flags, flags );

return ! hasDiffs();

}
};


Was für andere Funktionsaufrufe der Swiss Ephemeris wiederverwendbar ist, kommt in eine Oberklasse TestCalculation. Im Moment stehen hier nur die Differenzen-Sätze:

class TestCalculation {
public :
bool hasDiffs();
Diffs diffs;
};


Die ganz allgemeine Schicht, die diese Prüfungen durchführt und in der ich den Fehler hatte, folgt hier:

template<typename T> 
class Diff : public DiffBase {
public :
Diff(string name, T actual, T expected)
: actual(actual), expected(expected) {
this->var = new Var(name);
}
Diff(string name, int index, T actual, T expected)
: actual(actual), expected(expected) {
this->var = new VarMember(name,index);
}
virtual string toString() const {
stringstream s;
s << var->getName()
<< "\t: actual=" << actual
<< ", "
<< "expected=" << expected ;
return s.str();
}
~Diff() {
delete var;
}
private :
Var* var;
T actual;
T expected;
};

Die Klasse, die das Problem machte, war Diffs - eine Sammlung mehrerer solcher Abweichungen. Da ich sie auch im System herumreichte, hat mir der Destruktor in den Fuss geschossen, den ich zuerst vorgesehen hatte. Nach Löschung des Destruktors und Umstellung auf einen boost::shared_ptr (der das delete ja automatisch ausführt, wenn der letzte Besitzer des Pointers sein Leben aushaucht) lief alles problemlos.

class Diffs {
public :
Diffs() {
diffs = boost::shared_ptr<vector<DiffBase*>>
(new vector<DiffBase*>);
}
void getLog( vector<string>& log ) const {
log.clear();
for (vector<DiffBase*>::const_iterator i=diffs->begin();
i!=diffs->end();
++i) {
log.push_back( (*i)->toString() );
}
}
bool hasDiffs() { return diffs->size() > 0; }
template<typename T> void check( const string& name,
const T actual,
const T expected ) {
if (actual != expected) {
diffs->push_back( new Diff<T>(name,actual,expected) );
}
}
template<typename T> void check( const string& name,
const int size,
const T actual[],
const T expected[] ) {
for (int i=0; i<size;i++) {
if (actual[i] != expected[i]) {
diffs->push_back( new Diff<T>(name,i,actual[i],expected[i]) );
}
}
}
string toString() const {
string s;
for (vector<DiffBase*>::const_iterator i=diffs->begin();
i!=diffs->end();
++i) {
s.append((*i)->toString());
s.push_back('\n');
}
return s;
}
private :
boost::shared_ptr<vector<DiffBase*>> diffs;
};


Ein isoliert lauffähiges Beispiel habe ich codepad.org gestellt, wo es auch ausgeführt werden kann (ist sogar ISO-C++, obwohl ich mir für das Testprogramm immer den "Standard" C++0x erlaube).

Auch heute gibt es ein
Todo: Für double sollte ein Test mit einer vorgegebenen Genaugkeit durchgeführt werden. Wie ist das in das Template einzubauen?

Keine Kommentare:

Kommentar veröffentlichen