【问题标题】:python, unittest: is there a way to pass command line options to the apppython,unittest:有没有办法将命令行选项传递给应用程序
【发布时间】:2009-06-22 23:23:05
【问题描述】:

我有一个导入 unittest 并有一些 TestCases 的模块。我想 接受一些命令行选项(例如下面的数据文件的名称), 但是当我尝试传递选项时,我收到消息option -i not recognized。是否可以让unittest + 为应用程序提供选项(注意:我使用optparse 来处理选项)?谢谢。

$ python test_app_data.py -i data_1.txt

option -i not recognized

======================

跟进:这是建议解决方案的实施:

import cfg_master  #has the optparse option-handling code

...

if __name__ == '__main__':    
    #add you app's options here...
    options_tpl = ('-i', '--in_dir', '-o', '--out_dir')
    del_lst = []
    for i,option in enumerate(sys.argv):
        if option in options_tpl:
            del_lst.append(i)
            del_lst.append(i+1)

    del_lst.reverse()
    for i in del_lst:
        del sys.argv[i]
        
    unittest.main()

【问题讨论】:

  • 一般来说,是的。在这种情况下,答案似乎很大程度上取决于您没有提供的细节。
  • @jd。您的“跟进”应作为答案发布 - 您的问题应仅包含......好吧......问题。

标签: python unit-testing


【解决方案1】:

以 Alex 的回答为基础,使用 argparse 实际上很容易做到:

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--input', default='My Input')
    parser.add_argument('filename', default='some_file.txt')
    parser.add_argument('unittest_args', nargs='*')

    args = parser.parse_args()
    # TODO: Go do something with args.input and args.filename

    # Now set the sys.argv to the unittest_args (leaving sys.argv[0] alone)
    sys.argv[1:] = args.unittest_args
    unittest.main()

我还没有测试你可以传递给 unittest 的所有标志以查看它们是否有效,但是传递测试名称确实有效,例如:

python test.py --input=foo data.txt MyTest

使用 foodata.txt 运行 MyTest。

【讨论】:

  • 不错!更好的是,unittest.main() 接受 argv 参数,因此您不必弄乱全局 sys.argv,例如像这样:unit_argv = [sys.argv[0]] + args.unittest_args; unittest.main(argv=unit_argv)
  • 你可以使用.parse_known_args(),而不需要使用nargs='*'选项;我的版本见Python: run a unittest.TestCase without calling unittest.main()?
  • 我将这个答案与@MartijnPieters 引用的问题联系起来,因为这种方法对我很有吸引力。但是,当我在传递标志(--failfast 和--catch)时实现这个答案对我不起作用,所以我恢复到 Martijn 的答案。
  • 现在我希望pytest 有这样一个简单的答案!
  • 我建议nargs=argparse.REMAINDER 而不是nargs='*'。后者会阻塞任何--flags(例如--verbose)。
【解决方案2】:

在您没有向我们展示的if __name__ == '__main__': 部分中,您需要先先optparse,然后再将del sys.argv[1:] 传递给unittest 代码,这样后面的代码就不会尝试当你已经处理过它们时,再次解释你的命令行选项。 (有一些您自己的选项有点困难并且也将一些选项传递给unittest,尽管如果您确实有如此复杂的需求也可以这样做)。

【讨论】:

  • 太好了,谢谢;只是为了确认:首先允许 optparse 处理(可能是多个和可变的)应用程序选项,然后从 sys.argv 中删除它们,最后允许 unittest 接管?
  • @jd 是的,这就是它的要点!
  • 但是如果你想将参数传递给 unittest.main() 怎么办?
  • @Matt,将sys.argv 设置为您希望unittest.main 解析的那些参数——不多也不少。
【解决方案3】:

我想我会分享我的解决方案,在测试中添加一个 --debug 开关来控制记录器:

