Question

Je travaille sur un code informatique scientifique (écrit en C ++) et, en plus de la réalisation de tests unitaires pour les composants plus petits, je souhaite effectuer des tests de régression sur certaines sorties numériques en les comparant à un

  • Autoriser la comparaison de nombres avec une tolérance spécifiée (pour l’erreur d’arrondi et les attentes plus souples)
  • Possibilité de faire la distinction entre ints, doubles, etc. et d'ignorer le texte si nécessaire
  • Sortie bien formatée pour indiquer ce qui ne va pas et où: dans un tableau de données à plusieurs colonnes, affichez uniquement l'entrée de colonne différente
  • Retourne EXIT_SUCCESS ou EXIT_FAILURE selon que les fichiers correspondent

Existe-t-il de bons scripts ou applications pour cela, ou devrai-je rouler le mien en Python pour lire et comparer les fichiers de sortie? Je ne suis sûrement pas la première personne à avoir ce genre d'exigences.

[Ce qui suit n’est pas strictement pertinent, mais il peut être pris en compte dans la décision. J'utilise CMake et sa fonctionnalité CTest intégrée pour piloter des tests unitaires utilisant le framework Google Test. J'imagine qu'il ne devrait pas être difficile d'ajouter quelques add_custom_command déclarations dans mon CMakeLists.txt appel du logiciel de régression dont j'ai besoin.]

Était-ce utile?

La solution 3

J'ai fini par écrire un script Python pour faire plus ou moins ce que je voulais.

#!/usr/bin/env python

import sys
import re
from optparse import OptionParser
from math import fabs

splitPattern = re.compile(r',|\s+|;')

class FailObject(object):
    def __init__(self, options):
        self.options = options
        self.failure = False

    def fail(self, brief, full = ""):
        print ">>>> ", brief
        if options.verbose and full != "":
            print "     ", full
        self.failure = True


    def exit(self):
        if (self.failure):
            print "FAILURE"
            sys.exit(1)
        else:
            print "SUCCESS"
            sys.exit(0)

def numSplit(line):
    list = splitPattern.split(line)
    if list[-1] == "":
        del list[-1]

    numList = [float(a) for a in list]
    return numList

def softEquiv(ref, target, tolerance):
    if (fabs(target - ref) <= fabs(ref) * tolerance):
        return True

    #if the reference number is zero, allow tolerance
    if (ref == 0.0):
        return (fabs(target) <= tolerance)

    #if reference is non-zero and it failed the first test
    return False

def compareStrings(f, options, expLine, actLine, lineNum):
    ### check that they're a bunch of numbers
    try:
        exp = numSplit(expLine)
        act = numSplit(actLine)
    except ValueError, e:
#        print "It looks like line %d is made of strings (exp=%s, act=%s)." \
#                % (lineNum, expLine, actLine)
        if (expLine != actLine and options.checkText):
            f.fail( "Text did not match in line %d" % lineNum )
        return

    ### check the ranges
    if len(exp) != len(act):
        f.fail( "Wrong number of columns in line %d" % lineNum )
        return

    ### soft equiv on each value
    for col in range(0, len(exp)):
        expVal = exp[col]
        actVal = act[col]
        if not softEquiv(expVal, actVal, options.tol):
            f.fail( "Non-equivalence in line %d, column %d" 
                    % (lineNum, col) )
    return

def run(expectedFileName, actualFileName, options):
    # message reporter
    f = FailObject(options)

    expected  = open(expectedFileName)
    actual    = open(actualFileName)
    lineNum   = 0

    while True:
        lineNum += 1
        expLine = expected.readline().rstrip()
        actLine = actual.readline().rstrip()

        ## check that the files haven't ended,
        #  or that they ended at the same time
        if expLine == "":
            if actLine != "":
                f.fail("Tested file ended too late.")
            break
        if actLine == "":
            f.fail("Tested file ended too early.")
            break

        compareStrings(f, options, expLine, actLine, lineNum)

        #print "%3d: %s|%s" % (lineNum, expLine[0:10], actLine[0:10])

    f.exit()

################################################################################
if __name__ == '__main__':
    parser = OptionParser(usage = "%prog [options] ExpectedFile NewFile")
    parser.add_option("-q", "--quiet",
                      action="store_false", dest="verbose", default=True,
                      help="Don't print status messages to stdout")

    parser.add_option("--check-text",
                      action="store_true", dest="checkText", default=False,
                      help="Verify that lines of text match exactly")

    parser.add_option("-t", "--tolerance",
                      action="store", type="float", dest="tol", default=1.e-15,
                      help="Relative error when comparing doubles")

    (options, args) = parser.parse_args()

    if len(args) != 2:
        print "Usage: numdiff.py EXPECTED ACTUAL"
        sys.exit(1)

    run(args[0], args[1], options)

Autres conseils

Vous devriez choisir PyUnit , qui fait maintenant partie de la bibliothèque standard sous le nom unittest . Il prend en charge tout ce que vous avez demandé. La vérification de la tolérance, par exemple, est effectuée avec assertAlmostEqual() .

L'utilitaire ndiff peut être proche de ce que vous êtes cherche: c'est comme diff, mais il comparera les fichiers texte de nombres à la tolérance souhaitée.

Je sais que je suis assez en retard pour la fête, mais il y a quelques mois, j'ai écrit l'utilitaire nrtest dans une tentative de rendre ce flux de travail plus facile. Il semble que cela pourrait vous aider aussi.

Voici un aperçu rapide. Chaque test est défini par ses fichiers d'entrée et ses fichiers de sortie attendus. Après exécution, les fichiers de sortie sont stockés dans un répertoire de référence portable. Une seconde étape compare ensuite ce repère à un repère de référence. Une mise à jour récente a activé les extensions utilisateur. Vous pouvez donc définir des fonctions de comparaison pour vos données personnalisées.

J'espère que cela vous aidera.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top