testes de regressão numérica
-
20-08-2019 - |
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 ouEXIT_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.]
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.