第 6 章 unittest 单元测试框架 - Selenium3 自动化测试
第 6 章 unittest 单元测试框架
单元测试是一项对技术要求很高的工作,只有白盒测试人员和软件开发人员才能胜任。但用单元测试框架做单元测试却十分简单,而且单元测试框架不仅可以用来做单元测试,它还适用于不同类型的「自动化」测试。
1.提供测试用例组织和(批量)执行
在 Python 中,我们编写的代码可以定义类、方法和函数,那么如何定义一条「测试用例」?如何灵活地控制这些「测试用例」的执行顺序?单元测试框架会告诉我们。
2.提供丰富的断言方法
当我们进行功能测试时,测试用例需要有预期结果。当测试用例的执行结果与预期结果不一致时,判定测试用例失败。在自动化测试中,通过「断言」来判定测试用例执行成功与否。
一般单元测试框架会提供丰富的断言方法。例如,判断相等/不相等、包含/不包含、True/False 等。
3.提供丰富的日志和输出HTML报告
自动化测试在运行过程中并不需要人工干预,因此执行的结果非常重要。我们需要从结果中清晰地看出失败的原因。另外,我们还需要统计测试用例的执行结果,如总执行时间、失败测试用例数、成功测试用例数等,这些功能也是由单元测试框架提供的。
从以上几点来看,单元测试框架不仅可以用来写测试用例,凡是涉及自动化测试的工作都可以由单元测试框架完成拓展支持,如 Web 自动化测试、App 自动化测试,以及接口自动化测试等。
6.1 认识 unittest
在 Python 中有诸多单元测试框架,如 unittest、nose、pytest 等,Python 3.8.x 版本已经将 unittest 作为一个标准模块放入 Python 开发包中。
6.1.1 认识单元测试
不用单元测试框架能写单元测试吗?答案是肯定的。单元测试本质上就是通过一段代码去验证另外一段代码,所以不用单元测试框架也可以写单元测试。下面就通过例子演示。
创建一个被测试模块文件:calculator.py
# 计算器类 class Calculator: """ 用于完成两个数的加、减、乘、除 """ def __init__(self, a, b): self.a = int(a) self.b = int(b) # 加法 def add(self): return self.a + self.b # 减法 def sub(self): return self.a - self.b # 乘法 def mul(self): return self.a * self.b # 除法 def div(self): return self.a / self.b
test_calculator.py
from calculator import Calculator def test_add(): c = Calculator(3, 5) result = c.add() assert result == 8, '加法运算失败!' def test_sub(): c = Calculator(7, 2) result = c.sub() assert result == 5, '减法运算失败!' def test_mul(): c = Calculator(3, 3) result = c.mul() assert result == 10, '乘法运算失败!' def test_div(): c = Calculator(6, 2) result = c.div() assert result == 3, '除法运算失败!' if __name__ == '__main__': test_add() test_sub() test_mul() test_div()
这样的测试存在着一些问题。首先,我们需要自己定义断言失败的提示;其次,当一个测试函数运行失败后,后面的测试函数将不再执行;最后,执行结果无法保存并输出到统计HTML报告。
当然,我们可以通过编写更多的代码来解决这些问题,但这就偏离了我们做单元测试的初衷。我们应该将重点放在测试本身,而不是其他上面。引入单元测试框架可以很好地解决这些问题。
下面通过 unittest 单元测试框架重新编写测试用例。
test_calculator_ut.py
import unittest from calculator import Calculator class TestCalculator(unittest.TestCase): def test_add(self): c = Calculator(3, 5) result = c.add() self.assertEqual(result, 8) def test_sub(self): c = Calculator(7, 2) result = c.sub() self.assertEqual(result, 5) def test_mul(self): c = Calculator(3, 3) result = c.mul() self.assertEqual(result, 10) def test_div(self): c = Calculator(6, 2) result = c.div() self.assertEqual(result, 3) if __name__ == '__main__': unittest.main(verbosity=2)
运行结果如下
引入 unittest 模块。如果想用 unittest 编写测试用例,那么一定要遵守它的命名「规则」。
(1)创建一个测试类,这里为 TestCalculator 类,必须要继承 unittest 模块的 TestCase 类。
(2)创建一个测试方法,该方法必须以「test」开头。
接下来的测试步骤与前面测试代码相同。
首先,调用被测试类,传入初始化数据。
其次,调用被测试方法,得到计算结果。通过 unittest 提供的 assertEqual()方法来断言结果是否与预期结果相同。该方法由 TestCase 父类提供,由于继承了该类,所以可以通过 self 调用。
最后,调用 unittest 的 main()来执行测试用例,它会按照前面的两条命名规则查找测试用例并执行。
测试结果明显丰富了很多,用「.」表示一条运行通过的测试用例,用「F」表示一条运行失败的测试用例,用「E」表示一条运行错误的测试用例,用「s」表示一条运行跳过的测试用例。本次统计运行了 4 条测试用例,运行时间为 0.001s,失败(failures)了 1 条测试用例。失败的测试用例也有更清晰的说明。
6.1.2 重要的概念
在 unittest 文档中有四个重要的概念:Test Case、Test Suite、Test Runner 和 Test Fixture。只有理解了这几个概念,才能理解单元测试的基本特征。
1.Test Case
Test Case 是最小的测试单元,用于检查特定输入集合的特定返回值。unittest 提供了 TestCase 基类,我们创建的测试类需要继承该基类,它可以用来创建新的测试用例。
2.Test Suite
测试套件是测试用例、测试套件或两者组成的集合,用于组装一组要运行的测试。unittest 提供了 TestSuite 类来创建测试套件。
3.Test Runner
Test Runner 是一个输出组件,用于协调测试的执行并向用户提供结果。Test Runner 可以使用图形界面、文本界面或返回特殊值来展示执行测试的结果。unittest 提供了 TextTestRunner 类运行测试套件,为了生成 HTML 格式的测试报告,后面会选择使用 HTMLTestRunner 运行类。
4.Test Fixture
Test Fixture 代表执行一个或多个测试所需的环境准备,以及关联的清理动作。例如,创建临时或代理数据库、目录,或启动服务器进程。unittest 中提供了 setUp()/tearDown()、setUpClass()/tearDownClass()等方法来完成这些操作。
在理解了上面几个概念之后,我们对前面的测试用例做如下修改。
test_calculator_ut2
import unittest from calculator import Calculator class TestCalculator(unittest.TestCase): # 测试用例前置动作 @classmethod def setUpClass(cls): print("test start:") # 测试用例后置动作 @classmethod def tearDownClass(cls): print("test end") def test_add(self): c = Calculator(3, 5) result = c.add() self.assertEqual(result, 8) def test_sub(self): c = Calculator(7, 2) result = c.sub() self.assertEqual(result, 5) def test_mul(self): c = Calculator(3, 3) result = c.mul() self.assertEqual(result, 10) def test_div(self): c = Calculator(6, 2) result = c.div() self.assertEqual(result, 3) if __name__ == '__main__': # 创建测试套件 suit = unittest.TestSuite() suit.addTest(TestCalculator("test_add")) suit.addTest(TestCalculator("test_sub")) suit.addTest(TestCalculator("test_mul")) suit.addTest(TestCalculator("test_div")) # 创建测试运行器 runner = unittest.TextTestRunner() runner.run(suit)
首先,创建一个测试类并继承 TestCase 类,在该类下面创建一条以「test」开头的方法为测试用例。这个前面已有说明,这里再次说明是为了强调它的重要性。
其次,在测试类中增加了 setUp()/tearDown()方法,用于定义测试用例的前置和后置动作。因为在当前测试中暂时用不上,所以这里定义了一些简单的打印。
接下来,是测试用例的执行。首先,抛弃了 unittest 提供的 main()方法,而是调用 TestSuite 类下面的 addTest()来添加测试用例。因为一次只能添加一条用例,所以需要指定测试类及测试方法。然后,再调用 TextTestRunner 类下面的 run()运行测试套件。
这样做确实比直接使用 main()方法要麻烦得多,但也并非没有优点。
首先,测试用例的执行顺序可以由测试套件的添加顺序控制,而 main()方法只能按照测试类、方法的名称来执行测试用例。例如,TestA 类比 TestB 类先执行,test_add()用例比 test_div()用例先执行。
其次,当一个测试文件中有很多测试用例时,并不是每次都要执行所有的测试用例,尤其是比较耗时的 UI 自动化测试。因而通过测试套件和测试运行器可以灵活地控制要执行的测试用例。
执行结果如下。
从执行结果可以看到,setUp()/tearDown() 作用于每条测试用例的开始之处与结束之处。
6.1.3 断言方法
在执行测试用例的过程中,最终测试用例执行成功与否,是通过测试得到的实际结果与预期结果进行比较得到的。unittest 框架的 TestCase 类提供的用于测试结果的断言方法如表 6-1 所示。
断言方法的使用如下所示。
test_assert
import unittest class TestAssert(unittest.TestCase): def test_equal(self): self.assertEqual(2+2, 4) self.assertEqual("python", "python") self.assertNotEqual("hello", "python") def test_in(self): self.assertIn("hello", "hello world") self.assertNotIn("hi", "hello") def test_true(self): self.assertTrue(True) self.assertFalse(False) if __name__ == '__main__': unittest.main(verbosity=2)
运行上面的测试用例,即可通过测试结果推断出这些断言方法是如何使用的。
6.1.4 测试用例的组织与 discover 方法
前面针对 Calculator 类所编写的测试用例存在以下问题。
首先,一个功能对应一条测试用例显然是不够的,要写多少测试用例取决于你对功能需求与测试方法的理解。
其次,是测试用例的划分,笔者建议一个测试类对应一个被测试功能点。
calculator
# 计算器类 class Calculator: """ 用于完成两个数的加、减、乘、除 """ def __init__(self, a, b): self.a = int(a) self.b = int(b) # 加法 def add(self): return self.a + self.b # 减法 def sub(self): return self.a - self.b # 乘法 def mul(self): return self.a * self.b # 除法 def div(self): return self.a / self.b
test_calculator
from calculator import Calculator import unittest class TestAdd(unittest.TestCase): """ add()方法测试 """ def test_add_integer(self): """ 整数相加测试 """ c = Calculator(3, 5) self.assertEqual(c.add(), 8) def test_add_decimals(self): """ 小数相加测试 """ c = Calculator(3.2, 5.5) self.assertEqual(c.add(), 8) def test_add_string(self): """ 字符串整数相加测试 """ c = Calculator("7", "9") self.assertEqual(c.add(), 16) # …… class TestSub(unittest.TestCase): """ sub()方法测试 """ def test_sub(self): c = Calculator(7, 2) result = c.sub() self.assertEqual(result, 5) class TestMul(unittest.TestCase): """ mul()方法测试 """ def test_mul(self): c = Calculator(3, 3) result = c.mul() self.assertEqual(result, 9) class TestDiv(unittest.TestCase): """ div()方法测试 """ def test_div(self): c = Calculator(6, 2) result = c.div() self.assertEqual(result, 3) if __name__ == '__main__': unittest.main()
我们可以在一个测试文件中定义多个测试类,只要它们遵循测试用例的「规则」,main()方法就可以找到并执行它们。但是,我们要测试的类或方法可能有很多。
下面开发一个功能,用于判断某年是否为闰年。创建 leap_year.py 文件。
leap_year
class LeapYear: """计算结果某一年份是否为闰年""" def __init__(self, year): self.year = int(year) def answer(self): year = self.year if year % 100 == 0: if year % 400 == 0: # 整百年能被400整除的是闰年 return "{0}是闰年".format(year) else: return "{0}不是闰年".format(year) else: if year % 4 == 0: # 非整百年能被4整除的为闰年 return "{0}是闰年".format(year) else: return "{0}不是闰年".format(year)
创建对应的测试文件 test_leap_year.py。
test_leap_year
from leap_year import LeapYear import unittest class TestLeapYear(unittest.TestCase): def test_2000(self): ly = LeapYear(2000) self.assertEqual(ly.answer(), "2000是闰年") def test_2004(self): ly = LeapYear(2004) self.assertEqual(ly.answer(), "2004是闰年") def test_2017(self): ly = LeapYear(2017) self.assertEqual(ly.answer(), "2017不是闰年") def test_2100(self): ly = LeapYear(2100) self.assertEqual(ly.answer(), "2100不是闰年") if __name__ == '__main__': unittest.main()
显然,这里的判断闰年功能(leap_year.py)和计算器功能(calculator.py)并不相关,它们的代码分别写在两个文件当中,所以对应的测试用例最好分开,分别为 test_calculator.py 和 test_leap_year.py。
当前目录结构如下:
如何执行多个测试文件呢?unittest 中的 TestLoader 类提供的 discover()方法可以从多个文件中查找测试用例。该类根据各种标准加载测试用例,并将它们返回给测试套件。
正常情况下,不需要创建这个类的实例。unittest 提供了可以共享的 defaultTestLoader 类,可以使用其子类或方法创建实例,discover()方法就是其中之一。
找到指定目录及其子目录下的所有测试的模块文件,只有匹配的文件名才能被加载。如果启动的不是顶层目录,那么顶层目录必须单独指定。
● start_dir:待测试的模块名或测试用例目录。
● pattern='test*.py':测试用例文件名的匹配原则。此处匹配文件名以「test」开头的「.py」类型的文件,星号「*」表示任意多个字符。
● top_level_dir=None:测试模块的顶层目录,如果没有顶层目录,则默认为 None。
现在通过 discover()方法重新实现 run_tests.py 文件的功能。
run_tests.py
import unittest # 定义测试用例的目录为当前目录中test_case/目录 test_dir = "./test_case" suits = unittest.defaultTestLoader.discover(test_dir, pattern='test*.py',top_level_dir=None) if __name__ == '__main__': runner = unittest.TextTestRunner() runner.run(suits)
discover()方法会自动根据测试用例目录(test_dir)查找测试用例文件(test*.py),并将找到的测试用例添加到测试套件中,
因此,可以直接通过 run()方法执行测试套件 suits。这种方式极大地简化了测试用例的查找,我们需要做的就是按照文件的匹配规则创建测试文件即可。
执行单一目录内的测试用例
# get all tests from SearchProductTest and HomePageTest class search_tests = unittest.TestLoader().loadTestsFromTestCase(SearchTests) home_page_tests = unittest.TestLoader().loadTestsFromTestCase(HomePageTest) # create a test suite combining search_test and home_page_test smoke_tests = unittest.TestSuite([home_page_tests, search_tests])
首先通过unittest.TestLoader().loadTestsFromTestCase() 方法手动添加测试类
unittest.TestLoader().loadTestsFromTestCase(HomePageTest)
然后通过unittest.TestSuite() 方法添加上述的结果对象变量组成的列表
最后通过TestRunner.run()执行即可输出结果。
# get the directory path to output report file result_dir = os.getcwd() # open the report file outfile = open(result_dir + '\SmokeTestReport.html', 'w') # configure HTMLTestRunner options runner = HTMLTestRunner.HTMLTestRunner(stream=outfile, title='Test Report', description='Smoke Tests') # run the suite using HTMLTestRunner runner.run(smoke_tests)
6.2 关于 unittest 还需要知道的
关于 unittest 还有一些问题值得进一步探讨,如测试用例成员方法的执行顺序等。
6.2.1 测试用例的执行顺序
测试用例的执行顺序涉及多个层级:多个测试目录 > 多个测试文件 > 多个测试类 > 多个测试方法(测试用例)。unittest 提供的 main()方法和 discover()方法是按照什么顺序查找测试用例的呢?
我们先运行一个例子,再解释 unittest 的执行策略。
test_order_1
import unittest class TestBdd(unittest.TestCase): def setUp(self): print("test TestBdd:") def test_ccc(self): print("test ccc") def test_aaa(self): print("test aaa") class TestAdd(unittest.TestCase): def setUp(self): print("test TestAdd:") def test_bbb(self): print("test bbb") if __name__ == '__main__': unittest.main()
执行结果如下。
无论执行多少次,结果都是一样的。通过上面的结果,相信你已经找到 main()方法执行测试用例的规律了。
因为 unittest 默认根据 ASCII 码的顺序加载测试用例的(数字与字母的顺序为 0~9,A~Z,a~z),所以TestAdd 类会优先于TestBdd 类被执行,test_aaa() 方法会优先于test_ccc() 方法被执行,也就是说,它并不是按照测试用例的创建顺序从上到下执行的。
discover()方法和 main()方法的执行顺序是一样的。对于测试目录与测试文件来说,上面的规律同样适用。test_aaa.py 文件会优先于 test_bbb.py 文件被执行。所以,如果想让某个测试文件先执行,可以在命名上加以控制。
除命名外,有没有其他办法控制测试用例的执行顺序呢?答案是肯定的,前面也有介绍,我们可以声明测试套件 TestSuite 类,通过 addTest()方法按照一定的预期的顺序来加载测试用例。
修改上面的例子如下。
test_order_2
import unittest class TestBdd(unittest.TestCase): def setUp(self): print("test TestBdd:") def test_ccc(self): print("test ccc") def test_aaa(self): print("test aaa") class TestAdd(unittest.TestCase): def setUp(self): print("test TestAdd:") def test_bbb(self): print("test bbb") if __name__ == '__main__': # unittest.main() # 构造测试集 suite = unittest.TestSuite() suite.addTest(TestBdd("test_aaa")) suite.addTest(TestBdd("test_ccc")) suite.addTest(TestAdd("test_bbb")) # 执行测试 runner = unittest.TextTestRunner() runner.run(suite)
执行结果如下。
现在的执行顺序与 addTest()方法加载测试用例的顺序相同。不过,当测试用例非常多时,不推荐用这种方法创建测试套件,原因前面也有说明,最自然简便的方法是通过命名控制执行顺序。
如果测试用例在设计时不产生相互依赖,那么测试用例的执行顺序就没那么重要了。
6.2.2 执行多级目录的测试用例
当测试用例的数量达到一定量级时,就要考虑目录划分,比如规划如下测试目录。
对于上面的目录结构,如果将 discover()方法中的 start_dir 参数定义为「./test_case」目录,那么只能加载 test_a.py 文件中的测试用例。如何让 unittest 查找 test_case/下子目录中的测试文件呢?方法很简单,就是在每个子目录下放一个__init__.py 文件。__init__.py 文件的作用是将一个目录标记成一个标准的 Python 模块。
6.2.3 跳过测试和预期失败
在运行测试时,有时需要直接跳过某些测试用例,或者当测试用例符合某个条件时跳过测试,又或者直接将测试用例设置为失败。unittest 提供了实现这些需求的装饰器。
无条件地跳过装饰的测试,需要说明跳过测试的原因。
如果条件为真,则跳过装饰的测试。
当条件为真时,执行装饰的测试。
不管执行结果是否失败,都将测试标记为失败。
test_skip.py
import unittest class MyTest(unittest.TestCase): @unittest.skip("直接跳过测试") def test_skip(self): print("test aaa") @unittest.skipIf(3 > 2, "当条件为True时跳过测试") def test_skip_if(self): print('test bbb') @unittest.skipUnless(3 > 2, "当条件为True时执行测试") def test_skip_unless(self): print('test ccc') @unittest.expectedFailure def test_expected_failure(self): self.assertEqual(2, 3) if __name__ == '__main__': unittest.main()
执行结果如下。
上面的例子创建了四条测试用例。
第一条测试用例通过@unittest.skip()装饰,直接跳过测试。
第二条测试用例通过@unittest.skipIf()装饰,当条件为真时跳过测试;3>2 条件为真(True),所以跳过测试。
第三条测试用例通过@unittest.skipUnless()装饰,当条件为真时执行测试;3>2 条件为真(True),执行测试。
第四条测试用例通过@unittest.expectedFailure 装饰,不管执行结果是否失败,都将测试标记为失败,但不会抛出失败信息。
当然,这些方法同样适用于测试类,只需将它们针对测试类装饰即可。
6.2.4 Fixture
我们可以把 Fixture 看作夹心饼干外层的两片饼干,这两片饼干就是 setUp/tearDown,中间的奶油就是测试用例。除此之外,unittest 还提供了更大范围的 Fixture,如测试类和模块的 Fixture。
test_fixture.py
import unittest def setUpModule(): print("test module start >>>>>>>>>>>>>>") def tearDownModule(): print("test module end >>>>>>>>>>>>>>") class MyTest(unittest.TestCase): @classmethod def setUpClass(cls): print("test class start =======>") @classmethod def tearDownClass(cls): print("test class end =======>") def setUp(self): print("test case start -->") def tearDown(self): print("test case end -->") def test_case1(self): print("test case1") def test_case2(self): print("test case2") if __name__ == '__main__': unittest.main()
执行结果如下。
setUpModule/tearDownModule:在整个模块的开始与结束时被执行。
setUpClass/tearDownClass:在测试类的开始与结束时被执行。
setUp/tearDown:在测试用例(方法)的开始与结束时被执行。
需要注意的是,setUpClass/tearDownClass 为类方法,需要通过@classmethod 进行装饰。另外,方法的参数为 cls。其实,cls 与 self 并没有什么本质区别,都只表示方法的第一个参数。
6.3 编写 Web 自动化测试
我们学习 unittest 的目的是用它编写 Web 自动化测试用例,所以接下来会将 unittest 与 Selenium 结合起来进行 Web 自动化测试。
创建 test_baidu.py 文件。
test_baidu.py
import unittest from time import sleep from selenium import webdriver class TestBaidu(unittest.TestCase): @classmethod def setUpClass(cls): cls.driver = webdriver.Chrome() cls.base_url = "https://www.baidu.com" def baidu_search(self, search_key): self.driver.get(self.base_url) self.driver.find_element_by_id("kw").send_keys(search_key) self.driver.find_element_by_id("su").click() sleep(2) def test_search_key_selenium(self): search_key = "selenium" self.baidu_search(search_key) self.assertEqual(self.driver.title, search_key + "_百度搜索") def test_search_key_unttest(self): search_key = "unittest" self.baidu_search(search_key) self.assertEqual(self.driver.title, search_key + "_百度搜索") @classmethod def tearDownClass(cls): cls.driver.quit() if __name__ == '__main__': unittest.main()
对上面的代码不做过多介绍,都是以 unittest 创建测试类和方法的。方法中的代码是 Selenium 脚本。不过,这里的代码存在一些问题,我们来一 一改进。
首先,观察代码可以发现,两个测试用例中的步骤是一样的,唯一的区别是搜索的关键字和断言的结果不同。在第 5 章我们学习过模块化,所以这里把操作步骤封装成一个方法。
这里将百度首页的访问和搜索过程封装成一个 baidu_search()方法,并定义 search_key 参数为搜索关键字,根据接收的关键字执行不同内容的搜索。
这里的 baidu_search()方法会被当作测试用例执行吗?当然不会,因为根据 unittest 查找和执行测试用例的规则,它只会把以「test」开头的方法当作测试用例。
另一个值得讨论的问题是,测试用例的断言要不要写在封装的方法中?从前面的代码可以看出,测试的断言点是一样的。不过,笔者更倾向于把断言写在每一条测试用例里面,因为很多时候就算操作步骤是一样的,断言点也不完全一样。例如,登录功能的测试用例,虽然操作步骤相同,但是用户名为空和密码为空,这两条测试用例的提示信息可能显示在不同的位置,所以获取提示信息的定位方法是不一样的,因此断言也就不完全一样了。另外,从设计的角度来看,断言写在每一个测试用例中也会更加清晰。
我们发现每一条测试用例都要启动和关闭一次浏览器,这是非常耗时的,那么如何减少浏览器的启动和关闭次数呢?利用前面学过的 setUpClass/tearDownClass 可以解决这个问题。
test_baidu2.py
""" 使用 setUpModule/ tearDownModule 减少浏览器的启动 """ import unittest from time import sleep from selenium import webdriver class Browser: driver = None base_url = None def setUpModule(): Browser.driver = webdriver.Chrome() Browser.base_url = "https://www.baidu.com" def tearDownModule(): Browser.driver.quit() def baidu_search(search_key): Browser.driver.get(Browser.base_url) Browser.driver.find_element_by_id("kw").send_keys(search_key) Browser.driver.find_element_by_id("su").click() sleep(2) class TestBaidu(unittest.TestCase): def test_search_key_selenium(self): search_key = "selenium" baidu_search(search_key) self.assertEqual(Browser.driver.title, search_key + "_百度搜索") def test_search_key_unttest(self): search_key = "unittest" baidu_search(search_key) self.assertEqual(Browser.driver.title, search_key + "_百度搜索") class TestBaidu2(unittest.TestCase): def test_search_key_python(self): search_key = "python" baidu_search(search_key) self.assertEqual(Browser.driver.title, search_key + "_百度搜索") if __name__ == '__main__': unittest.main()
虽然我们将 driver 驱动定义为 cls.driver,但是在每个测试用例中使用时依然为 self.driver。当整个测试类中的所有测试用例都运行完成后,会调用 cls.driver.quit()关闭浏览器。当一个测试类中有多条测试用例时,这种方式将大大缩短测试用例的执行时间。