Pregunta

Estoy trabajando en un código informático científico (escrito en C ++), y además de realizar pruebas unitarias para los componentes más pequeños, me gustaría hacer pruebas de regresión en algunos de los resultados numéricos comparándolos con un quot; conocido-bueno " respuesta de revisiones anteriores. Hay algunas características que me gustaría:

  • Permitir comparar números con una tolerancia especificada (tanto para error de redondeo como para expectativas más flexibles)
  • Capacidad para distinguir entre ints, dobles, etc., e ignorar el texto si es necesario
  • Salida bien formateada para saber qué salió mal y dónde: en una tabla de datos de varias columnas, solo muestra la entrada de columna que difiere
  • Volver EXIT_SUCCESS o EXIT_FAILURE dependiendo de si los archivos coinciden

¿Hay algún buen script o aplicación que haga esto, o tendré que usar el mío en Python para leer y comparar archivos de salida? Seguramente no soy la primera persona con este tipo de requisitos.

[Lo siguiente no es estrictamente relevante, pero puede tener en cuenta la decisión de qué hacer. Utilizo CMake y su funcionalidad CTest incorporada para conducir pruebas unitarias que usan el marco de prueba de Google. Me imagino que no debería ser difícil agregar algunas add_custom_command declaraciones en mi CMakeLists.txt para llamar al software de regresión que necesito.]

¿Fue útil?

Solución 3

Terminé escribiendo un script de Python para hacer más o menos lo que quería.

#!/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)

Otros consejos

Debería elegir PyUnit , que ahora forma parte de la biblioteca estándar en el nombre unittest . Es compatible con todo lo que pediste. La verificación de tolerancia, por ejemplo, se realiza con assertAlmostEqual() .

La utilidad ndiff puede estar cerca de lo que usted es buscando: es como diff, pero comparará archivos de texto de números con la tolerancia deseada.

Sé que llego bastante tarde a la fiesta, pero hace unos meses escribí la utilidad nrtest en un intento de facilitar este flujo de trabajo. Parece que también podría ayudarte.

Aquí hay una descripción general rápida. Cada prueba se define por sus archivos de entrada y sus archivos de salida esperados. Después de la ejecución, los archivos de salida se almacenan en un directorio de referencia portátil. Un segundo paso luego compara este punto de referencia con un punto de referencia. Una actualización reciente ha habilitado extensiones de usuario, por lo que puede definir funciones de comparación para sus datos personalizados.

Espero que ayude.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top