if __name__=='__main__':
     parser = argparse.ArgumentParser(description="Build a compilation script")
     parser.add_argument('--debug', help='Turn on debug messages', action='store_true', default=False)

     args = parser.parse_args()

     if args.debug:
         log_level = logging.DEBUG
     else:
         log_level = logging.INFO

     logging.basicConfig(level=log_level)
     sys.argv.pop()
     unittest.main()

然后我扩展 unittest.TestCase 以添加日志记录:

 class mcs_TestCase(unittest.TestCase, object):
     def __init__(self, *args, **kwargs):
         super(mcs_TestCase,self).__init__(*args,**kwargs)
         logging.basicConfig()
         self.logger = logging.getLogger(__name__)
         ...

现在我可以使用 --debug 在我的测试中打开和关闭消息,但它在常规回归中会被忽略。

【讨论】:

  • 为什么我们需要sys.argv.pop()
  • 我认为这与程序的进一步发展有关。
【解决方案4】:

对于小型独立应用程序,我使用初始标记选项 (-t) 并在调用 argparse.ArgumentParser() 之前调用 unittest.main()

if __name__ == "__main__":
    if len(sys.argv) > 1 and sys.argv[1] in ["-t", "--test"]:
        del(sys.argv[1])
        sys.exit(unittest.main()) # pass sys.argv[

    p = argparse.ArgumentParser()
    . . .

【讨论】:

    【解决方案5】:

    除了其他答案之外,我想就这个问题提供不同的看法。我知道这个问题要求提供一种为单元测试提供命令行选项的方法,但是所陈述的实际问题只是“如何将参数传递给单元测试”。

    这也可以通过环境变量来实现。

    这是一个示例,您可以如何在本地和构建服务器上为测试提供不同的数据:

    my-script.py:

    from os import environ
    
    database = MyDatabase(
        user = 'root',
        password = 'some-pw',
        host = environ['MYSQL_HOST'],
        port = environ['MYSQL_TCP_PORT']
    )
    

    test-local.sh:

    MYSQL_TCP_PORT="3311"
    MYSQL_HOST="localhost"
    python3.7 -m unittest
    

    test-ci.sh:

    MYSQL_TCP_PORT="3306"
    MYSQL_HOST="my-database"
    python3.7 -m unittest
    

    【讨论】:

      【解决方案6】:

      您不应该使用参数和选项来运行单元测试,因为这样会使它们在不同的、不可预测的条件下运行。您应该弄清楚为什么需要使用不同的数据运行测试,并使您的测试套件足够完整以涵盖所有数据集的基础,而不会每次都以不同的方式运行。

      【讨论】:

      • 我收到每周数据报告(文件);在生成公共报告之前,我想将这些新报告提供给单元测试。如果由于某种原因数据的结构或类型发生了变化(例如,添加了一个新的列字段,一个数据范围发生了变化,并且发现了一个错误),我想通过测试来捕捉它们。希望这是有道理的。
      • 条件测试跳过怎么样?我通过执行 unittest 的测试发现来运行我所有的测试用例,但是如果能够在某些测试上添加注释,比如 @skipUnless('-full' in sys.argv),那就太好了,这样我就可以有一个简单的命令行方式运行所有测试,或者只运行一些测试。
      • 如果您想知道我为什么要这样做,如何轻松分离快速单元测试与更长的回归或压力测试?
      • 作为一名 QA 测试工程师,我同意 Ironfroggy 不公平地缓和下来的答案。当您针对某个问题找到一个有争议的解决方案时——例如基于变量和 argparse 的测试——最好再次查看该问题以查看是否有更好的解决方案。例如,如果您想进行“一个测试”并针对庞大的测试配置矩阵运行它,那么有办法做到这一点。这些天(2015 年)还有“Py.test”,它与 unittest 不同,它允许根据测试函数从数据中轻松生成测试用例 id。它比单元测试更少样板。
      • 我理解对导致潜在问题的 OP 想法的看法和警告。但这属于评论,而不是答案。 OP 要求解决他的要求,而不是批评他的要求的可取性。
      猜你喜欢
      • 2011-09-10
      • 2018-05-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-08
      相关资源
      最近更新 更多