diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ad3b2e9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,62 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*,cover
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# IDE Specific
+.vs/
+*.pyproj
+*.sln
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..a3c1590
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,13 @@
+language: python
+python:
+ - "3.2"
+ - "3.3"
+ - "3.4"
+ - "3.5"
+ - "3.5-dev" # 3.5 development branch
+ - "3.6-dev" # 3.6 development branch
+ - "nightly" # currently points to 3.7-dev
+# command to install dependencies
+install: "pip install -r requirements.txt"
+# command to run tests
+script: py.test test.py
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..5444a0f
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2016 Joel Wang
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..45303c4
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,155 @@
+junitparser -- Pythonic JUnit/xUnit Result XML Parser
+======================================================
+
+.. image:: https://travis-ci.org/gastlygem/junitparser.svg?branch=master
+
+What does it do?
+----------------
+
+junitparser is a JUnit/xUnit Result XML Parser. Use it to parse and manipulate
+existing Result XML files, or create new JUnit/xUnit result XMLs from scratch.
+
+There are already a lot of modules that converts JUnit/xUnit XML from a
+specific format, but you may run into some proprietory or less-known formats
+and you want to convert them and feed the result to another tool. This is where
+junitparser come into handy.
+
+Why junitparser?
+----------------
+
+* Functionality. There are various JUnit/xUnit XML libraries, some does
+ parsing, some does XML generation, some does manipulation. This module tries
+ to do most functions in a single package.
+* Extensibility. JUnit/xUnit is hardly a standardized format. The base format
+ is somewhat universally agreed with, but beyond that, there could be "custom"
+ elements and attributes. junitparser aims to support them all, by
+ monkeypatching and subclassing some base classes.
+* Pythonic. You can manipulate test cases and suites in a pythonic way.
+* Simplicity. No external dependencies. Though it will use lxml if available.
+
+Installation
+-------------
+
+ pip install junitparser
+
+Usage
+-----
+
+Create Junit XML format reports from scratch
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: python
+
+ from junitparser import TestCase, TestSuite, JunitXml, Skipped, Error
+
+ # Create cases
+ case1 = TestCase('case1')
+ case1.result = Skipped()
+ case2 = TestCase('case2')
+ case2.result = Error('Example error message', 'the_error_type')
+
+ # Create suite and add cases
+ suite = TestSuite('suite1')
+ suite.add_property('build', '55')
+ suite.add_testcase(case1)
+ suite.add_testcase(case2)
+ suite.delete_testcase(case2)
+
+ # Add suite to JunitXml
+ xml = JunitXml()
+ xml.add_testsuite(suite)
+ xml.write('junit.xml')
+
+Read and manipulate exiting JUnit/xUnit XML files
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: python
+
+ from junitparser import JUnitXml
+
+ xml = JUnitXml('/path/to/junit.xml')
+ for suite in result:
+ # handle suites
+ for case in suite:
+ # handle cases
+ xml.write() # Writes back to file
+
+
+Merge XML files
+~~~~~~~~~~~~~~~
+
+.. code-block:: python
+
+ from junitparser import JUnitXml
+
+ xml1 = JUnitXml('/path/to/junit1.xml')
+ xml2 = JUnitXml('/path/to/junit2.xml')
+
+ newxml = xml1 + xml2
+ # Alternatively, merge inplace
+ xml1 += xml2
+
+
+Create XML with custom attributes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: python
+
+ from junitparser import TestCase, Attr
+
+ # The id attribute is not supported by default
+ # But we can support it by monky patching
+ TestCase.id = Attr('id')
+ case = TestCase()
+ case.id = '123'
+
+ print(case.tostring())
+
+And you get the following output::
+
+ b'\n'
+
+Create XML with custom element
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+There may be once in 1000 years you want to it this way, but anyways::
+
+.. code-block:: python
+
+ from junitparser import Element, Attr, TestSuite
+
+ # Suppose you want to add element Custom to TestSuite.
+ # You can create the new element by subclassing Element,
+ # Then add custom attributes to it.
+ class Custom(Element):
+ _tag = 'custom'
+ foo = Attr()
+ bar = Attr()
+
+ # Then monkeypatch TestSuite to handle Custom
+ # TODO: Tricky part, update later
+
+
+TODO
+----
+
+
+Notes
+-----
+
+Python 2 is not supported. Currently there is no plan to support Python 2.
+
+There are some other packages providing similar functionalities,
+https://pypi.python.org/pypi/xunitparser/
+https://pypi.python.org/pypi/xunitgen
+https://pypi.python.org/pypi/xunitmerge
+https://pypi.python.org/pypi/junit-xml
+
+
+
+
+Usage
+-----
+
+
+_`junit-xml`: https://pypi.python.org/pypi/junit-xml
diff --git a/junitparser.py b/junitparser.py
new file mode 100644
index 0000000..aa5e735
--- /dev/null
+++ b/junitparser.py
@@ -0,0 +1,330 @@
+"""
+junitparser is a JUnit/xUnit Result XML Parser. Use it to parse and manipulate
+existing Result XML files, or create new JUnit/xUnit result XMLs from scratch.
+
+:copyright: (c) 2016 by Joel Wang.
+:license: Apache2, see LICENSE for more details.
+"""
+
+try:
+ from lxml import etree
+except ImportError:
+ from xml.etree import ElementTree as etree
+
+
+class JUnitXmlError(Exception):
+ "Exception for JUnit XML related errors."
+
+
+class Attr:
+ "XML element attribute descriptor."
+ def __init__(self, name=None):
+ self.name = name
+ def __get__(self, instance, cls):
+ "Gets value from attribute, return None if attribute doesn't exist."
+ return instance._elem.attrib.get(self.name)
+ def __set__(self, instance, value):
+ "Sets XML element attribute."
+ instance._elem.attrib[self.name] = value
+
+
+def attributed(cls):
+ "Decorator to read XML element attribute name from class attribute."
+ for key, value in vars(cls).items():
+ if isinstance(value, Attr):
+ value.name = key
+ return cls
+
+
+class junitxml(type):
+ "Metaclass to decorate the xml class"
+ def __new__(meta, name, bases, methods):
+ cls = super().__new__(meta, name, bases, methods)
+ cls = attributed(cls)
+ return cls
+
+
+class Element(metaclass=junitxml):
+ "Base class for all Junit elements."
+ def __init__(self, name):
+ self._elem = etree.Element(name)
+ def __hash__(self):
+ return hash(etree.tostring(self._elem))
+ def append(self, elem):
+ "Append an child element to current element."
+ self._elem.append(elem._elem)
+ @classmethod
+ def fromstring(cls, text):
+ "Construct Junit objects with XML string."
+ instance = cls()
+ instance._elem = etree.fromstring(text)
+ return instance
+ @classmethod
+ def fromelem(cls, elem):
+ "Constructs Junit objects with an element."
+ if elem is None:
+ return
+ instance = cls()
+ instance._elem = elem
+ for attr in vars(instance):
+ if isinstance(getattr(instance, attr), Attr):
+ setattr(instance, attr, instance._elem.attrib.get(attr))
+ return instance
+ def iterchildren(self, Child):
+ "Iterate through specified Child type elements."
+ elems = self._elem.iterfind(Child._tag)
+ for elem in elems:
+ yield Child.fromelem(elem)
+ def child(self, Child):
+ "Find a single child of specified type."
+ elem = self._elem.find(Child._tag)
+ return Child.fromelem(elem)
+ def remove(self, instance):
+ for elem in self._elem.iterfind(instance._tag):
+ child = instance.__class__.fromelem(elem)
+ if child == instance:
+ self._elem.remove(child._elem)
+ def tostring(self):
+ "Converts element to XML string."
+ return etree.tostring(self._elem, encoding = 'utf-8', pretty_print=True)
+
+
+class JUnitXml(Element):
+ _tag = 'testsuites'
+ def __init__(self):
+ super().__init__(self._tag)
+ def __iter__(self):
+ return super().iterchildren(TestSuite)
+ def __len__(self):
+ return len(list(self.__iter__()))
+ def __add__(self, other):
+ result = JUnitXml()
+ for suite in self:
+ result.add_testsuite(suite)
+ for suite in other:
+ result.add_testsuite(suite)
+ return result
+ def __iadd__(self, other):
+ for suite in other:
+ self.add_testsuite(suite)
+ return self
+ def add_testsuite(self, suite):
+ self.append(suite)
+ @classmethod
+ def fromfile(cls, filepath):
+ instance = cls()
+ tree = etree.parse(filepath)
+ instance._elem = tree.getroot()
+ if instance._elem.tag != cls._tag:
+ raise JUnitXmlError("Invalid format.")
+ return instance
+ def write(self, filepath):
+ tree = etree.ElementTree(self._elem)
+ tree.write(filepath, encoding='utf-8', xml_declaration=True)
+
+
+class TestSuite(Element):
+ _tag = 'testsuite'
+ name = Attr()
+ hostname = Attr()
+ time = Attr()
+ timestamp = Attr()
+ tests = Attr()
+ failures = Attr()
+ errors = Attr()
+ def __init__(self, name=None):
+ super().__init__(self._tag)
+ if name:
+ self.name = name
+ def __iter__(self):
+ return super().iterchildren(TestCase)
+ def __len__(self):
+ return len(list(self.__iter__()))
+ def remove_testcase(self, testcase):
+ for case in self:
+ if case == testcase:
+ super().remove(case)
+ def update_case_count(self):
+ tests = errors = failures = 0
+ for case in self:
+ tests += 1
+ if isinstance(case.result, Failure):
+ failures += 1
+ elif isinstance(case.result, Error):
+ errors += 1
+ self.tests = str(tests)
+ self.errors = str(errors)
+ self.failures = str(failures)
+ def add_property(self, name, value):
+ props = self.child(Properties)
+ if props is None:
+ props = Properties()
+ self.append(props)
+ prop = Property(name, value)
+ props.add_property(prop)
+ def add_testcase(self, testcase):
+ self.append(testcase)
+ def add_suite(self, suite):
+ self.append(suite)
+ def properties(self):
+ props = self.child(Properties)
+ if props is None:
+ return
+ for prop in props:
+ yield prop
+ def remove_property(self, property):
+ props = self.child(Properties)
+ if props is None:
+ return
+ for prop in props:
+ if prop == property:
+ props.remove(property)
+ def testsuites(self):
+ for suite in self.iterchildren(TestSuite):
+ yield suite
+
+
+class Properties(Element):
+ _tag = 'properties'
+ def __init__(self):
+ super().__init__(self._tag)
+ def add_property(self, property):
+ self.append(property)
+ def __iter__(self):
+ return super().iterchildren(Property)
+
+
+class Property(Element):
+ _tag = 'property'
+ name = Attr()
+ value = Attr()
+ def __init__(self, name=None, value=None):
+ super().__init__(self._tag)
+ if name:
+ self.name = name
+ if value:
+ self.value = value
+ def __eq__(self, other):
+ return self.name == other.name
+
+
+class Result(Element):
+ _tag = None
+ message = Attr()
+ type = Attr()
+ def __init__(self, message=None, type=None):
+ super().__init__(self._tag)
+ if message:
+ self.message = message
+ if type:
+ self.type = type
+ def __eq__(self, other):
+ return (self._tag == other._tag and
+ self.type == other.type and
+ self.message == other.message)
+
+
+class Skipped(Result):
+ _tag = 'skipped'
+ def __eq__(self, other):
+ return super().__eq__(other)
+
+
+class Failure(Result):
+ _tag = 'failure'
+ def __eq__(self, other):
+ return super().__eq__(other)
+
+
+class Error(Result):
+ _tag = 'error'
+ def __eq__(self, other):
+ return super().__eq__(other)
+
+
+class TestCase(Element):
+ _tag = 'testcase'
+ name = Attr()
+ classname = Attr()
+ time = Attr()
+ _possible_results = {Failure, Error, Skipped}
+ def __init__(self):
+ super().__init__(self._tag)
+ def __hash__(self):
+ return super().__hash__()
+ def __eq__(self, other):
+ # TODO: May not work correctly of unreliable hash method is used.
+ return hash(self) == hash(other)
+ @property
+ def result(self):
+ "One of the Failure, Skipped, and Error objects."
+ results = []
+ for res in self._possible_results:
+ result = self.child(res)
+ if result is not None:
+ results.append(result)
+ if len(results) > 1:
+ raise JUnitXmlError("Only one result allowed per test case.")
+ elif len(results) == 0:
+ return None
+ else:
+ return results[0]
+ @result.setter
+ def result(self, value):
+ # First remove all existing results
+ for res in self._possible_results:
+ result = self.child(res)
+ if result is not None:
+ self.remove(result)
+ # Then add current result
+ self.append(value)
+ @property
+ def system_out(self):
+ elem = self.child(SystemOut)
+ if elem is not None:
+ return elem.text
+ return None
+ @system_out.setter
+ def system_out(self, value):
+ out = self.child(SystemOut)
+ if out is not None:
+ out.text = value
+ else:
+ out = SystemOut(value)
+ self.append(out)
+ @property
+ def system_err(self):
+ elem = self.child(SystemErr)
+ if elem is not None:
+ return elem.text
+ return None
+ @system_err.setter
+ def system_err(self, value):
+ err = self.child(SystemErr)
+ if err is not None:
+ err.text = value
+ else:
+ err = SystemErr(value)
+ self.append(err)
+
+
+class System(Element):
+ "Parent class for SystemOut and SystemErr"
+ _tag = ''
+ def __init__(self, content=None):
+ super().__init__(self._tag)
+ self.text = content
+ @property
+ def text(self):
+ return self._elem.text
+ @text.setter
+ def text(self, value):
+ self._elem.text = value
+
+
+class SystemOut(System):
+ _tag = 'system-out'
+
+
+class SystemErr(System):
+ _tag = 'system-err'
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..55b033e
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1 @@
+pytest
\ No newline at end of file
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..9a635ba
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,32 @@
+from setuptools import setup, find_packages
+import os
+import sys
+
+def read(fname):
+ try:
+ return open(os.path.join(os.path.dirname(__file__), fname)).read()
+ except IOError:
+ return ''
+
+setup(name='junitparser',
+ version='0.1',
+ description='Manipulates JUnit/xUnit Result XML files',
+ long_description=read('README.rst'),
+ classifiers=[
+ 'Development Status :: 3 - Alpha',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: Apache Software License',
+ 'Topic :: Text Processing',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.3',
+ 'Programming Language :: Python :: 3.4',
+ 'Programming Language :: Python :: 3.5',
+ 'Programming Language :: Python :: 3.6',
+ ],
+ url='',
+ author='Joel Wang',
+ author_email='gastlygem@gmail.com',
+ license='Apache 2.0',
+ keywords='junit xunit xml parser',
+ packages=find_packages(),
+ zip_safe=False)
diff --git a/test.py b/test.py
new file mode 100644
index 0000000..f003be4
--- /dev/null
+++ b/test.py
@@ -0,0 +1,253 @@
+import unittest
+from junitparser import TestCase, TestSuite, Skipped, Failure, Error, Attr, JUnitXmlError, JUnitXml
+from xml.etree import ElementTree as etree
+
+
+class Test_JunitXml(unittest.TestCase):
+ def test_fromstring(self):
+ text = """
+
+
+
+
+ """
+ result = JUnitXml.fromstring(text)
+ self.assertEqual(len(result), 2)
+ def test_add_suite(self):
+ suite1 = TestSuite()
+ suite2 = TestSuite()
+ result = JUnitXml()
+ result.add_testsuite(suite1)
+ result.add_testsuite(suite2)
+ self.assertEqual(len(result), 2)
+ def test_construct_xml(self):
+ suite1 = TestSuite()
+ suite1.name = 'suite1'
+ case1 = TestCase()
+ case1.name = 'case1'
+ suite1.add_testcase(case1)
+ result = JUnitXml()
+ result.add_testsuite(suite1)
+ self.assertEqual(result._elem.tag, 'testsuites')
+ suite = result._elem.findall('testsuite')
+ self.assertEqual(len(suite), 1)
+ self.assertEqual(suite[0].attrib['name'], 'suite1')
+ case = suite[0].findall('testcase')
+ self.assertEqual(len(case), 1)
+ self.assertEqual(case[0].attrib['name'], 'case1')
+ def test_add(self):
+ result1 = JUnitXml()
+ suite1 = TestSuite()
+ result1.add_testsuite(suite1)
+ result2 = JUnitXml()
+ suite2 = TestSuite()
+ result2.add_testsuite(suite2)
+ result3 = result1 + result2
+ self.assertEqual(len(result3), 2)
+ def test_iadd(self):
+ result1 = JUnitXml()
+ suite1 = TestSuite()
+ result1.add_testsuite(suite1)
+ result2 = JUnitXml()
+ suite2 = TestSuite()
+ result2.add_testsuite(suite2)
+ result1 += result2
+ self.assertEqual(len(result1), 2)
+
+
+class Test_RealFile(unittest.TestCase):
+ def setUp(self):
+ import tempfile
+ self.tmp = tempfile.mktemp(suffix='.xml')
+ def tearDown(self):
+ import os
+ os.remove(self.tmp)
+ def test_fromfile(self):
+ text = """
+
+
+
+
+
+
+
+
+
+ Assertion failed
+
+
+
+
+
+
+"""
+ with open(self.tmp, 'w') as f:
+ f.write(text)
+ xml = JUnitXml.fromfile(self.tmp)
+ suite1, suite2 = list(iter(xml))
+ self.assertEqual(len(list(suite1.properties())), 0)
+ self.assertEqual(len(list(suite2.properties())), 3)
+ self.assertEqual(len(suite2), 3)
+ self.assertEqual(suite2.name, 'JUnitXmlReporter.constructor')
+ self.assertEqual(suite2.tests, '3')
+ case_results = [Failure, Skipped, type(None)]
+ for case, result in zip(suite2, case_results):
+ self.assertIsInstance(case.result, result)
+ def test_write(self):
+ suite1 = TestSuite()
+ suite1.name = 'suite1'
+ case1 = TestCase()
+ case1.name = 'case1'
+ suite1.add_testcase(case1)
+ result = JUnitXml()
+ result.add_testsuite(suite1)
+ result.write(self.tmp)
+ with open(self.tmp) as f:
+ text = f.read()
+ self.assertIn('suite1', text)
+ self.assertIn('case1', text)
+
+class Test_TestSuite(unittest.TestCase):
+ def test_fromstring(self):
+ text = """
+
+
+ """
+ suite = TestSuite.fromstring(text)
+ suite.update_case_count()
+ self.assertEqual(suite.name, 'suitename')
+ self.assertEqual(suite.tests, '1')
+ def test_props_fromstring(self):
+ text = """
+
+ """
+ suite = TestSuite.fromstring(text)
+ for prop in suite.properties():
+ self.assertEqual(prop.name, 'name1')
+ self.assertEqual(prop.value, 'value1')
+ def test_len(self):
+ text = """
+
+ """
+ suite = TestSuite.fromstring(text)
+ self.assertEqual(len(suite), 2)
+ def test_add_case(self):
+ suite = TestSuite()
+ case1 = TestCase()
+ case2 = TestCase()
+ case2.result = Failure()
+ case3 = TestCase()
+ case3.result = Error()
+ suite.add_testcase(case1)
+ suite.add_testcase(case2)
+ suite.add_testcase(case3)
+ suite.update_case_count()
+ self.assertEqual(suite.tests, '3')
+ self.assertEqual(suite.failures, '1')
+ self.assertEqual(suite.errors, '1')
+ def test_add_property(self):
+ suite = TestSuite()
+ suite.add_property('name1', 'value1')
+ res_prop = next(suite.properties())
+ self.assertEqual(res_prop.name, 'name1')
+ self.assertEqual(res_prop.value, 'value1')
+ def test_remove_case(self):
+ suite = TestSuite()
+ case1 = TestCase()
+ case1.name = 'test1'
+ case2 = TestCase()
+ case2.name = 'test2'
+ suite.add_testcase(case1)
+ suite.add_testcase(case2)
+ suite.remove_testcase(case1)
+ self.assertEqual(len(suite), 1)
+ def test_remove_property(self):
+ suite = TestSuite()
+ suite.add_property('name1', 'value1')
+ suite.add_property('name2', 'value2')
+ suite.add_property('name3', 'value3')
+ for prop in suite.properties():
+ if prop.name == 'name2':
+ suite.remove_property(prop)
+ self.assertEqual(len(list(suite.properties())), 2)
+ def test_suite_in_suite(self):
+ suite = TestSuite('parent')
+ childsuite = TestSuite('child')
+ suite.add_suite(childsuite)
+ self.assertEqual(len(list(suite.testsuites())), 1)
+
+
+class Test_TestCase(unittest.TestCase):
+ def test_fromstring(self):
+ text = """
+
+ System out
+ System err
+ """
+ case = TestCase.fromstring(text)
+ self.assertEqual(case.name, "testname")
+ self.assertIsInstance(case.result, Failure)
+ self.assertEqual(case.system_out, "System out")
+ self.assertEqual(case.system_err, "System err")
+ def test_illegal_xml_multi_results(self):
+ text = """
+
+
+
+ """
+ case = TestCase.fromstring(text)
+ self.assertRaises(JUnitXmlError)
+ def test_case_attributes(self):
+ case = TestCase()
+ case.name = 'testname'
+ case.classname = 'testclassname'
+ case.time = '15.123'
+ case.result = Skipped()
+ self.assertEqual(case.name, 'testname')
+ self.assertEqual(case.classname, 'testclassname')
+ self.assertEqual(case.time, '15.123')
+ self.assertIsInstance(case.result, Skipped)
+ def test_case_output(self):
+ case = TestCase()
+ case.system_err = 'error message'
+ case.system_out = 'out message'
+ self.assertEqual(case.system_err, 'error message')
+ self.assertEqual(case.system_out, 'out message')
+ case.system_err = 'error2'
+ case.system_out = 'out2'
+ self.assertEqual(case.system_err, 'error2')
+ self.assertEqual(case.system_out, 'out2')
+ def test_set_multiple_results(self):
+ case = TestCase()
+ case.result = Skipped()
+ case.result = Failure()
+ self.assertIsInstance(case.result, Failure)
+ def test_monkypatch(self):
+ TestCase.id = Attr('id')
+ case = TestCase()
+ case.id = "100"
+ self.assertEqual(case.id, "100")
+ def test_equal(self):
+ case = TestCase()
+ case.name = 'test1'
+ case2 = TestCase()
+ case2.name = 'test1'
+ self.assertEqual(case, case2)
+ def test_not_equal(self):
+ case = TestCase()
+ case.name = 'test1'
+ case2 = TestCase()
+ case2.name = 'test2'
+ self.assertNotEqual(case, case2)
+ def test_from_elem(self):
+ elem = etree.Element('testcase', name='case1')
+ case = TestCase.fromelem(elem)
+ self.assertEqual(case.name, 'case1')
+ def test_to_string(self):
+ case = TestCase()
+ case.name = 'test1'
+ case_str = case.tostring()
+ self.assertEqual(case_str, b'\n')
+
+if __name__ == '__main__':
+ unittest.main()