《Python Testing Cookbook》读书笔记之一:单元测试

本文是《Python Testing Cookbook》第一章的读书笔记,详细介绍了如何使用Unittest进行单元测试,包括创建虚拟环境、断言基础、测试前后的处理、选择性运行测试、组织测试套件以及批量测试的方法。强调了将复杂测试拆分为小测试的重要性,以及使用迭代实现批量测试的技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


Python Testing Cookbook

读书笔记 pythontesting

Chapter 1: Using Unittest To Develop Basic Tests

配置虚拟环境

在开始写代码测试前,先创建一个独立的测试开发环境,这样可以避免各种包和现有开发环境互相影响,适合进行测试。

一般可以通过virtualenv来创建虚拟环境,这里是官方文档和一篇写得比较好的中文版指南。如果你和我一样使用Anaconda的Python发行版的话,可以使用conda create命令来进行操作,指南戳这里

Anaconda 是一个用来进行大规模数据处理,预测分析和科学计算的Python发行包,里面内置了iPython,NumPy,SciPy等近200种常用包,如果你用python用来做这些事情比较多的话,建议可以直接下载这个。官方地址:https://store.continuum.io/cshop/anaconda/

Asserting the basics

使用例子:

class RomanNumeralConverter(object):
    def __init__(self, roman_numeral):
        self.roman_numeral = roman_numeral
        self.digit_map = {"M":1000, "D":500, "C":100, "L":50, "X":10, "V":5, "I":1}

    def convert_to_decimal(self):
        val = 0
        for char in self.roman_numeral:
            val += self.digit_map[char]
        return val

import unittest

class RomanNumeralConverterTest(unittest.TestCase):
    def test_parsing_millenia(self):
        value = RomanNumeralConverter("M")
        self.assertEquals(1000, value.convert_to_decimal())

    def test_parsing_century(self):
        value = RomanNumeralConverter("C")
        self.assertEquals(100, value.convert_to_decimal())

    def test_parsing_half_century(self):
        value = RomanNumeralConverter("L")
        self.assertEquals(50, value.convert_to_decimal())

    def test_parsing_decade(self):
        value = RomanNumeralConverter("X")
        self.assertEquals(10, value.convert_to_decimal())

    def test_parsing_half_decade(self):
        value = RomanNumeralConverter("V")
        self.assertEquals(5, value.convert_to_decimal())

    def test_parsing_one(self):
        value = RomanNumeralConverter("I")
        self.assertEquals(1, value.convert_to_decimal())

    def test_empty_roman_numeral(self):
        value = RomanNumeralConverter("")
        self.assertTrue(value.convert_to_decimal() == 0)
        self.assertFalse(value.convert_to_decimal() > 0)

    def test_no_roman_numeral(self):
        value = RomanNumeralConverter(None)
        self.assertRaises(TypeError, value.convert_to_decimal)

if __name__ == "__main__":
    unittest.main()
  • 使用方法

    1. 建立测试类,命名方式为要测试的类名+Test,并继承unittest类,如ClassXxxTest(unittest.TestCase)
    2. 在测试类中建立测试方法,如test_xxx_xxx方法
    3. 在类外import unittest,并在主程序入口执行unittest.main()
  • 基本的assert语句:

assertEquals(first, second[, msg])
assertTrue(expression[, msg])
assertFalse(expression[, msg])
assertRaises(exception, callable, ...)
  • 尽量使用assertEquals语句而非assertTrueassertFalse,因为其他几个只会报错,而assertEquals可以显示两个值分别是多少,提供了更多的信息。

  • Unittest可以使用self.fail([msg])来产生失败测试,但尽量使用assert语句改写,因为这个语句在测试正确的情况下用不到。

使用setUptearDown函数在测试前后进行有关处理

如需要每个测试都需要新建实例进行初始化操作的话,可以定义在setUp中;需要在测试中打开文件的话,在tearDown中使用close方法。

将测试类打包成test suite进行测试

if __name__ == "__main__":
    suite = unittest.TestLoader().loadTestsFromTestCase( \
                    RomanNumeralConverterTest)
    unittest.TextTestRunner(verbosity=2).run(suite)

在测试方法中插入注释信息,在每一次该方法运行和失败时显示

def test_parsing_century(self):
    "This test method is coded to fail for demo."
    value = RomanNumeralConverter("C")
    self.assertEquals(10, value.convert_to_decimal())

Alt text

运行一部分测试用例

当测试例子变得很多的时候,每一次都全部运行需要花费很长的时间,此时我们可以用这个方法运行一部分测试用例。

