Pergunta

Eu estou trabalhando em um código de computação científica (escrito em C ++), e além de realizar testes de unidade para os componentes menores, eu gostaria de fazer testes de regressão em alguns dos resultados numéricos, comparando a um "conhecido -boa" resposta de revisões anteriores. Existem algumas características que eu gosto:

  • Permitir comparar números para uma tolerância especificada (para ambos os roundoff expectativas de erro e mais frouxas)
  • Capacidade de distinguir entre ints, duplas, etc, e ignorar texto, se necessário
  • saída para dizer o que deu errado e onde
  • Bem-formatado: em uma tabela de dados multi-coluna, mostram apenas a entrada coluna que difere
  • EXIT_SUCCESS Return ou EXIT_FAILURE dependendo se os arquivos corresponder

Existem quaisquer scripts bons ou aplicações lá fora que fazer isso, ou eu terei que fazer a minha própria em Python para ler e comparar arquivos de saída? Certamente eu não sou a primeira pessoa com este tipo de requisitos.

[O que se segue não é estritamente relevante, mas pode fator na decisão do que fazer. Eu uso CMake e sua funcionalidade CTest incorporado para conduzir testes de unidade que usam a estrutura Google Teste. Imagino que não deve ser difícil para adicionar algumas declarações add_custom_command na minha CMakeLists.txt a chamada seja qual for regressão software eu preciso.]

Foi útil?

Solução 3

Acabei escrevendo um script Python para fazer mais ou menos o que eu queria.

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

Outras dicas

Você deve ir para PyUnit , que agora faz parte da lib padrão sob o nome unittest . Ele suporta tudo o que você pediu. A verificação de tolerância, por exemplo, é feito com assertAlmostEqual() .

O utilitário Ndiff pode estar perto do que você está procurando:. é como diff, mas ele irá comparar arquivos de texto de números para uma tolerância desejada

Eu sei que sou muito tarde para a festa, mas alguns meses atrás eu escrevi o utilitário nrtest em uma tentativa de tornar este fluxo de trabalho mais fácil. Parece que ele pode ajudá-lo também.

Aqui está uma visão geral rápida. Cada teste é definida por seus arquivos de entrada e seus arquivos de saída esperados. Após a execução, arquivos de saída são armazenados em um diretório de referência portátil. Uma segunda etapa, em seguida, compara este valor de referência para um índice de referência. Uma atualização recente permitiu extensões de usuário, para que possa definir funções de comparação para os seus dados personalizado.

Espero que ajude.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top