2009-06-28 1 views
3

Я работаю над научным вычислительным кодом (написанным на C++), и в дополнение к выполнению модульных тестов для небольших компонентов, я бы хотел сделать регрессионное тестирование на некоторых числовых выводах, сравнив их с «хорошо известный» ответ от предыдущих изменений. Есть несколько особенностей, я хотел бы:Числовые регрессионные тесты

  • Разрешить сравнение чисел с заданной точностью (как для ошибок округления и рыхлого ожидания)
  • Способности различать Интс, удваивается, и т.д., и игнорировать текст, если необходимо
  • Ну-форматированный вывод сказать, что пошло не так и где: в таблице нескольких столбцов данных, показывают только запись столбца, отличающуюся
  • Возврат EXIT_SUCCESS или EXIT_FAILURE в зависимости от того, соответствуют ли файлы

Есть ли какие-нибудь хорошие сценарии или приложения, которые делают это, или мне придется катить свой собственный в Python, чтобы читать и сравнивать выходные файлы? Конечно, я не первый человек с такими требованиями.

[Следующее не имеет особого значения, но это может повлиять на решение о том, что делать. Я использую CMake и его встроенную функциональность CTest для тестирования модульных тестов, которые используют платформу Google Test. Я полагаю, что это не должно быть трудно добавить несколько add_custom_command заявления в моем CMakeLists.txt назвать любое программное обеспечение, регрессия мне нужно.]

+0

Большинство из того, что вы описываете, являются стандартными функциями модульного тестирования. Вы просите что-то выше и выше unittest? –

+0

Единичное тестирование, насколько я понимаю, оно рассматривает минимальные тесты с априорными ответами. (Если я напишу функцию returnTwo, предназначенную для возврата значения два, я могу сделать единичный тест, чтобы проверить правильность возвращаемого значения.) Регрессионное тестирование, поскольку я имею в виду, что он должен генерировать данные на гораздо более высоком уровне и сравнивать данные, сгенерированные будущими версиями, с более старыми данными. –

+0

Извините, ваше впечатление от регрессионного тестирования не очень полезно. Вы не сравниваете эту версию с предыдущей версией. Вы сравниваете обе версии с известными хорошими результатами. Всегда можно создать единичный тест, который имеет «магический» ответ, полученный из предыдущей версии, без объяснения причин.Это может служить проверкой того, что результаты предыдущей версии сохраняются, даже если никто не знает, правильны они или нет. –

ответ

0

В итоге я написал сценарий Python, чтобы делать больше или меньше того, что хотел.

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

Вы должны пойти на PyUnit, который теперь является частью стандартной Lib под названием unittest , Он поддерживает все, что вы просили. Проверка допуска, например, выполняется с помощью assertAlmostEqual().

+0

Извините, я не уточнил ранее, но код, который я проверяю, не является кодом Python. Я бы написал внешний скрипт Python для чтения в файле данных и проверил значения там. –

+0

В этом случае scipy может помочь в чтении данных: http://www.scipy.org/Cookbook/InputOutput –

0

Утилита ndiff может быть близка к тому, что вы ищете: это как diff, но будет сравнивать текстовые файлы чисел с требуемым допуском.

0

Я знаю, что я довольно поздно к партии, но несколько месяцев назад я написал утилиту nrtest в попытке сделать этот рабочий процесс проще. Похоже, вам это тоже поможет.

Вот краткий обзор. Каждый тест определяется его входными файлами и ожидаемыми выходными файлами. После выполнения выходные файлы хранятся в портативном контрольном каталоге. Второй шаг затем сравнивает этот тест с эталонным эталоном. Недавнее обновление позволило расширить пользовательские расширения, поэтому вы можете определить функции сравнения для своих пользовательских данных.

Надеюсь, это поможет.