(翻译)30天学习Python👨‍💻第二十天——调试和测试

597 阅读6分钟

30天学习Python👨‍💻第二十天——调试和测试

开发者写不出完美的代码,所以我们尝试构建应用时会出现各种各样的错误、异常或者bug。不仅应该知道语法以及编程语言的概念,还应该知道通过调试解决Bug,通过编写测试确保我们的代码能够在不同的实际场景下运行。因此为了能够用Python写出高质量的代码,我探索了调试和测试的概念,今天主要是写单元测试。

调试

调试是计算机科学中的一个术语,用来找出我们代码中可能存在的异常或者bug。了解代码调试的概念对于理解引起应用程序中意料之外的错误是非常有用的。

Python提供了一些必要的工具,用开箱即用的内置函数来调试代码。最简单的调试代码的方式是使用print语句,这也是我最常使用的。

mian.py

def is_prime(num):
  if num > 1:
    for i in range(2,num):
        if (num % i) == 0:
            return False
    else:
        return True

  else:
    return False

result = is_prime('2')
print(result) # 类型错误

上面的代码块抛出了类型错误。现在简单的方式是检查错误,我们可以使用一个print语句知道函数内部发生了什么。

main.py

def is_prime(num):
    print(num) # 2 (Here using a print might confuse us!)
    if num > 1:
        for i in range(2, num):
            if (num % i) == 0:
                return False
        else:
            return True

    else:
        return False

result = is_prime('2')
print(result)

使用print语句用于检查输入的值可能会有点混乱,因为它看起来像一个数字。这个函数是一个非常简单的例子,可能并不需要这样去批判性的分析,但是这对我们理解通常解决问题的方式是有帮助的。

为了更好的使用调试,Python提供了一个内置的pdb信息。它提供了很多有用的用于调试的方法,比如有set_trace()

main.py

import pdb

def is_prime(num):
    pdb.set_trace()
    if num > 1:
        for i in range(2, num):
            if (num % i) == 0:
                return False
        else:
            return True

    else:
        return False

result = is_prime('2')
print(result)

当程序运行时,编译器会在set_trace()调用的地方暂停程序。现在我们在控制台调试,我们可以输入任何想要在执行点检查其值的变量,例如这个例子中的num。它会立即显示出'2',这是一个字符串。因此我们可以解决这个问题。

从Python3.7开始,有一种非常好的调试方式,使用新的breakpoint方法会在底层自动的调用set_trace方法,这也是推荐的调试方法。

main.py

def is_prime(num):
    breakpoint() # places a breakpoint
    if num > 1:
        for i in range(2, num):
            if (num % i) == 0:
                return False
        else:
            return True

    else:
        return False

result = is_prime('2')
print(result)

现在在pdb控制台中有很多的调试命令可以使用。输入help的时候会提供命令列表。

调试资源

单元测试

我们的IDEs和编辑器提供了很多的工具,可以帮助我们写出更好的代码以及减少错误,比如pylint这样的检查器。我们还可以调试我们的代码检查可能导致的错误。但是更加有效的编程原则是通过单元测试来编写防御性代码,这能够确保我们的程序在不同的实际场景和边界情况下运行。

第一次写测试听起来无聊并且让人心生畏惧。但是从长期来看,编写测试时非常有用的,它可以通过阻止大量不可预测的bug来节省大量的时间和精力。这也能够改善我们的代码,同时可以作为一份好的文档。对于开发者来说,阅读简短的测试理解功能相比通过大量的文档列表来理解更加容易和实用。知道如何为我们的程序编写好的和简单的测试总是很好的。

Python为单元测试提供了一个开箱即用的内置模块——unittest。它也被称为测试运行程序,它可以同时对整个项目进行多个测试。

让我们尝试通过写一些简单的测试来对上面的is_prime函数进行测试。为此,我们需要创建一个测试文件,在本例中是test.py

test.py

import unittest
import main

class TestPrime(unittest.TestCase):
  def test_valid_type(self):
    test_input = 13
    test_result = main.is_prime(test_input)
    expected_result = True
    self.assertEqual(test_result, expected_result)

if(__name__ == '__main__'):
  unittest.main()

在这个例子中第二个测试失败了,因为我们没有对这个函数中可能的类型错误进行处理。因此这个函数需要进行相应的修改。

main.py

def is_prime(num):
    if (not isinstance(num, int)):
        return False
    if num > 1:
        for i in range(2, num):
            if (num % i) == 0:
                return False
            else:
                return True

    else:
        return False

现在这个函数处理无效的输入,并且在这个场景中不会被中断。让我们添加更多的测试用例。另一种方法是将代码块放在try except块中,并在那里处理所有可能的异常。

test.py

import unittest
import main

class TestPrime(unittest.TestCase):
    def test_valid_type(self):
        test_input = 13
        test_result = main.is_prime(test_input)
        expected_result = True
        self.assertEqual(test_result, expected_result)

    def test_invalid_input(self):
        test_input = 'hello'
        test_result = main.is_prime(test_input)
        expected_result = False
        self.assertEqual(test_result, expected_result)

    def test_none_input(self):
        test_input= None
        test_result = main.is_prime(test_input)
        expected_result = False
        self.assertEqual(test_result, expected_result)

    def test_negative_input(self):
        test_input= -13
        test_result = main.is_prime(test_input)
        expected_result = False
        self.assertEqual(test_result, expected_result)

if (__name__ == '__main__'):
    unittest.main()

如果想要给初始化一些变量,或者在测试运行之前进行一些配置,可以写在setup方法中。类似的每个测试后面任何类型的清理都可以写在teardown方法中。setUp方法比tearDown方法更常用。

import unittest
import main

class TestPrime(unittest.TestCase):
    def setUp(self):
        print('This will run before each test')

    def test_valid_type(self):
        test_input = 13
        test_result = main.is_prime(test_input)
        expected_result = True
        self.assertEqual(test_result, expected_result)

    def test_invalid_input(self):
        test_input = 'hello'
        test_result = main.is_prime(test_input)
        expected_result = False
        self.assertEqual(test_result, expected_result)

    def test_none_input(self):
        test_input = None
        test_result = main.is_prime(test_input)
        expected_result = False
        self.assertEqual(test_result, expected_result)

    def test_negative_input(self):
        test_input = -13
        test_result = main.is_prime(test_input)
        expected_result = False
        self.assertEqual(test_result, expected_result)

    def tearDown(self):
        print('this will run after each test')

if (__name__ == '__main__'):
    unittest.main()

这就是为什么单元测试能够帮助我们改善代码,以及确保我们的代码在不同的场景下能够运行的原因。并且它也能确保一个新引入的功能不会影响已经存在的功能。

下面有一些非常的资源可以帮助理解和探索更多Python中的单元测试。

我希望我用简单易懂的解释了调试和测试Python代码的好处以及用法。我们越早的开始进行测试和调试,就能够越早的知道关于这门语言如何写出更好的代码。

这就是今天的全部。明天我将探索如何使用Python创建用于各种用途的脚本。

原文链接

30 Days of Python 👨‍💻 - Day 20 - Debugging and Testing