Python unittest.TestCase执行顺序

2025-04-17 08:58:00
admin
原创
16
摘要:问题描述:Python 中有没有办法unittest设置测试用例的运行顺序?在我目前的TestCase课程中,一些测试用例会产生副作用,从而为其他测试用例的正常运行设置条件。现在我意识到正确的做法是使用 来setUp()完成所有与设置相关的操作,但我希望实现一种设计,让每个后续测试构建的状态比下一个测试能够使...

问题描述:

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

在我目前的TestCase课程中,一些测试用例会产生副作用,从而为其他测试用例的正常运行设置条件。现在我意识到正确的做法是使用 来setUp()完成所有与设置相关的操作,但我希望实现一种设计,让每个后续测试构建的状态比下一个测试能够使用的状态略多一些。我觉得这样更优雅。

class MyTest(TestCase):

  def test_setup(self):
    # Do something

  def test_thing(self):
    # Do something that depends on test_setup()

理想情况下,我希望测试按照它们在类中出现的顺序运行。目前看来,它们是按字母顺序运行的。


解决方案 1:

不要让它们成为独立的测试 - 如果您想要一个整体测试,请编写一个整体测试。

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

(子测试功能适用unittest2于 Python 3.4 之前的版本:https://pypi.python.org/pypi/unittest2

解决方案 2:

针对此类期望,始终编写一个单体测试是一个好习惯。但是,如果你像我一样笨手笨脚,那么你可以简单地编写一些看起来丑陋的方法,按照字母顺序排列,以便它们按照 Python 文档unittest — 单元测试框架中提到的从 a 到 b 排序。

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

例子

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

解决方案 3:

来自unittest — 单元测试框架组织测试代码部分:

注意:各种测试的运行顺序是通过根据字符串的内置顺序对测试方法名称进行排序来确定的。

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

请注意,您不应依赖此行为——不同的测试函数应该与执行顺序无关。如果您明确需要顺序,请参阅上面ngcohlan 的回答以获取解决方案。

解决方案 4:

我在任何相关问题中没有看到列出的另一种方法:使用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)...')
  • 创建一个可调用的测试套件生成,按照您想要的顺序添加,改编自文档和这个问题:

  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 方法列出任何几个“单元测试排序问题”(例如,这个问题和其他问题,包括执行顺序,或更改顺序,或测试顺序)。

解决方案 5:

我最终找到了一个适合我的简单解决方案:

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())

解决方案 6:

一种简单而灵活的方法是将比较器函数分配给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。

解决方案 7:

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

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

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

后一种行为可以用于提高实用性——例如,优先运行最新的工作测试,以加快编辑-测试-运行周期。但这种行为不应用于建立真正的依赖关系。请注意,测试可以通过命令行选项等单独运行。

解决方案 8:

一种方法是让这些子测试不被模块视为测试,方法unittest是将它们附加_在它们前面,然后构建一个基于执行这些子操作的正确顺序的测试用例。

这比依赖unittest模块的排序顺序要好,因为模块的排序顺序明天可能会改变,而且实现顺序的拓扑排序也不是很简单。

这种方法的一个例子,取自这里 (免责声明:我自己的模块),如下所示。

在这里,测试用例会运行独立的测试,例如检查表参数是否未设置(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:

你可以从以下开始:

test_order = ['base']

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

第二步定义订单功能:

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)

解决方案 10:

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 += "

FAIL: {}
 {} 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)

解决方案 11:

我还想为我的测试指定一个特定的执行顺序。这里与其他答案的主要区别是:

  • 我想扭曲更详细的测试方法名称,而不使用等替换整个名称step1step2

  • 我还希望控制台中打印的方法执行具有一定的粒度,而不是在其他一些答案中使用单片解决方案。

因此,单片测试方法的执行如下所示:

test_booking (__main__.TestBooking) ... ok

我想要:

test_create_booking__step1 (__main__.TestBooking) ... ok
test_process_booking__step2 (__main__.TestBooking) ... ok
test_delete_booking__step3 (__main__.TestBooking) ... ok

如何实现这一目标

我为方法名称添加了后缀,例如__step<order>定义顺序并不重要)

def test_create_booking__step1(self):
    [...]

def test_delete_booking__step3(self):
    [...]

def test_process_booking__step2(self):
    [...]

对于测试套件,覆盖__iter__将为测试方法构建迭代器的函数。

class BookingTestSuite(unittest.TestSuite):
    """ Extends the functionality of the the standard test suites """
    
    def __iter__(self):
        for suite in self._tests:
            suite._tests = sorted(
                [x for x in suite._tests if hasattr(x, '_testMethodName')], 
                key = lambda x: int(x._testMethodName.split("step")[1])
            )
        return iter(self._tests)

这将按顺序对测试方法进行排序并相应地执行它们。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   2482  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1533  
  PLM(产品生命周期管理)项目对于企业优化产品研发流程、提升产品质量以及增强市场竞争力具有至关重要的意义。然而,在项目推进过程中,范围蔓延是一个常见且棘手的问题,它可能导致项目进度延迟、成本超支以及质量下降等一系列不良后果。因此,有效避免PLM项目范围蔓延成为项目成功的关键因素之一。以下将详细阐述三大管控策略,助力企业...
plm系统   0  
  PLM(产品生命周期管理)项目管理在企业产品研发与管理过程中扮演着至关重要的角色。随着市场竞争的加剧和产品复杂度的提升,PLM项目面临着诸多风险。准确量化风险优先级并采取有效措施应对,是确保项目成功的关键。五维评估矩阵作为一种有效的风险评估工具,能帮助项目管理者全面、系统地评估风险,为决策提供有力支持。五维评估矩阵概述...
免费plm软件   0  
  引言PLM(产品生命周期管理)开发流程对于企业产品的全生命周期管控至关重要。它涵盖了从产品概念设计到退役的各个阶段,直接影响着产品质量、开发周期以及企业的市场竞争力。在当今快速发展的科技环境下,客户对产品质量的要求日益提高,市场竞争也愈发激烈,这就使得优化PLM开发流程成为企业的必然选择。缺陷管理工具和六西格玛方法作为...
plm产品全生命周期管理   0  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用