if __name__ == "__main__":
  import sys
  suite = unittest.TestSuite()
  if len(sys.argv) == 1:
    suite = unittest.TestLoader().loadTestsFromTestCase(\
                  RomanNumeralConverterTest)
  else:
    for test_name in sys.argv[1:]:
      suite.addTest(\
        RomanNumeralConverterTest(test_name))
  unittest.TextTestRunner(verbosity=2).run(suite)

用独立的test文件测试几个test suite

这是将几个test suite都放在主程序中

 if __name__ == "__main__":
     import unittest
     from recipe5 import *
     suite1 = unittest.TestLoader().loadTestsFromTestCase( \
                    RomanNumeralConverterTest)
     suite2 = unittest.TestLoader().loadTestsFromTestCase( \
                    RomanNumeralComboTest)
     suite = unittest.TestSuite([suite1, suite2])
     unittest.TextTestRunner(verbosity=2).run(suite)

还可以将几个不同的test suite定义在测试模块中

def combos():
  return unittest.TestSuite(map(RomanNumeralConverterTest,\
       ["test_combo1", "test_combo2", "test_combo3"]))
def all():
  return unittest.TestLoader().loadTestsFromTestCase(\
                RomanNumeralConverterTest)

用以下方法调用所有的suite,如需调用某一个或某几个,参照修改即可。不同的suite可以用来实现不同功能的测试。

if __name__ == "__main__":
    for suite_func in [combos, all]:
       print "Running test suite '%s'" % suite_func.func_name
       suite = suite_func()
       unittest.TextTestRunner(verbosity=2).run(suite)

将老的assert测试代码改成单元测试代码

这是老的测试类

class RomanNumeralTester(object):
  def __init__(self):
    self.cvt = RomanNumeralConverter()
  def simple_test(self):
    print "+++ Converting M to 1000"
    assert self.cvt.convert_to_decimal("M") == 1000

通过unittest.FunctionTestCase方法将其转换成unittest方法,然后添加到suite里。传统的assert方法在一个assert失败后就会报错退出,改成这种形式后,会将所有的测试用例都测试后才退出,并展示错误信息。

import unittest
if __name__ == "__main__":
    tester = RomanNumeralTester()
    suite = unittest.TestSuite()
    for test in [tester.simple_test, tester.combo_test1, \
            tester.combo_test2, tester.other_test]:
        testcase = unittest.FunctionTestCase(test)
        suite.addTest(testcase)
    unittest.TextTestRunner(verbosity=2).run(suite)

将有多个assertion的复杂测试方法拆散成每次测试一个简单功能的小测试方法

def test_convert_to_decimal(self):
    self.assertEquals(0, self.cvt.convert_to_decimal(""))
    self.assertEquals(1, self.cvt.convert_to_decimal("I"))
    self.assertEquals(2010, self.cvt.convert_to_decimal("MMX"))
    self.assertEquals(4000, self.cvt.convert_to_decimal("MMMM"))

应该写成

def test_to_decimal1(self):
    self.assertEquals(0, self.cvt.convert_to_decimal(""))
def test_to_decimal2(self):
    self.assertEquals(1, self.cvt.convert_to_decimal("I"))
def test_to_decimal3(self):
    self.assertEquals(2010, self.cvt.convert_to_decimal("MMX"))
def test_to_decimal4(self):
    self.assertEquals(4000, self.cvt.convert_to_decimal("MMMM"))

这样的好处是前者发生错误时只会报一个错,且第一个assert语句出错时不会执行后面的测试,而第二种方法会检测所有用例,并给出详细的错误统计。

如果我们有很多组要测试的值,这里面会出现大量的重复代码,有没有简单点儿的方法呢?我们可以手动改变python的命名空间实现批量加入函数.

先来看一段代码:

def make_add(n):
    def func(x):
        return x+n
    return func

if __name__ == '__main__':
    for i in xrange(1,10):
        locals()['add_%d'%i] = make_add(i)
    print add_1(7)
    print add_9(19)

在python中函数可以作为参数传递,所以make_add方法可以生成一个加n的函数返回。而在主程序的循环里,我们将add_n方法通过make_add函数来生成,再通过加入locals()添加到本地命名空间,这相当于在本地创建了从add_1add_9的9个函数。

这个搞明白后,就可以动手改写前面的代码了。

v_s = [
    (1000, "M"),
    (100, "C"),
    (50, "L"),
    (10, "X"),
    (5, "V"),
    (1, "I"),
]
def make_test(v, s):
    def func(self):
        value = RomanNumeralConverter(s)
        self.assertEquals(v, value.convert_to_decimal())
    return func
