【问题标题】:How do I test beginner student Python programs that use input() (maybe with unittest?)?如何测试使用 input() 的初学者 Python 程序(也许使用 unittest?)?
【发布时间】:2011-02-01 16:30:34
【问题描述】:

我是使用 Python 的初级编程课程的评分者。我自己的python-fu不是那么强,但我想尝试自动化一些分级。

在网上看,我喜欢 PyUnit 测试套件,尽管它可能有点超出我的要求。

我的问题是我不确定如何将我想要的测试输入传递给学生的​​函数,因为他们还没有使用命令行参数甚至多个函数,而是通过input() 函数获取用户输入。

一个愚蠢的例子:

#/usr/bin/python3.1
# A silly example python program
def main():
    a = int(input("Enter an integer: "))
    b = int(input("Enter another integer: "))
    c = a+b
    print("The sum is %d" % c)

if __name__ == '__main__'
    main()

对于我这个愚蠢的例子,我将如何编写一个可以检查多个不同输入的输出的单元测试? (即,如果我将 2 和 3 传递给输入,则输出字符串应为“总和为 5”)

【问题讨论】:

  • 两行输入缺少右括号。
  • @Javier:已修复。谢谢,有人编辑了我的问题并添加了eval(,但没有关闭另一边。

标签: python unit-testing testing python-unittest


【解决方案1】:

编辑:仅提出此建议,因为该示例不可进行单元测试(我假设初学者只会被约束弄糊涂)

如果您只关心与您正​​在寻找的输出匹配,为什么不使用一些“愚蠢的”bash?比如:

echo -e "2\n3" | python test.py | grep -q "The sum is 5" && echo "Success"

如果您正在执行类似这样的相对琐碎的程序,那么这应该是一个足够或足够好的解决方案,只需要很少的努力。

【讨论】:

  • 这实际上对我最有用。你是对的,制作可测试的程序不在学生所学的范围内(这是一个非常入门级的课程),但这样的事情实际上是最好的。
  • @Jason - 在回答之前我应该​​更全面地阅读这个问题。 叹息
【解决方案2】:

你不能真正对它进行单元测试。编写单元测试的其中一件事是,您通常需要以不同的方式编写代码以允许对其进行单元测试。因此,在这种情况下,您需要将输入调用分解到一个单独的函数中,然后您可以对其进行修补。

def my_input(prompt):
    return input(prompt)

def main():
    a = int(eval(my_input("Enter an integer: "))

等等。现在您的测试可以猴子修补myscript.my_input 以返回您想要的值。

【讨论】:

  • 感谢您的澄清。出于测试目的制作这样的东西在我所阅读的内容中很有意义。
【解决方案3】:

如果您需要与命令行程序的交互比echo 提供的更复杂,那么您可能需要查看expect

【讨论】:

    【解决方案4】:

    来自docs

    对象 sys.stdin、sys.stdout 和 sys.stderr 被初始化为文件 对应的对象 解释器的标准输入、输出 和错误流。

    所以按照这个逻辑,这样的事情似乎有效。使用所需的输入创建一个文件:

    $ cat sample_stdin.txt
    hello
    world
    

    然后重定向sys.stdin 指向该文件:

    #!/usr/bin/env python
    import sys
    
    fh = open('sample_stdin.txt', 'r')
    sys.stdin = fh
    
    line1 = raw_input('foo: ')
    line2 = raw_input('bar: ')
    
    print line1
    print line2
    

    输出:

    $python redirecting_stdin.py
    foo: bar: hello
    world
    

    【讨论】:

      【解决方案5】:

      简短的回答,不要那样做。您必须针对可测试性进行设计。这意味着提供一种简单的方法来为用于与系统资源通信的事物提供接口,这样您就可以在测试时提供这些接口的替代实现。

      另一个答案中描述的猴子修补解决方案确实有效,但它是您选择中最原始的。就个人而言,我会为用户交互编写一个接口类。例如:

      class UserInteraction(object):
          def get_input(self):
              raise NotImplementedError()
          def send_output(self, output):
              raise NotImplementedError()
      

      然后,需要与用户对话的事情可以获取您的类的实例作为构造函数或函数参数。默认实现可以调用实际的input 函数或其他任何东西,但有一个用于测试的版本提供样本输入或缓冲输出以便可以检查。

      顺便说一句,这就是我讨厌 Singleton 的原因(无论如何它都不能真正在 Python 中有效地实现)。它通过创建一个可全局访问的实例来破坏您的测试能力,而该实例无法使用存根版本进行测试。

      【讨论】:

        【解决方案6】:

        我的建议是使用 Python 为单元测试提供的两个框架之一重构您的代码:unittest(又名 PyUnit)和 doctest

        这是一个使用 unittest 的示例:

        import unittest
        
        def adder(a, b):
            "Return the sum of two numbers as int"
            return int(a) + int(b)
        
        class TestAdder(unittest.TestCase):
            "Testing adder() with two int"
            def test_adder_int(self):
                self.assertEqual(adder(2,3), 5)
        
            "Testing adder() with two float"
            def test_adder_float(self):
                self.assertEqual(adder(2.0, 3.0), 5)
        
            "Testing adder() with two str - lucky case"
            def test_adder_str_lucky(self):
                self.assertEqual(adder('4', '1'), 5)
        
            "Testing adder() with two str"
            def test_adder_str(self):
                self.assertRaises(ValueError, adder, 'x', 'y')
        
        if __name__ == '__main__':
            unittest.main()
        

        这是一个使用 doctest 的示例:

        # adder.py
        
        def main(a, b):
            """This program calculate the sum of two numbers. 
            It prints an int (see %d in print())
        
            >>> main(2, 3)
            The sum is 5
        
            >>> main(3, 2)
            The sum is 5
        
            >>> main(2.0, 3)
            The sum is 5
        
            >>> main(2.0, 3.0)
            The sum is 5
        
            >>> main('2', '3')
            Traceback (most recent call last):
                ...
            TypeError: %d format: a number is required, not str
            """
            c = a + b
            print("The sum is %d" % c)
        
        def _test():
            import doctest, adder
            return doctest.testmod(adder)
        
        if __name__ == '__main__':
            _test()
        

        使用 doctest 我使用 input() 做了另一个示例(我假设您使用的是 Python 3.X):

        # adder_ugly.py
        
        def main():
            """This program calculate the sum of two numbers.
            It prints an int (see %d in print())
        
            >>> main()
            The sum is 5
            """
            a = int(input("Enter an integer: "))
            b = int(input("Enter another integer: "))
            c = a+b
            print("The sum is %d" % c)
        
        
        def _test():
            import doctest, adder_ugly
            return doctest.testmod(adder_ugly)
        
        if __name__ == '__main__':
            _test()
        

        我将使用-v 选项运行上述每个示例:

        python adder_ugly.py -v
        

        供您参考:

        http://docs.python.org/py3k/library/unittest.html?highlight=unittest#unittest

        http://docs.python.org/py3k/library/doctest.html#module-doctest

        【讨论】:

          【解决方案7】:

          您可以模拟 input 函数来提供来自您的测试环境的输入。

          这似乎可行。未经测试。

          class MockInput( object ):
              def __init__( self, *values ):
                  self.values= list(values)
                  self.history= []
              def __call__( self, *args, **kw ):
                  try:
                      response= self.values.pop(0)
                      self.history.append( (args, kw, response) )
                      return response
                  except IndexError:
                      raise EOFError()
          
          class TestSomething( unittest.TestCase ):
              def test_when_input_invalid( self ):
                  input= MockInput( "this", "and", "that" )
                  # some test case based on the input function
          

          【讨论】:

            【解决方案8】:

            将 sys.stdin 替换为 StringIO(或 cStringIO)对象。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2018-02-09
              • 2016-02-14
              • 1970-01-01
              • 1970-01-01
              • 2012-03-23
              • 1970-01-01
              相关资源
              最近更新 更多