Добавьте метаданные в TestCase в модульном тесте Python - PullRequest
0 голосов
/ 28 сентября 2018

Я бы хотел добавить метаданные к отдельным тестам в TestCase, который я написал для использования в модуле Python unittest.Метаданные (в действительности, строка) должны передаваться в процессе тестирования и выводиться в файл XML.

Кроме оставшихся в тесте, данные не будут использоваться ни юниттестом, ни моим тестовым кодом.(У меня есть программа, которая будет запускаться потом, открывать файл XML и искать метаданные / строку).

Ранее я использовал NUnit, который позволяет использовать для этого атрибут C #.В частности, вы можете поместить это над классом:

[Property("SmartArrayAOD", -3)]

, а затем позже найти это в выводе XML.

Возможно ли присоединить метаданные к тесту в unittest Python

1 Ответ

0 голосов
/ 19 августа 2019

Простой способ простого дампа XML

Если все, что вам нужно, это записывать вещи в файл XML после каждого модульного теста, просто добавьте метод tearDown в свой тестовый класс (например, если у вас есть , give it a).

class MyTest(unittest.TestCase):
    def tearDown(self):
        dump_xml_however_you_do()

    def test_whatever(self):
        pass

Общий метод

Если вам нужен общий способ сбора и отслеживания метаданных из всех ваших тестов и их возвращения в конце, попробуйте создать таблицу астропии в своемпроверить класс __init__() и добавить в него строки во время tearDown(), а затем извлечь ссылку на ваши инициализированные экземпляры тестового класса из unittest, например:

Шаг 1: установить повторно используемый подклассunittest.TestCase поэтому нам не нужно дублировать обработку таблицы

(поместить весь пример кода в один файл или скопировать импорт)

"""
Demonstration of adding and retrieving meta data from python unittest tests
"""

import sys
import warnings
import unittest
import copy
import time
import astropy
import astropy.table
if sys.version_info < (3, 0):
    from StringIO import StringIO
else:
    from io import StringIO


class DemoTest(unittest.TestCase):
    """
    Demonstrates setup of an astropy table in __init__, adding data to the table in tearDown
    """

    def __init__(self, *args, **kwargs):
        super(DemoTest, self).__init__(*args, **kwargs)

        # Storing results in a list made it convenient to aggregate them later
        self.results_tables = [astropy.table.Table(
            names=('Name', 'Result', 'Time', 'Notes'),
            dtype=('S50', 'S30', 'f8', 'S50'),
        )]
        self.results_tables[0]['Time'].unit = 'ms'
        self.results_tables[0]['Time'].format = '0.3e'

        self.test_timing_t0 = 0
        self.test_timing_t1 = 0

    def setUp(self):
        self.test_timing_t0 = time.time()

    def tearDown(self):
        test_name = '.'.join(self.id().split('.')[-2:])
        self.test_timing_t1 = time.time()
        dt = self.test_timing_t1 - self.test_timing_t0

        # Check for errors/failures in order to get state & description.  https://stackoverflow.com/a/39606065/6605826
        if hasattr(self, '_outcome'):  # Python 3.4+
            result = self.defaultTestResult()  # these 2 methods have no side effects
            self._feedErrorsToResult(result, self._outcome.errors)
            problem = result.errors or result.failures
            state = not problem
            if result.errors:
                exc_note = result.errors[0][1].split('\n')[-2]
            elif result.failures:
                exc_note = result.failures[0][1].split('\n')[-2]
            else:
                exc_note = ''
        else:  # Python 3.2 - 3.3 or 3.0 - 3.1 and 2.7
            # result = getattr(self, '_outcomeForDoCleanups', self._resultForDoCleanups)  # DOESN'T WORK RELIABLY
            # This is probably only good for python 2.x, meaning python 3.0, 3.1, 3.2, 3.3 are not supported.
            exc_type, exc_value, exc_traceback = sys.exc_info()
            state = exc_type is None
            exc_note = '' if exc_value is None else '{}: {}'.format(exc_type.__name__, exc_value)

        # Add a row to the results table
        self.results_tables[0].add_row()
        self.results_tables[0][-1]['Time'] = dt*1000  # Convert to ms
        self.results_tables[0][-1]['Result'] = 'pass' if state else 'FAIL'
        with warnings.catch_warnings():
            warnings.filterwarnings('ignore', category=astropy.table.StringTruncateWarning)
            self.results_tables[0][-1]['Name'] = test_name
            self.results_tables[0][-1]['Notes'] = exc_note