for for i, (j, k) in enumerate(v_s, 1):
    locals()['test_to_decimal%d' % i] = make_test(v, s)

这段代码可以实现前面第二种写法的功能,当你想要添加新的测试用例时,只需在列表v_s中添加即可。

通过迭代实现批量测试

当测试用例很多的时候,还可以使用下面的方法实现批量添加。我们自己写了一个生成assert语句的函数。但这种情况类似于上面讲过的第一种方法,即将很多assert语句写在了同一个测试函数中,如果有一个发生错误,它后面的例子都不会被测试。

def test_bad_inputs(self):
    r = self.cvt.convert_to_roman
    d = self.cvt.convert_to_decimal
    edges = [("equals", r, "", None),\
             ("equals", r, "I", 1.2),\
             ("raises", d, TypeError, None),\
             ("raises", d, TypeError, 1.2)\
            ]

    [self.checkout_edge(edge) for edge in edges]

def checkout_edge(self, edge):
    if edge[0] == "equals":
        f, output, input = edge[1], edge[2], edge[3]
        print("Converting %s to %s..." % (input, output))
        self.assertEquals(output, f(input))
    elif edge[0] == "raises":
        f, exception, args = edge[1], edge[2], edge[3:]
        print("Converting %s, expecting %s" % \
                                       (args, exception))
        self.assertRaises(exception, f, *args)

  
Python Testing Cookbook Paperback: 364 pages Publisher: Packt Publishing (May 17, 2011) Language: English ISBN-10: 1849514666 ISBN-13: 978-1849514668 Over 70 simple but incredibly effective recipes for taking control of automated testing using powerful Python testing tools Learn to write tests at every level using a variety of Python testing tools The first book to include detailed screenshots and recipes for using Jenkins continuous integration server (formerly known as Hudson) Explore innovative ways to introduce automated testing to legacy systems Written by Greg L. Turnquist – senior software engineer and author of Spring Python 1.1 Part of Packt’s Cookbook series: Each recipe is a carefully organized sequence of instructions to complete the task as efficiently as possible In Detail Are you looking at new ways to write better, more efficient tests? Are you struggling to add automated testing to your existing system? The Python unit testing framework, originally referred to as “PyUnit” and now known as unittest, is a framework that makes it easier for you to write automated test suites efficiently in Python. This book will show you exactly how to squeeze every ounce of value out of automated testing. The Python Testing Cookbook will empower you to write tests using lots of Python test tools, code samples, screenshots, and detailed explanations. By learning how and when to write tests at every level, you can vastly improve the quality of your code and your personal skill set. Packed with lots of test examples, this will become your go-to book for writing good tests. This practical cookbook covers lots of test styles including unit-level, test discovery, doctest, BDD, acceptance, smoke, and load testing. It will guide you to use popular Python tools effectively and discover how to write custom extensions. You will learn how to use popular continuous integration systems like Jenkins (formerly known as Hudson) and TeamCity to automatically test your code upon check in. This book explores Python’s built-in ability to run code found embedded in doc strings and also plugging in to popular web testing tools like Selenium. By the end of this book, you will be proficient in many test tactics and be ready to apply them to new applications as well as legacy ones. A practical guide, this cookbook will ensure you fully utilize Python testing tools to write tests efficiently. What you will learn from this book : Get started with the basics of writing automated unit tests and asserting results Use Nose to discover tests and build suites automatically Write Nose plugins that control what tests are discovered and how to produce test reports Add testable documentation to your code Filter out test noise, customize test reports, and tweak doctest’s to meet your needs Write testable stories using lots of tools including doctest, mocks, Lettuce, and Should DSL Get started with the basics of customer-oriented acceptance testing Test the web security of your application Configure Jenkins and TeamCity to run your test suite upon check-in Capture test coverage reports in lots of formats, and integrate with Jenkins and Nose Take the pulse of your system with a quick smoke test and overload your system to find its breaking points Add automated testing to an existing legacy system that isn’t test oriented Approach This cookbook is written as a collection of code recipes containing step-by-step directions on how to install or build different types of Python test tools to solve different problems. Each recipe contains explanations of how it works along with answers to common questions and cross references to other relevant recipes. The easy-to-understand recipe names make this a handy test reference book. Who this book is written for Python developers and programmers with a basic understanding of Python and Python testing will find this cookbook beneficial. It will build on that basic knowledge equipping you with the intermediate and advanced skills required to fully utilize the Python testing tools. Broken up into lots of small code recipes, you can read this book at your own pace, whatever your experience. No prior experience of automated testing is required.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值