背景
我们之前刚简单聊完 语雀文档宕机 事件,没出几天,阿里又出故障,这次直接是全系产品不可用。从之前的香港机房故障导致服务中断 12 小时,语雀数据库故障导致服务故障 8 小时,这次原因尚未可知(不过看恢复时间,估计是某个基础应用 api 发布异常)。
这几次接连发生的事件真是“触目惊心”,不断地在透支用户对于阿里云技术的信任,身边很多架构师朋友都有计划将迁移阿里云的服务。其实应用出问题在所难免,AWS 云服务曾因 UPS 和人为错误中断,“腾讯云清远机房故障事件” 等等。因为人都会犯错,没有任何开发者敢保证一辈子不会出 bug,这就需要容灾架构来保驾护航,接二连三的出问题,很难让人信服系统的架构设计。今天我们针对系统应用自身保障,来简单聊聊单元测试及集成测试实践。
单元测试
单元测试在应用系统及软件开发中具有重要的地位,不仅是一种良好的编程实践,还有很多实际的重要性。单元测试可以让开发者在代码的早期阶段就发现和纠正错误。通过及早发现问题,可以减少在后期修复缺陷所需的时间和成本。同时,单元测试迫使开发者编写可测试的、模块化的代码。这有助于提高代码的可维护性和可读性,并促使开发者遵循良好的编码实践。当代码发生变化时,单元测试可以验证修改是否破坏了现有的功能。可以帮助回归已有的功能,这有助于确保代码的稳定性,尤其是在大型项目中,其中不同部分可能由不同的团队负责。
springboot 实践
对于 Java中的 Spring Boot 应用程序,通常使用 JUnit 作为主要的测试框架。
1.新增依赖
在 Maven 或 Gradle 项目中,确保你的 pom.xml 或 build.gradle 文件中包含了 JUnit 的依赖。Spring Boot 通常会自动添加所需的测试依赖。
代码语言:java复制<!-- Maven 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
2.编写测试类
创建一个与被测试类对应的测试类,并使用 @RunWith(SpringRunner.class) 注解来告诉 JUnit 使用 Spring 的测试框架。
代码语言:java复制import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class MyServiceTest {
// 测试代码
}
3.编写测试方法
在测试类中编写用于测试各个方法的测试方法。可以使用 @Test 注解标记测试方法。
代码语言:java复制import org.junit.Test;
public class MyServiceTest {
@Test
public void testSomeMethod() {
// 测试代码
}
}
4.使用Mockito进行 mock
对于一些依赖,你可能想要使用Mockito等框架创建模拟对象。这有助于隔离被测试类,并专注于测试特定的单元。
代码语言:java复制import org.mockito.Mock;
import static org.mockito.Mockito.when;
public class MyServiceTest {
@Mock
private SomeDependency someDependency;
@Test
public void testMethodWithDependency() {
when(someDependency.someMethod()).thenReturn("mocked result");
// 测试代码
}
}
通过以上步骤,我们就能够在 Spring Boot 应用程序中进行基本的单元测试。
测试覆盖率
单元测试我们通常也会有一些测试指标,不是简单的跑跑单测就完事了。通常会用行覆盖率和分支覆盖率这两个指标。测试中的行覆盖率和分支覆盖率是两个与代码覆盖度相关的概念,用于衡量在测试中覆盖源代码的程度。它们提供了关于测试覆盖度的度量,有助于评估测试的全面性。
行覆盖率
行覆盖率是指测试中执行的代码行占总代码行数的百分比。在单元测试或集成测试中,行覆盖率告诉你有多少代码是被测试覆盖的,即被至少执行一次的代码行数。
公式:
行覆盖率 = (被测试执行的行数/代码总行数) * 100%
例如,如果你的代码有100行,而测试覆盖了其中的80行,则行覆盖率为80%。
分支覆盖率
分支覆盖率是指在测试中覆盖了代码中所有可能的分支的百分比。分支通常是 if 语句或类似结构中的条件语句。分支覆盖率告诉你有多少代码分支是被测试覆盖的,即被至少执行一次的分支数。
公式:
分支覆盖率 = (被测试执行的分支数/代码总分支数) * 100%
这两种覆盖率的目标是尽可能接近100%,因为高覆盖率通常表示测试覆盖了大部分代码路径,从而提高了对潜在错误的检测能力。但是,覆盖率仅仅是测试质量的一个度量标准,不是唯一的评估指标。在设计测试用例时,还需要考虑测试的全面性、边界条件、异常处理等因素。
集成测试
应用系统集成测试是软件测试中更高的一种层次,它关注不同模块、组件或系统之间的交互和集成。目标是验证这些组件在一起能否按照预期工作。在 Spring Boot 应用程序中,集成测试通常涉及到测试整个应用程序的多个组件的协同工作,而不仅仅是单个组件的功能。在集成测试中,也可以使用模拟或模拟对象来代替真实的外部依赖,以确保测试的独立性和可重复性。集成测试可以涉及多个层次,包括数据库层、服务层、控制器层等。测试用例需要覆盖这些不同层次的集成点。
springboot 实践
1.配置注解
使用 @SpringBootTest 注解: 在 Spring Boot 中,可以使用 @SpringBootTest 注解来指定一个集成测试。这个注解会自动加载整个应用程序上下文,并为测试提供必要的配置。
代码语言:java复制import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.junit.runner.RunWith;
@RunWith(SpringRunner.class)
@SpringBootTest
public class IntegrationTest {
// 测试代码
}
2.mock 依赖
使用 Mockito 等框架,可以模拟或替代外部依赖,以确保测试的独立性。例如,可以使用 @MockBean注解模拟某个服务。
代码语言:java复制import org.springframework.boot.test.mock.mockito.MockBean;
@RunWith(SpringRunner.class)
@SpringBootTest
public class IntegrationTest {
@MockBean
private ExternalService externalService;
// 测试代码
}
3.数据库集成测试
如果应用程序使用数据库,可以使用嵌入式数据库(如H2)或者配置测试数据库来执行数据库层的集成测试。
代码语言:java复制@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureTestDatabase
public class DatabaseIntegrationTest {
// 测试代码
}
4.模拟外部调用
在测试中,可以使用RestTemplate或TestRestTemplate来模拟HTTP请求和测试RESTful服务。
代码语言:javascript复制import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class RestIntegrationTest {
@LocalServerPort
private int port;
private final TestRestTemplate restTemplate = new TestRestTemplate();
// 测试代码
}
集成测试在确保系统不同部分协同工作时发挥着关键作用,有助于捕获系统级别的问题和潜在的集成错误。在设计和执行集成测试时,需要考虑应用程序的整体架构和不同组件之间的依赖关系。
页面测试
页面测试通常是指对 Web 应用程序的用户界面进行测试的过程。它主要关注确保用户界面的各个部分(如页面布局、交互元素和表单等)在不同情况下能够正常工作。页面测试通常涉及模拟用户与页面进行交互,并验证页面在用户操作后的状态。能够同时测试到页面前后端的运行情况,通常使用 playwright 工具。
Playwright 是一个由 Microsoft 开发的开源工具,用于自动化浏览器测试、截图和执行页面交互。它支持多种浏览器,包括 Chromium、Firefox 和 WebKit。
实践
1.安装
使用以下命令安装 Playwright,你可以选择安装特定浏览器的版本:
代码语言:javascript复制npm install playwright
2.编写测试脚本:
创建一个测试脚本,使用 Playwright 提供的 API 进行页面测试。以下是一个简单的例子,测试一个网站是否能够成功加载:
代码语言:javascript复制const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://example.com');
const title = await page.title();
console.log('Page title:', title);
await browser.close();
})();
3.运行测试脚本:
在命令行中运行写好的测试脚本,Playwright 将启动浏览器实例,并执行测试脚本中定义的操作。
代码语言:javascript复制node your-test-script.js
4.执行页面交互:
使用 Playwright 提供的方法执行页面上的各种交互,例如点击按钮、填写表单等。以下是一个简单的例子,模拟在搜索框中输入关键字并点击搜索按钮:
代码语言:javascript复制const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://example.com');
await page.type('input[name="q"]', 'Playwright');
await page.click('input[type="submit"]');
// 等待搜索结果页面加载完成
await page.waitForSelector('.search-results');
await browser.close();
})();
5.断言和验证:
在测试中使用断言来验证页面的状态。你可以检查页面上的元素是否存在、是否包含特定的文本等。这有助于确保页面在交互后的预期行为。
代码语言:javascript复制const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://example.com');
const title = await page.title();
// 断言页面标题是否符合预期
assert.strictEqual(title, 'Example Domain');
await browser.close();
})();
通过使用 Playwright,你可以编写强大的页面测试,覆盖应用程序中各个交互点,确保用户界面的正常运行。 Playwright 的跨浏览器支持和丰富的 API 使其成为执行可靠页面测试的强大工具。
总结
当我们涉及软件开发时,测试是确保应用程序质量和可靠性的关键步骤之一。本文介绍了单元测试、集成测试和页面测试的基本概念和实践方法。
- 单元测试作为软件开发的基石,旨在验证代码的独立单元是否按照预期工作。通过早期检测和修复错误,单元测试提高了代码的质量、可维护性和可读性。在 Spring Boot 应用程序中,使用 JUnit 等测试框架可以轻松地编写和执行单元测试,确保代码的各个部分都能够正常运行。
- 集成测试将焦点放在不同模块、组件或系统之间的协同工作上,确保整个应用程序在集成时表现出预期的行为。在 Spring Boot 中,使用 @SpringBootTest 注解和各种模拟技术,可以测试应用程序的不同层次和组件之间的集成点。集成测试有助于捕获系统级别的问题,提高整个应用程序的稳定性。
- 页面测试关注用户界面的各个部分,确保页面在用户操作后能够正确显示和交互。使用工具如Playwright,可以自动化浏览器测试、执行页面交互和验证页面状态。页面测试是确保 Web 应用程序用户体验的重要一环,有助于捕获与页面交互相关的问题,提高应用程序的整体质量。
在软件开发过程中,这三种测试形式相辅相成,构建了一个全面的测试策略。通过单元测试,我们确保每个单独的部分都正确无误;通过集成测试,我们保证这些部分在一起协同工作;而页面测试则关注最终用户与应用程序的交互。这些测试实践有助于开发人员和团队在持续集成和交付的过程中确保代码的稳定性和可靠性,提高软件交付的质量。