Python unittest.TestCase执行顺序

时间:2011-03-22 05:40:13

标签: python unit-testing

Python unittest中是否有办法设置运行测试用例的顺序?

在我目前的TestCase课程中,一些测试用例会产生副作用,为其他测试用例设置正常运行的条件。现在我意识到这样做的正确方法是使用setUp()来完成所有设置实现的事情,但我想实现一个设计,其中每个连续的测试构建稍微更多的状态,下一个可以使用。我发现这更优雅。

class MyTest(TestCase):
  def test_setup(self):
   #do something
  def test_thing(self)
   #do something that depends on test_setup()

理想情况下,我希望测试按照它们在课堂中出现的顺序运行。它们看起来按字母顺序排列。

10 个答案:

答案 0 :(得分:66)

不要让它们进行独立测试 - 如果你想进行单片测试,那就写一个单片测试。

class Monolithic(TestCase):
  def step1(self):
      ...

  def step2(self):
      ...

  def _steps(self):
    for name in dir(self): # dir() result is implicitly sorted
      if name.startswith("step"):
        yield name, getattr(self, name) 

  def test_steps(self):
    for name, step in self._steps():
      try:
        step()
      except Exception as e:
        self.fail("{} failed ({}: {})".format(step, type(e), e))

如果测试后来开始失败,并且您希望获得有关所有失败步骤的信息,而不是在第一个失败步骤中暂停测试用例,则可以使用subtests功能:https://docs.python.org/3/library/unittest.html#distinguishing-test-iterations-using-subtests

