跳转至

13.0 Testing

Types of Tests

在软件开发中,尤其是在 Agile 开发中,测试是关键活动。测试套件通常可作为需求文档的代理。有多种方式可以将测试分成不同类别。一种分类方式如下:

  1. Unit tests(单元测试):测试单个函数,确保其行为正确。
    • 例如,测试函数 calculate_total_price 是否返回正确结果。
  2. Integration test(集成测试):执行每个场景,确保模块正确集成。
    • 测试由多个函数组合而成的更大场景。例如,测试创建新用户是否成功,不仅检查创建函数输出,还检查数据库中是否存在该用户。
  3. System test(系统测试):集成真实硬件平台并测试其行为。
    • 在 Web 开发中,这指的是测试应用程序在浏览器中的行为。
  4. Acceptance test(验收测试):通过用户界面运行完整的用户场景。
    • 测试用户故事,验证用户是否能够完成特定任务。例如,测试用户是否能打开报告并编辑某一行。

V model 将不同类型的测试链接到开发过程的不同阶段。在本课程中,我们将重点关注 Unit testsSystem tests。对于小组项目,期望有良好的 Unit testsSelenium WebDriver 测试选择,不期望有 integrationacceptance tests

Desirable Properties of Unit Tests

Unit test 的目的是测试单个函数以确保其行为正确。为了覆盖所有边缘情况,通常每个函数需要 2 到 5 个单元测试。单元测试的属性包括:
1. It should be automated(应该是自动化的)– 运行它不应需要人工干预。
2. It should be repeatable(应该是可重复的)– 它不应更新持久状态,例如外部数据库。
3. It should run quickly(应该快速运行)– 单元测试会很多,所以每个测试都应快速。
4. It should pinpoint the failure(应该能精确定位失败)– 如果失败,应该能找到问题。错误消息应该提供诊断信息。
5. It should be limited in scope(范围应有限)– 对函数外部任何内容的更改不应影响该函数的测试是否通过。

为了解决最后一个问题,即隔离 system under test (SUT)(被测系统)与外部系统,我们使用 test doubles(测试替身):fakesstubsmocks

Test Doubles: Fakes, Mocks, Stubs

Test doubles 用于隔离被测系统与其外部依赖。

  • Fakes(假对象)是具有工作实现的对象,但与生产环境不同。
    • 例如,用一个包装 HashMap 的对象来模拟数据库,而不是使用实际数据库。Flask 中可以通过 sqlite:///:memory: 创建一个临时的非持久性内存数据库模拟。
  • Stubs(桩对象)是持有预定义数据以响应特定请求的对象,但不提供完整功能。
    • 例如,一个桩数据库系统只为每个学生提供固定的预定成绩集,用于测试 averageGrades 函数。另一个例子是为测试登录 GUI 提供一个桩,该桩只接受密码 'pw',无论用户是谁。
  • Mocks(模拟对象)的工作方式类似 stubs,但它们会记住接收到的调用,因此我们可以断言执行了正确的操作或发送了正确的消息。
    • 例如,使用门窗模拟来验证是否调用了 close() 方法,而无需与真实硬件交互。

在小组项目中,不期望自己创建 mocks, fakes, stubs

High-level Structure of Unit Tests (Python unittest)

在 Python 中,单元测试通常使用 unittest 模块。

  • Test fixtures(测试夹具):用于准备测试用例的方法,包括在每个测试运行前执行的 setUp 和在每个测试运行后执行的 tearDown。这些方法用于初始化和清理系统状态,如设置或清除测试数据库。
  • TestCase(测试用例):这是一个运行测试的标准类。通常通过继承 unittest.TestCase 类来编写测试。它指定了测试夹具和要执行的函数。
  • 测试方法:在 TestCase 子类中,每个测试都是一个方法,名称必须以 test 开头
  • Assertions(断言):断言描述了测试执行的检查。如果断言为真,测试通过。每个测试可以有多个断言,只有所有断言都为真,测试才通过。unittest 包提供了许多现有断言方法,例如 assertTrue, assertFalse, assertEqual, assertRaises 等。断言可以附带消息,提供失败案例的诊断信息。
  • TestSuite(测试套件):允许将测试用例分组以便一次运行。
  • TestRunner(测试运行器):负责运行测试和报告结果的类。

通常,只需要编写 TestCase 和测试方法,其余大部分是自动化的。可以从命令行运行测试:python -m unittest <filename>

为了测试 Flask 应用,我们不希望使用主数据库。可以创建一个模拟数据库。但应用结构可能需要重构以支持不同配置选项(例如测试配置和部署配置)。这通常涉及创建一个工厂方法 (create_app) 来根据不同配置创建应用实例。为了在多个应用实例(部署和测试)中注册相同的路由,可以使用 blueprintblueprint 可以被视为一个接口,声明了一组路由而无需创建应用实例。

Browser Testing (Selenium)

System tests 在 Web 中意味着测试应用程序在浏览器中的行为。Selenium 是一个可以用于自动化浏览器的程序,以运行测试用例。它有两种变体:

  • Selenium IDE:一个浏览器插件,可以记录与网站的交互并重播,以确认结果保持不变。
    • 优点:易于记录和重播,可用于快速原型测试和创建 bug 复现报告。
    • 缺点:维护困难(页面更改需要重新记录),难以应用测试夹具 (setUp/tearDown),需要运行中的浏览器实例。
    • 小组项目中不期望使用 SeleniumIDE
  • Selenium WebDriver:提供一组工具(如 Python 类)来编写系统测试脚本,与浏览器进行交互。
    • 优点:允许用代码编写浏览器测试,解决了 IDE 的维护和夹具问题。可以与浏览器进行交互(查找元素、执行动作如点击、输入文本、拖放),提取信息,并与标准断言库结合。
    • SetUptearDown 方法与单元测试类似,但需要启动服务器线程并初始化 webdriver。可以在无头模式 (headless mode) 下运行测试,无需创建浏览器窗口.
    • 小组项目中期望有良好的 Selenium WebDriver 测试选择。

Expectations for the Project

根据课程要求和源材料,在小组项目中,对于测试的期望包括:

  • 拥有良好选择的 Unit tests
  • 拥有良好选择的 Selenium WebDriver 测试。
  • 期望能够阅读、评判和修复基本的测试代码,特别是使用 Python unittest 包编写的代码 [User Query]。虽然源材料没有直接说不期望自己编写,但项目要求侧重于选择和理解。
  • 不期望有 integrationacceptance tests
  • 不期望使用 test-driven design
  • 不期望使用 SeleniumIDE
  • 不期望自己创建 mocks, fakes, stubs
  • 不期望使用 coverage testing

测试是小组项目中容易被忽视但很重要的部分,应确保编写足够的测试以满足要求。