Шаг 2: настроитьменеджер тестов, который извлекает метаданные

def manage_tests(tests):
    """
    Function for running tests and extracting meta data
    :param tests: list of classes sub-classed from DemoTest

    :return: (TextTestResult, Table, string)
        result returned by unittest
        astropy table
        string: formatted version of the table

    """
    table_sorting_columns = ['Result', 'Time']

    # Build test suite
    suite_list = []
    for test in tests:
        suite_list.append(unittest.TestLoader().loadTestsFromTestCase(test))
    combo_suite = unittest.TestSuite(suite_list)

    # Run tests
    results = [unittest.TextTestRunner(verbosity=1, stream=StringIO(), failfast=False).run(combo_suite)]

    # Catch test classes
    suite_tests = []
    for suite in suite_list:
        suite_tests += suite._tests

    # Collect results tables
    results_tables = []
    for suite_test in suite_tests:
        if getattr(suite_test, 'results_tables', [None])[0] is not None:
            results_tables += copy.copy(suite_test.results_tables)

    # Process tables, if any
    if len(results_tables):
        a = []
        while (len(a) == 0) and len(results_tables):
            a = results_tables.pop(0)  # Skip empty tables, if any
        results_table = a
        for rt in results_tables:
            if len(rt):
                with warnings.catch_warnings():
                    warnings.filterwarnings('ignore', category=DeprecationWarning)
                    results_table = astropy.table.join(results_table, rt, join_type='outer')
        try:
            results_table = results_table.group_by(table_sorting_columns)
        except Exception:
            print('Error sorting test results table. Columns may not be in the preferred order.')
        column_names = list(results_table.columns.keys())
        alignments = ['<' if cn == 'Notes' else '>' for cn in column_names]
        if len(results_table):
            rtf = '\n'.join(results_table.pformat(align=alignments, max_width=-1))
            exp_res = sum([result.testsRun - len(result.skipped) for result in results])
            if len(results_table) != exp_res:
                print('ERROR forming results table. Expected {} results, but table length is {}.'.format(
                    exp_res, len(results_table),
                ))
        else:
            rtf = None

    else:
        results_table = rtf = None

    return results, results_table, rtf

Шаг 3: Пример использования

class FunTest1(DemoTest):
    @staticmethod
    def test_pass_1():
        pass

    @staticmethod
    def test_fail_1():
        assert False, 'Meant to fail for demo 1'


class FunTest2(DemoTest):
    @staticmethod
    def test_pass_2():
        pass

    @staticmethod
    def test_fail_2():
        assert False, 'Meant to fail for demo 2'


res, tab, form = manage_tests([FunTest1, FunTest2])
print(form)
print('')
for r in res:
    print(r)
    for error in r.errors:
        print(error[0])
        print(error[1])

Пример результатов:

$ python unittest_metadata.py 
        Name         Result    Time                    Notes                  
                                ms                                            
-------------------- ------ --------- ----------------------------------------
FunTest2.test_fail_2   FAIL 5.412e-02 AssertionError: Meant to fail for demo 2
FunTest1.test_fail_1   FAIL 1.118e-01 AssertionError: Meant to fail for demo 1
FunTest2.test_pass_2   pass 6.199e-03                                         
FunTest1.test_pass_1   pass 6.914e-03                                         

<unittest.runner.TextTestResult run=4 errors=0 failures=2>

Должен работать с Python 2.7 или 3.7.Вы можете добавить любые столбцы в таблицу.Вы можете добавить параметры и данные в таблицу в setUp, tearDown или даже во время тестов.

Предупреждения:

Это решение обращается к защищенному атрибуту _tests из unittest.suite.TestSuite, который может иметь неожиданные результаты.Эта конкретная реализация работает, как и ожидалось для меня в python2.7 и python3.7, но небольшие различия в том, как построен и опрошен комплект, могут легко привести к странным вещам.Однако я не мог найти другой способ извлечь ссылки на экземпляры моих классов, которые использует unittest.

...