(对于Python 3.4之前的版本,可以通过unittest2获得子测试功能:https://pypi.python.org/pypi/unittest2

答案 1 :(得分:30)

总是为这样的期望写一个单一的测试是一个很好的做法,但是如果你像我一样愚蠢的家伙,那么你可以简单地按字母顺序写出难看的方法,以便它们按照a到b的顺序排序。 python docs http://docs.python.org/library/unittest.html

  

请注意,运行各种测试用例的顺序是   通过对测试函数名称进行排序来确定   内置字符串排序

实施例

  def test_a_first():
  print "1"
  def test_b_next(): 
  print "2" 
  def test_c_last(): 
  print "3"

答案 2 :(得分:21)

http://docs.python.org/library/unittest.html

  

请注意,运行各种测试用例的顺序是通过根据字符串的内置顺序对测试函数名称进行排序来确定的。

因此,请确保test_setup的名称具有最小的字符串值。

请注意,您不应该依赖此行为 - 不同的测试函数应该独立于执行顺序。如果您明确指出,请参阅上面ngcohlan的答案需要订单。

答案 3 :(得分:12)

旧问题,但我没有看到任何相关问题中列出的其他方式:使用TestSuite

完成排序的另一种方法是将测试添加到unitest.TestSuite。这似乎尊重使用suite.addTest(...)将测试添加到套件的顺序。要做到这一点:

  • 创建一个或多个TestCase子类

    class FooTestCase(unittest.TestCase):
        def test_ten():
            print('Testing ten (10)...')
        def test_eleven():
            print('Testing eleven (11)...')
    
    class BarTestCase(unittest.TestCase):
        def test_twelve():
            print('Testing twelve (12)...')
        def test_nine():
            print('Testing nine (09)...')
    
  • 根据您需要的顺序创建一个可调用的测试套件生成 ,改编自the docsthis question

    def suite():
        suite = unittest.TestSuite()
        suite.addTest(BarTestCase('test_nine'))
        suite.addTest(FooTestCase('test_ten'))
        suite.addTest(FooTestCase('test_eleven'))
        suite.addTest(BarTestCase('test_twelve'))
        return suite
    
  • 执行测试套件,例如,

    if __name__ == '__main__':
        runner = unittest.TextTestRunner(failfast=True)
        runner.run(suite())
    

对于上下文,我需要这个,并且对其他选项不满意。我决定采用上面的测试排序方式。我没有看到这个TestSuite方法列出了几个"单元测试订购问题中的任何一个" (例如,此问题和其他问题,包括execution orderchanging ordertests order)。

答案 4 :(得分:3)

我最终得到了一个对我有用的简单解决方案:

class SequentialTestLoader(unittest.TestLoader):
    def getTestCaseNames(self, testCaseClass):
        test_names = super().getTestCaseNames(testCaseClass)
        testcase_methods = list(testCaseClass.__dict__.keys())
        test_names.sort(key=testcase_methods.index)
        return test_names

然后

unittest.main(testLoader=utils.SequentialTestLoader())

答案 5 :(得分:1)

应该将真正相互依赖的测试明确地链接到一个测试中。

需要不同级别设置的测试,也可能有相应的setUp()运行足够的设置 - 各种方式可以想象。

否则unittest默认按字母顺序处理测试类中的测试类和测试方法(即使loader.sortTestMethodsUsing为None)。 dir()在内部使用,按保证排序。

后一种行为可以用于实用性 - 例如让最新的工作测试先运行,以加快编辑测试周期。 但是,这种行为不应该用于建立真正的依赖。考虑可以通过命令行选项等单独运行测试。

答案 6 :(得分:0)

当我来到这个帖子时,@ ncoghlan的答案正是我所寻找的。我最终修改它以允许每个步测试运行,即使前一步已经抛出错误;这有助于我(也许你!)在多线程以数据库为中心的软件中发现并计划错误传播。

class Monolithic(TestCase):
  def step1_testName1(self):
      ...

  def step2_testName2(self):
      ...

  def steps(self):
      '''
      Generates the step methods from their parent object
      '''
      for name in sorted(dir(self)):
          if name.startswith('step'):
              yield name, getattr(self, name)

  def test_steps(self):
      '''
      Run the individual steps associated with this test
      '''
      # Create a flag that determines whether to raise an error at
      # the end of the test
      failed = False

      # An empty string that the will accumulate error messages for 
      # each failing step
      fail_message = ''
      for name, step in self.steps():
          try:
              step()
          except Exception as e:
              # A step has failed, the test should continue through
              # the remaining steps, but eventually fail
              failed = True

              # get the name of the method -- so the fail message is
              # nicer to read :)
              name = name.split('_')[1]
              # append this step's exception to the fail message
              fail_message += "\n\nFAIL: {}\n {} failed ({}: {})".format(name,
                                                                       step,
                                                                       type(e),
                                                                       e)

      # check if any of the steps failed
      if failed is True:
          # fail the test with the accumulated exception message
          self.fail(fail_message)

答案 7 :(得分:0)

一种简单而灵活的方法是将比较器函数分配给unittest.TestLoader.sortTestMethodsUsing

getTestCaseNames()和所有loadTestsFrom*()方法中对方法名称进行排序时用来比较方法名称的函数。

最低使用量:

import unittest

class Test(unittest.TestCase):
    def test_foo(self):
        """ test foo """
        self.assertEqual(1, 1)

    def test_bar(self):
        """ test bar """
        self.assertEqual(1, 1)

if __name__ == "__main__":
    test_order = ["test_foo", "test_bar"] # could be sys.argv
    loader = unittest.TestLoader()
    loader.sortTestMethodsUsing = lambda x, y: test_order.index(x) - test_order.index(y)
    unittest.main(testLoader=loader, verbosity=2)

输出:

test_foo (__main__.Test)
test foo ... ok
test_bar (__main__.Test)
test bar ... ok

这是按源代码顺序而不是默认词法顺序(输出如上)运行测试的概念证明。

import inspect
import unittest

class Test(unittest.TestCase):
    def test_foo(self):
        """ test foo """
        self.assertEqual(1, 1)

    def test_bar(self):
        """ test bar """
        self.assertEqual(1, 1)

if __name__ == "__main__":
    test_src = inspect.getsource(Test)
    unittest.TestLoader.sortTestMethodsUsing = lambda _, x, y: (
        test_src.index(f"def {x}") - test_src.index(f"def {y}")
    )
    unittest.main(verbosity=2)

我在本文中使用了Python 3.8.0。

答案 8 :(得分:0)

一种方法可以是通过将unittest附加在子测试之前,然后将它们按正确的顺序构建一个测试用例,从而使这些子测试不被_模块视为测试。子操作已执行。

这比依靠unittest模块的排序顺序要好,因为明天可能会改变,并且按照该顺序进行拓扑排序也不是一件容易的事。

这种方法的示例taken from here (免责声明:我自己的模块)如下所示。

在这里,测试用例运行独立的测试,例如检查表参数未设置(test_table_not_set)还是仍然并行测试主键(test_primary_key),但是CRUD测试只有在以下情况下才有意义由先前操作设置的正确顺序和状态。因此,这些测试只是单独进行unit而不是测试。然后,另一项测试(test_CRUD为这些操作建立正确的顺序并进行测试。

import os
import sqlite3
import unittest

from sql30 import db

DB_NAME = 'review.db'


class Reviews(db.Model):
    TABLE = 'reviews'
    PKEY = 'rid'
    DB_SCHEMA = {
        'db_name': DB_NAME,
        'tables': [
            {
                'name': TABLE,
                'fields': {
                    'rid': 'uuid',
                    'header': 'text',
                    'rating': 'int',
                    'desc': 'text'
                    },
                'primary_key': PKEY
            }]
        }
    VALIDATE_BEFORE_WRITE = True

class ReviewTest(unittest.TestCase):

    def setUp(self):
        if os.path.exists(DB_NAME):
            os.remove(DB_NAME)

    def test_table_not_set(self):
        """
        Tests for raise of assertion when table is not set.
        """
        db = Reviews()
        try:
            db.read()
        except Exception as err:
            self.assertIn('No table set for operation', str(err))

    def test_primary_key(self):
        """
        Ensures , primary key is honored.
        """
        db = Reviews()
        db.table = 'reviews'
        db.write(rid=10, rating=5)
        try:
            db.write(rid=10, rating=4)
        except sqlite3.IntegrityError as err:
            self.assertIn('UNIQUE constraint failed', str(err))

    def _test_CREATE(self):
        db = Reviews()
        db.table = 'reviews'
        # backward compatibility for 'write' API
        db.write(tbl='reviews', rid=1, header='good thing', rating=5)

        # New API with 'create'
        db.create(tbl='reviews', rid=2, header='good thing', rating=5)

        # backward compatibility for 'write' API, without tbl,
        # explicitly passed
        db.write(tbl='reviews', rid=3, header='good thing', rating=5)

        # New API with 'create', without table name explicitly passed.
        db.create(tbl='reviews', rid=4, header='good thing', rating=5)

        db.commit()   # save the work.

    def _test_READ(self):
        db = Reviews()
        db.table = 'reviews'

        rec1 = db.read(tbl='reviews', rid=1, header='good thing', rating=5)
        rec2 = db.read(rid=1, header='good thing')
        rec3 = db.read(rid=1)

        self.assertEqual(rec1, rec2)
        self.assertEqual(rec2, rec3)

        recs = db.read()  # read all
        self.assertEqual(len(recs), 4)

    def _test_UPDATE(self):
        db = Reviews()
        db.table = 'reviews'

        where = {'rid': 2}
        db.update(condition=where, header='average item', rating=2)
        db.commit()

        rec = db.read(rid=2)[0]
        self.assertIn('average item', rec)

    def _test_DELETE(self):
        db = Reviews()
        db.table = 'reviews'

        db.delete(rid=2)
        db.commit()
        self.assertFalse(db.read(rid=2))

    def test_CRUD(self):
        self._test_CREATE()
        self._test_READ()
        self._test_UPDATE()
        self._test_DELETE()

    def tearDown(self):
        os.remove(DB_NAME)

答案 9 :(得分:0)

你可以开始:

test_order = ['base']

def index_of(item, list):
    try:
        return list.index(item)
    except:
        return len(list) + 1

2nd 定义order函数:

def order_methods(x, y):
    x_rank = index_of(x[5:100], test_order)
    y_rank = index_of(y[5:100], test_order)
    return (x_rank > y_rank) - (x_rank < y_rank)

第三次在课堂上设置:

class ClassTests(unittest.TestCase):
    unittest.TestLoader.sortTestMethodsUsing = staticmethod(order_methods)