集成测试与系统测试详解
1. 集成测试与系统测试概述
集成测试是将各个单元组合成更大的集合,并对这些集合进行测试。当集成测试的范围扩展到覆盖整个程序时,就变成了系统测试。集成测试中最棘手的部分是选择将哪些单元集成到每个测试中,这样才能始终有一个可靠的代码基础,在引入更多代码时有所依托。
2. 确定集成顺序的步骤
为了确定集成测试的边界和顺序,可以按照以下步骤进行:
1. 使用纸张或图形程序,写出时间规划器项目中每个单元的名称或表示形式,并将每个类的方法分组。同一类中的单元存在明显的关系,可利用这一点。
2. 在应该直接交互的单元之间绘制箭头,从调用者指向被调用者。为了让箭头更清晰,可以移动类的位置。
3. 在每个类以及通过至少一条线连接的每对类周围画圈。
4. 继续在重叠的圈对周围画圈,直到只剩下三个圈。圈出其中一对,然后再用一个大圈将整个图形圈起来。
5. 这些圈告诉我们编写集成测试的顺序,圈越小,测试应该越早编写。
以下是一个简单的 mermaid 流程图来表示这个过程:
graph LR
A[列出单元并分组] --> B[绘制交互箭头]
B --> C[圈出类和类对]
C --> D[圈出重叠圈对]
D --> E[确定测试顺序]
3. 自动化测试工具
集成测试和单元测试的唯一真正区别在于,在集成测试中可以将被测试的代码分解成更小的有意义的块,而在单元测试中进一步分解代码就没有意义了。因此,用于自动化单元测试的工具也可以应用于集成测试,系统测试作为最高级别的集成测试,同样可以使用这些工具。
doctest 在集成测试中的作用相对有限,它的优势主要体现在开发过程的早期。在编写集成测试时,unittest 和 Nose 是更常用的工具。集成测试需要相互隔离,即使测试中包含多个相互作用的单元,了解测试外部没有影响因素也是有益的,因此 unittest 是编写自动化集成测试的不错选择,Nose 和 Mocker 与 unittest 配合使用效果更佳。
4. 为时间规划器编写集成测试
在有了时间规划器代码的集成图后,可以开始编写自动化集成测试。具体步骤如下:
1.
选择起始测试类
:从集成图中可以看出,statuses 和 activities 类位于很多箭头的末端,但不是任何箭头的起始端,这意味着它们在运行时不依赖外部的任何东西,因此是很好的起始测试类。可以先从 statuses 类开始,然后是 activities 类。
-
statuses 类的测试代码
:
from unittest import TestCase
from planner.data import statuses, task_error
from datetime import datetime
class statuses_integration_tests(TestCase):
def setUp(self):
self.A = statuses('A',
datetime(year=2008, month=7, day=15),
datetime(year=2009, month=5, day=2))
def test_repr(self):
self.assertEqual(repr(self.A), '<A 2008-07-15T00:00:00 2009‑05‑02T00:00:00>')
def test_equality(self):
self.assertEqual(self.A, self.A)
self.assertNotEqual(self.A, statuses('B',
datetime(year=2008, month=7, day=15),
datetime(year=2009, month=5, day=2)))
self.assertNotEqual(self.A, statuses('A',
datetime(year=2007, month=7, day=15),
datetime(year=2009, month=5, day=2)))
self.assertNotEqual(self.A, statuses('A',
datetime(year=2008, month=7, day=15),
datetime(year=2010, month=5, day=2)))
def test_overlap_begin(self):
status = statuses('status name',
datetime(year=2007, month=8, day=11),
datetime(year=2008, month=11, day=27))
self.assertTrue(status.overlaps(self.A))
def test_overlap_end(self):
status = statuses('status name',
datetime(year=2008, month=1, day=11),
datetime(year=2010, month=4, day=16))
self.assertTrue(status.overlaps(self.A))
def test_overlap_inner(self):
status = statuses('status name',
datetime(year=2007, month=10, day=11),
datetime(year=2010, month=1, day=27))
self.assertTrue(status.overlaps(self.A))
def test_overlap_outer(self):
status = statuses('status name',
datetime(year=2008, month=8, day=12),
datetime(year=2008, month=9, day=15))
self.assertTrue(status.overlaps(self.A))
def test_overlap_after(self):
status = statuses('status name',
datetime(year=2011, month=2, day=6),
datetime(year=2015, month=4, day=27))
self.assertFalse(status.overlaps(self.A))
- **activities 类的测试代码**:
from unittest import TestCase
from planner.data import activities, task_error
from datetime import datetime
class activities_integration_tests(TestCase):
def setUp(self):
self.A = activities('A',
datetime(year=2008, month=7, day=15),
datetime(year=2009, month=5, day=2))
def test_repr(self):
self.assertEqual(repr(self.A), '<A 2008-07-15T00:00:00 2009‑05‑02T00:00:00>')
def test_equality(self):
self.assertEqual(self.A, self.A)
self.assertNotEqual(self.A, activities('B',
datetime(year=2008, month=7, day=15),
datetime(year=2009, month=5, day=2)))
self.assertNotEqual(self.A, activities('A',
datetime(year=2007, month=7, day=15),
datetime(year=2009, month=5, day=2)))
self.assertNotEqual(self.A, activities('A',
datetime(year=2008, month=7, day=15),
datetime(year=2010, month=5, day=2)))
def test_overlap_begin(self):
activity = activities('activity name',
datetime(year=2007, month=8, day=11),
datetime(year=2008, month=11, day=27))
self.assertTrue(activity.overlaps(self.A))
self.assertTrue(activity.excludes(self.A))
def test_overlap_end(self):
activity = activities('activity name',
datetime(year=2008, month=1, day=11),
datetime(year=2010, month=4, day=16))
self.assertTrue(activity.overlaps(self.A))
self.assertTrue(activity.excludes(self.A))
def test_overlap_inner(self):
activity = activities('activity name',
datetime(year=2007, month=10, day=11),
datetime(year=2010, month=1, day=27))
self.assertTrue(activity.overlaps(self.A))
self.assertTrue(activity.excludes(self.A))
def test_overlap_outer(self):
activity = activities('activity name',
datetime(year=2008, month=8, day=12),
datetime(year=2008, month=9, day=15))
self.assertTrue(activity.overlaps(self.A))
self.assertTrue(activity.excludes(self.A))
def test_overlap_after(self):
activity = activities('activity name',
datetime(year=2011, month=2, day=6),
datetime(year=2015, month=4, day=27))
self.assertFalse(activity.overlaps(self.A))
5. 进一步的集成测试
接下来,需要考虑将
schedules
类与
statuses
或
activities
类进行集成。在此之前,先对
schedules
类自身的交互进行测试:
from unittest import TestCase
from mocker import MockerTestCase, MATCH, ANY
from planner.data import schedules, schedule_error
from datetime import datetime
class schedules_tests(MockerTestCase):
def setUp(self):
mocker = self.mocker
A = mocker.mock()
A.__eq__(MATCH(lambda x: x is A))
mocker.result(True)
mocker.count(0, None)
A.__eq__(MATCH(lambda x: x is not A))
mocker.result(False)
mocker.count(0, None)
A.overlaps(ANY)
mocker.result(False)
mocker.count(0, None)
A.begins
mocker.result(5)
mocker.count(0, None)
B = mocker.mock()
A.__eq__(MATCH(lambda x: x is B))
mocker.result(True)
mocker.count(0, None)
B.__eq__(MATCH(lambda x: x is not B))
mocker.result(False)
mocker.count(0, None)
B.overlaps(ANY)
mocker.result(False)
mocker.count(0, None)
B.begins
mocker.result(3)
mocker.count(0, None)
C = mocker.mock()
C.__eq__(MATCH(lambda x: x is C))
mocker.result(True)
mocker.count(0, None)
C.__eq__(MATCH(lambda x: x is not C))
mocker.result(False)
mocker.count(0, None)
C.overlaps(ANY)
mocker.result(False)
mocker.count(0, None)
C.begins
mocker.result(7)
mocker.count(0, None)
self.A = A
self.B = B
self.C = C
mocker.replay()
def test_equality(self):
sched1 = schedules()
sched2 = schedules()
self.assertEqual(sched1, sched2)
sched1.add(self.A)
sched1.add(self.B)
sched2.add(self.A)
sched2.add(self.B)
sched2.add(self.C)
self.assertNotEqual(sched1, sched2)
sched1.add(self.C)
self.assertEqual(sched1, sched2)
然后,进行
schedules
类与
statuses
类的集成测试:
from planner.data import schedules, statuses
from unittest import TestCase
from datetime import datetime, timedelta
class test_schedules_and_statuses(TestCase):
def setUp(self):
self.A = statuses('A',
datetime.now(),
datetime.now() + timedelta(minutes = 7))
self.B = statuses('B',
datetime.now() - timedelta(hours = 1),
datetime.now() + timedelta(hours = 1))
self.C = statuses('C',
datetime.now() + timedelta(minutes = 10),
datetime.now() + timedelta(hours = 1))
def test_usage_pattern(self):
sched = schedules()
sched.add(self.A)
sched.add(self.C)
self.assertTrue(self.A in sched)
self.assertTrue(self.C in sched)
self.assertFalse(self.B in sched)
sched.add(self.B)
self.assertTrue(self.B in sched)
self.assertEqual(sched, sched)
sched.remove(self.A)
self.assertFalse(self.A in sched)
self.assertTrue(self.B in sched)
self.assertTrue(self.C in sched)
sched.remove(self.B)
sched.remove(self.C)
self.assertFalse(self.B in sched)
self.assertFalse(self.C in sched)
以及
schedules
类与
activities
类的集成测试:
from planner.data import schedules, activities, schedule_error
from unittest import TestCase
from datetime import datetime, timedelta
class test_schedules_and_activities(TestCase):
def setUp(self):
self.A = activities('A',
datetime.now(),
datetime.now() + timedelta(minutes = 7))
self.B = activities('B',
datetime.now() - timedelta(hours = 1),
datetime.now() + timedelta(hours = 1))
self.C = activities('C',
datetime.now() + timedelta(minutes = 10),
datetime.now() + timedelta(hours = 1))
def test_usage_pattern(self):
sched = schedules()
sched.add(self.A)
sched.add(self.C)
self.assertTrue(self.A in sched)
self.assertTrue(self.C in sched)
self.assertFalse(self.B in sched)
self.assertRaises(schedule_error, sched.add, self.B)
self.assertFalse(self.B in sched)
self.assertEqual(sched, sched)
sched.remove(self.A)
self.assertFalse(self.A in sched)
self.assertFalse(self.B in sched)
self.assertTrue(self.C in sched)
sched.remove(self.C)
self.assertFalse(self.B in sched)
self.assertFalse(self.C in sched)
6. 多类集成测试
将
schedules
、
statuses
和
activities
类集成到同一个测试中:
from planner.data import schedules, statuses, activities, schedule_error
from unittest import TestCase
from datetime import datetime, timedelta
class test_schedules_activities_and_statuses(TestCase):
def setUp(self):
self.A = statuses('A',
datetime.now(),
datetime.now() + timedelta(minutes = 7))
self.B = statuses('B',
datetime.now() - timedelta(hours = 1),
datetime.now() + timedelta(hours = 1))
self.C = statuses('C',
datetime.now() + timedelta(minutes = 10),
datetime.now() + timedelta(hours = 1))
self.D = activities('D',
datetime.now(),
datetime.now() + timedelta(minutes = 7))
self.E = activities('E',
datetime.now() + timedelta(minutes=30),
datetime.now() + timedelta(hours=1))
self.F = activities('F',
datetime.now() - timedelta(minutes=20),
datetime.now() + timedelta(minutes=40))
def test_usage_pattern(self):
sched = schedules()
sched.add(self.A)
sched.add(self.B)
sched.add(self.C)
sched.add(self.D)
self.assertTrue(self.A in sched)
self.assertTrue(self.B in sched)
self.assertTrue(self.C in sched)
self.assertTrue(self.D in sched)
self.assertRaises(schedule_error, sched.add, self.F)
self.assertFalse(self.F in sched)
sched.add(self.E)
sched.remove(self.D)
self.assertTrue(self.E in sched)
self.assertFalse(self.D in sched)
self.assertRaises(schedule_error, sched.add, self.F)
self.assertFalse(self.F in sched)
sched.remove(self.E)
self.assertFalse(self.E in sched)
sched.add(self.F)
self.assertTrue(self.F in sched)
7. 文件类集成测试
在集成
file
类之前,先对其自身的交互进行测试:
from unittest import TestCase
from planner.persistence import file
from os import unlink
class test_file(TestCase):
def setUp(self):
storage = file('file_test.sqlite')
storage.store_object('tag1', 'A')
storage.store_object('tag2', 'B')
storage.store_object('tag1', 'C')
storage.store_object('tag1', 'D')
storage.store_object('tag3', 'E')
storage.store_object('tag3', 'F')
def tearDown(self):
unlink('file_test.sqlite')
def test_other_instance(self):
storage = file('file_test.sqlite')
self.assertEqual(set(storage.load_objects('tag1')),
set(['A', 'C', 'D']))
self.assertEqual(set(storage.load_objects('tag2')),
set(['B']))
self.assertEqual(set(storage.load_objects('tag3')),
set(['E', 'F']))
在运行这个测试时,发现了一个之前未发现的错误:数据库的更改没有提交到文件中,因此在存储它们的事务之外不可见。可以通过修改
file
类的
store_object
方法来修复这个问题:
def store_object(self, tag, object):
self.connection.execute('insert into objects values (?, ?)',
(tag, sqlite3.Binary(dumps(object))))
self.connection.commit()
8. 时间表与文件集成测试
进行
schedules
类与
file
类的集成测试:
from mocker import Mocker, MockerTestCase, ANY
from planner.data import schedules
from planner.persistence import file
from os import unlink
def unpickle_mocked_task(begins):
mocker = Mocker()
ret = mocker.mock()
ret.overlaps(ANY)
mocker.result(False)
mocker.count(0, None)
ret.begins
mocker.result(begins)
mocker.count(0, None)
mocker.replay()
return ret
unpickle_mocked_task.__safe_for_unpickling__ = True
class test_schedules_and_file(MockerTestCase):
def setUp(self):
mocker = self.mocker
A = mocker.mock()
A.overlaps(ANY)
mocker.result(False)
mocker.count(0, None)
A.begins
mocker.result(5)
mocker.count(0, None)
A.__reduce_ex__(ANY)
mocker.result((unpickle_mocked_task, (5,)))
mocker.count(0, None)
B = mocker.mock()
B.overlaps(ANY)
mocker.result(False)
mocker.count(0, None)
B.begins
mocker.result(3)
mocker.count(0, None)
B.__reduce_ex__(ANY)
mocker.result((unpickle_mocked_task, (3,)))
mocker.count(0, None)
C = mocker.mock()
C.overlaps(ANY)
mocker.result(False)
mocker.count(0, None)
C.begins
mocker.result(7)
mocker.count(0, None)
C.__reduce_ex__(ANY)
mocker.result((unpickle_mocked_task, (7,)))
mocker.count(0, None)
self.A = A
self.B = B
self.C = C
mocker.replay()
def tearDown(self):
try:
unlink('test_schedules_and_file.sqlite')
except OSError:
pass
def test_save_and_restore(self):
sched1 = schedules()
sched1.add(self.A)
sched1.add(self.B)
sched1.add(self.C)
store1 = file('test_schedules_and_file.sqlite')
sched1.store(store1)
del sched1
del store1
store2 = file('test_schedules_and_file.sqlite')
sched2 = schedules.load(store2)
self.assertEqual(set([x.begins for x in sched2.tasks]),
set([3, 5, 7]))
9. 系统测试
最后,进行涉及整个系统的测试,不使用任何模拟对象:
from planner.data import schedules, statuses, activities, schedule_error
from planner.persistence import file
from unittest import TestCase
from datetime import datetime, timedelta
from os import unlink
class test_system(TestCase):
def setUp(self):
self.A = statuses('A',
datetime.now(),
datetime.now() + timedelta(minutes = 7))
self.B = statuses('B',
datetime.now() - timedelta(hours = 1),
datetime.now() + timedelta(hours = 1))
self.C = statuses('C',
datetime.now() + timedelta(minutes = 10),
datetime.now() + timedelta(hours = 1))
self.D = activities('D',
datetime.now(),
datetime.now() + timedelta(minutes = 7))
self.E = activities('E',
datetime.now() + timedelta(minutes=30),
datetime.now() + timedelta(hours = 1))
self.F = activities('F',
datetime.now() - timedelta(minutes=20),
datetime.now() + timedelta(minutes=40))
def tearDown(self):
try:
unlink('test_system.sqlite')
except OSError:
pass
def test_usage_pattern(self):
sched1 = schedules()
sched1.add(self.A)
sched1.add(self.B)
sched1.add(self.C)
sched1.add(self.D)
sched1.add(self.E)
store1 = file('test_system.sqlite')
sched1.store(store1)
del store1
store2 = file('test_system.sqlite')
sched2 = schedules.load(store2)
self.assertEqual(sched1, sched2)
sched2.remove(self.D)
sched2.remove(self.E)
self.assertNotEqual(sched1, sched2)
sched2.add(self.F)
self.assertTrue(self.F in sched2)
self.assertFalse(self.F in sched1)
self.assertRaises(schedule_error, sched2.add, self.D)
self.assertRaises(schedule_error, sched2.add, self.E)
self.assertTrue(self.A in sched1)
self.assertTrue(self.B in sched1)
self.assertTrue(self.C in sched1)
self.assertTrue(self.D in sched1)
self.assertTrue(self.E in sched1)
self.assertFalse(self.F in sched1)
self.assertTrue(self.A in sched2)
self.assertTrue(self.B in sched2)
self.assertTrue(self.C in sched2)
self.assertFalse(self.D in sched2)
self.assertFalse(self.E in sched2)
self.assertTrue(self.F in sched2)
10. 测试代码分析
-
statuses_integration_tests类 :-
setUp方法创建一个status对象,每个测试都有自己独立的self.A版本,一个测试中的更改不会影响其他测试。 -
test_equality测试检查status对象与自身相等,以及名称、开始时间或结束时间不同时对象不相等。 -
一系列重叠测试检查
status对象是否能正确识别重叠情况。
-
-
activities_integration_tests类 :与statuses类的测试类似,创建一个activity对象并进行操作,确保不同名称、开始时间或结束时间的activity对象不相等,同时检查重叠和排除情况。 -
schedules_tests类 :使用模拟对象测试schedules类自身的交互,主要测试相等性比较。 -
test_schedules_and_statuses类 :使用真实的status对象测试schedules类与statuses类的交互,运行整个预期的使用模式。 -
test_schedules_and_activities类 :与test_schedules_and_statuses类类似,但由于activities类可以相互排除,当尝试添加重叠的activity时会抛出异常。 -
test_schedules_activities_and_statuses类 :不使用任何模拟对象,测试schedules、activities和statuses类的交互,确保它们能正常工作。 -
test_file类 :创建一个持久化数据库,在每个测试前存储数据,测试后删除数据库。测试从数据库加载数据是否符合预期,发现并修复了数据库更改未提交的问题。 -
test_schedules_and_file类 :测试schedules类与持久化文件的交互,创建并存储一个schedule,然后从文件中加载一个新的schedule,检查是否符合预期。
11. 测试代码冗余问题
很多测试代码可能看起来冗余,因为有些内容在不同测试中被反复检查。但这样做是有必要的,因为不同的测试场景可能会暴露出不同的问题,确保代码在各种情况下都能正常工作。通过逐步进行测试,可以清楚地知道新发现的错误源自何处,便于及时修复。
通过以上的集成测试和系统测试,可以全面地验证代码的正确性和稳定性,提高软件的质量。在实际开发中,按照这样的步骤进行测试可以有效地减少错误,确保系统的正常运行。
集成测试与系统测试详解(续)
12. 测试流程总结
为了更清晰地展示整个测试过程,我们可以用一个表格来总结各个测试阶段及其主要内容:
| 测试阶段 | 测试内容 | 主要代码类 | 关键操作 |
| — | — | — | — |
| 基础类测试 | 测试
statuses
和
activities
类自身的功能,如相等性、重叠判断等 |
statuses_integration_tests
、
activities_integration_tests
| 创建对象,进行各种操作和判断 |
|
schedules
类自身测试 | 测试
schedules
类自身的相等性比较 |
schedules_tests
| 使用模拟对象,创建
schedules
实例并比较 |
|
schedules
与其他类集成测试 | 分别测试
schedules
与
statuses
、
activities
的交互 |
test_schedules_and_statuses
、
test_schedules_and_activities
| 创建真实对象,进行添加、删除、检查等操作 |
| 多类集成测试 | 测试
schedules
、
statuses
和
activities
的共同交互 |
test_schedules_activities_and_statuses
| 创建多个对象,模拟实际使用场景 |
|
file
类自身测试 | 测试
file
类的存储和加载功能 |
test_file
| 创建数据库,存储和加载数据,检查结果 |
|
schedules
与
file
集成测试 | 测试
schedules
与
file
的交互,如保存和恢复 |
test_schedules_and_file
| 创建
schedules
实例,存储到文件,再从文件加载 |
| 系统测试 | 测试整个系统的功能,不使用模拟对象 |
test_system
| 创建所有相关对象,模拟完整的使用流程 |
以下是一个 mermaid 流程图来表示整个测试流程:
graph LR
A[基础类测试] --> B[schedules 类自身测试]
B --> C[schedules 与其他类集成测试]
C --> D[多类集成测试]
D --> E[file 类自身测试]
E --> F[schedules 与 file 集成测试]
F --> G[系统测试]
13. 自动化测试工具的选择与使用
在整个测试过程中,我们使用了多种自动化测试工具,如
unittest
、
mocker
等。这些工具的选择和使用有其特定的原因和场景:
-
unittest
:是 Python 内置的测试框架,适用于各种类型的测试,包括单元测试和集成测试。它提供了丰富的断言方法,如
assertEqual
、
assertTrue
等,方便我们进行测试结果的验证。在多个测试类中,我们都继承了
TestCase
类,使用
setUp
方法进行测试前的准备工作,使用各种断言方法进行测试。
-
mocker
:用于创建模拟对象,在测试
schedules
类自身交互时,我们使用
mocker
创建了多个模拟对象,模拟其他类的行为,以便专注于
schedules
类自身的功能测试。
14. 测试中的注意事项
-
隔离性
:集成测试需要相互隔离,确保一个测试的结果不会影响其他测试。例如,在
statuses_integration_tests和activities_integration_tests类中,setUp方法为每个测试创建独立的对象,保证测试的独立性。 -
错误处理
:在测试中,我们要考虑各种可能的错误情况,如
test_schedules_and_activities类中,当尝试添加重叠的activity时,会抛出schedule_error异常,我们使用assertRaises方法来验证异常的抛出。 -
资源管理
:对于涉及文件操作的测试,如
test_file和test_schedules_and_file类,要注意资源的管理,在测试结束后及时清理资源,避免资源泄漏。例如,在tearDown方法中删除创建的数据库文件。
15. 测试的价值和意义
通过逐步进行集成测试和系统测试,我们可以获得以下好处:
-
发现潜在问题
:如在
test_file
类的测试中,发现了数据库更改未提交的问题,及时进行了修复,避免了在实际使用中出现更严重的错误。
-
确保代码质量
:对各个类和模块进行全面的测试,确保代码在各种情况下都能正常工作,提高了软件的稳定性和可靠性。
-
便于维护和扩展
:清晰的测试代码和测试流程,使得在后续的开发中,当需要对代码进行修改或扩展时,可以快速验证修改的正确性,减少引入新错误的风险。
16. 总结与建议
- 总结 :集成测试和系统测试是软件开发过程中不可或缺的环节,通过合理的测试顺序和使用合适的测试工具,可以有效地发现和解决问题,提高软件质量。
-
建议
:
- 在进行集成测试前,先进行单元测试,确保每个单元的功能正确。
- 绘制集成图,帮助确定测试顺序,使测试更加有条理。
- 编写清晰、独立的测试代码,便于维护和扩展。
- 定期运行测试,及时发现和解决新出现的问题。
总之,集成测试和系统测试是保障软件质量的重要手段,通过遵循一定的方法和流程,我们可以有效地提高软件的可靠性和稳定性,为用户提供更好的使用体验。
集成测试与系统测试全面解析
超级会员免费看

被折叠的 条评论
为什么被折叠?



