测试类型
什么是测试?正如维基百科所说:“测试或测试是通过将系统置于不同的情况下并跟踪其中可观察到的变化来研究系统底层过程的一种方法。” 换句话说,这是对我们系统在某些情况下能否正确运行的测试。好吧,让我们看看有哪些类型的测试:-
单元测试是其任务是单独测试系统的每个模块的测试。期望这些是系统的最小可分割部分,例如模块。
-
系统测试是一种高级测试,用于测试应用程序的较大部分或整个系统的运行情况。
-
回归测试是用于检查新功能或错误修复是否影响应用程序现有功能以及旧错误是否再次出现的测试。
-
功能测试是检查应用程序的一部分是否符合规范、用户故事等中规定的要求。
功能测试的类型:
- “白盒”测试,在了解系统内部实现的情况下,测试部分应用程序是否符合要求;
- “黑盒”测试用于在不了解系统内部实现的情况下部分应用程序是否符合要求。
- 性能测试是一种测试,旨在确定系统或其一部分在特定负载下运行的速度。
- 负载测试- 旨在检查系统在标准负载下的稳定性并找到应用程序正常工作的最大可能峰值的测试。
- 压力测试是一种测试,旨在检查非标准负载下应用程序的功能,并确定系统不会崩溃的最大可能峰值。
- 安全测试- 用于检查系统安全性的测试(免受黑客攻击、病毒、未经授权访问机密数据和其他生活乐趣)。
- 本地化测试是应用程序的本地化测试。
- 可用性测试是一种旨在检查用户的可用性、可理解性、吸引力和可学习性的测试。 这一切听起来不错,但在实践中它是如何运作的呢?很简单:使用迈克·科恩的测试金字塔: 这是金字塔的简化版本:现在它被分为更小的部分。但今天我们不会曲解并考虑最简单的选择。
-
单元- 在应用程序的各个层中使用的单元测试,测试应用程序的最小可分逻辑:例如,一个类,但最常见的是一个方法。这些测试通常尝试尽可能地与外部逻辑隔离,即创建应用程序的其余部分正在标准模式下工作的假象。
应该总是有很多这样的测试(比其他类型更多),因为它们测试小块并且非常轻量级,不会消耗大量资源(我所说的资源是指 RAM 和时间)。
-
集成——集成测试。它检查系统的较大部分,也就是说,它要么是多个逻辑部分(多个方法或类)的组合,要么是使用外部组件的正确性。这些测试通常比单元测试少,因为它们更重。
作为集成测试的示例,您可以考虑连接到数据库并检查与其一起使用的方法的正确执行。
-
UI - 检查用户界面操作的测试。它们影响应用程序各个级别的逻辑,这就是它们也被称为端到端的原因。通常,它们的数量要少得多,因为它们是最重量级的并且必须检查最必要的(使用的)路径。
在上图中,我们看到三角形不同部分的面积之比:在实际工作中这些测试的数量大致保持相同的比例。
今天我们将仔细研究最常用的测试 - 单元测试,因为所有有自尊心的 Java 开发人员都应该能够在基础级别上使用它们。
- 关于JavaRush和Habré上的代码覆盖率的材料;
- 基本测试理论。
- 我们正在编写我们的测试。
- 我们运行测试,无论它是否通过(我们看到一切都是红色的 - 不要惊慌:这就是它应该的样子)。
- 我们添加应该满足此测试的代码(运行测试)。
- 我们重构代码。
- 指定要测试的数据(夹具)。
- 使用被测代码(调用被测方法)。
- 检查结果并将其与预期结果进行比较。
assertEquals(Object expecteds, Object actuals)
— 检查传输的对象是否相等。assertTrue(boolean flag)
— 检查传递的值是否返回 true。assertFalse(boolean flag)
— 检查传递的值是否返回 false。assertNull(Object object)
– 检查对象是否为空。assertSame(Object firstObject, Object secondObject)
— 检查传递的值是否引用同一个对象。assertThat(T t, Matcher<T> matcher)
— 检查 t 是否满足匹配器中指定的条件。
单元测试的关键概念
测试覆盖率(Code Coverage)是应用程序测试质量的主要评估之一。这是测试覆盖的代码百分比 (0-100%)。在实践中,许多人追逐这个百分比,我不同意这一点,因为他们开始在不需要的地方添加测试。例如,我们的服务具有标准的 CRUD(创建/获取/更新/删除)操作,无需额外的逻辑。这些方法纯粹是中介,将工作委托给与存储库一起工作的层。在这种情况下,我们没有什么可以测试的:也许这个方法是否调用了道中的方法,但这并不严重。为了评估测试覆盖率,通常会使用额外的工具:JaCoCo、Cobertura、Clover、Emma等。要更详细地研究这个问题,请保留几篇合适的文章:测试阶段
测试分为三个阶段:测试环境
现在我们开始谈正事吧。有多种可用于 Java 的测试环境(框架)。其中最受欢迎的是 JUnit 和 TestNG。为了便于回顾,我们使用: JUnit 测试是包含在类中的方法,仅用于测试。类的名称通常与它正在测试的类的名称相同,最后带有+Test。例如,CarService→CarServiceTest。Maven 构建系统自动将此类类包含在测试区域中。事实上,这个类被称为测试类。让我们稍微回顾一下基本的注解: @Test - 定义这个方法作为测试方法(实际上,用这个注解标记的方法是一个单元测试)。 @Before - 标记每次测试之前将执行的方法。例如,填充类测试数据、读取输入数据等。 @After - 放置在每次测试后将调用的方法之上(清理数据、恢复默认值)。 @BeforeClass - 放置在方法上方 - 类似于@Before。但此方法仅在给定类的所有测试之前调用一次,因此必须是静态的。它用于执行更繁重的操作,例如提升测试数据库。 @AfterClass与 @BeforeClass 相反:对于给定的类执行一次,但在所有测试之后执行。例如,用于清理持久资源或断开与数据库的连接。 @Ignore - 注意到下面的方法被禁用,并且在整体运行测试时将被忽略。它用于不同的情况,例如,如果基本方法发生更改并且没有时间重新进行测试。在这种情况下,还建议添加描述 - @Ignore("Some description")。 @Test (expected = Exception.class) - 用于负面测试。这些测试检查方法在发生错误时的行为方式,即测试期望该方法抛出一些异常。这样的方法由 @Test 注释表示,但需要捕获错误。 @Test(timeout=100) - 检查该方法的执行时间是否不超过 100 毫秒。 @Mock - 在字段上使用类来将给定对象设置为模拟(这不是来自 Junit 库,而是来自 Mockito),如果我们需要它,我们将在特定情况下设置模拟的行为,直接在测试方法中。 @RunWith(MockitoJUnitRunner.class) - 该方法放置在类之上。这是用于在其中运行测试的按钮。Runner 可以不同:例如有以下几种:MockitoJUnitRunner、JUnitPlatform、SpringRunner 等)。在 JUnit 5 中,@RunWith 注释被更强大的 @ExtendWith 注释取代。让我们看一下比较结果的一些方法:assertThat(firstObject).isEqualTo(secondObject)
这里我讨论了基本方法,因为其余的都是上述方法的不同变体。
测试实践
现在让我们用一个具体的例子来看看上面的材料。我们将测试服务的方法——更新。我们不会考虑 dao 层,因为它是我们的默认层。让我们添加一个测试启动器:<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.2.2.RELEASE</version>
<scope>test</scope>
</dependency>
所以,服务类:
@Service
@RequiredArgsConstructor
public class RobotServiceImpl implements RobotService {
private final RobotDAO robotDAO;
@Override
public Robot update(Long id, Robot robot) {
Robot found = robotDAO.findById(id);
return robotDAO.update(Robot.builder()
.id(id)
.name(robot.getName() != null ? robot.getName() : found.getName())
.cpu(robot.getCpu() != null ? robot.getCpu() : found.getCpu())
.producer(robot.getProducer() != null ? robot.getProducer() : found.getProducer())
.build());
}
}
8 - 从数据库中提取更新的对象 9-14 - 通过构建器创建对象,如果传入的对象有字段 - 设置它,如果没有 - 保留数据库中的内容看看我们的测试:
@RunWith(MockitoJUnitRunner.class)
public class RobotServiceImplTest {
@Mock
private RobotDAO robotDAO;
private RobotServiceImpl robotService;
private static Robot testRobot;
@BeforeClass
public static void prepareTestData() {
testRobot = Robot
.builder()
.id(123L)
.name("testRobotMolly")
.cpu("Intel Core i7-9700K")
.producer("China")
.build();
}
@Before
public void init() {
robotService = new RobotServiceImpl(robotDAO);
}
1 — 我们的 Runner 4 — 通过替换模拟将服务与 dao 层隔离 11 — 为类设置一个测试实体(我们将用作测试仓鼠的实体) 22 — 设置我们将测试的服务对象
@Test
public void updateTest() {
when(robotDAO.findById(any(Long.class))).thenReturn(testRobot);
when(robotDAO.update(any(Robot.class))).then(returnsFirstArg());
Robot robotForUpdate = Robot
.builder()
.name("Vally")
.cpu("AMD Ryzen 7 2700X")
.build();
Robot resultRobot = robotService.update(123L, robotForUpdate);
assertNotNull(resultRobot);
assertSame(resultRobot.getId(),testRobot.getId());
assertThat(resultRobot.getName()).isEqualTo(robotForUpdate.getName());
assertTrue(resultRobot.getCpu().equals(robotForUpdate.getCpu()));
assertEquals(resultRobot.getProducer(),testRobot.getProducer());
}
在这里,我们看到测试明确分为三个部分: 3-9 - 设置装置 11 - 执行测试部分 13-17 - 检查结果 更多详细信息: 3-4 - 设置 moka dao 的行为 5 - 设置我们将在标准之上更新的实例 11 - 使用该方法并获取结果实例 13 - 检查它是否不为零 14 - 检查结果 ID 和指定的方法参数 15 - 检查名称是否已更新 16 - 查看 cpu 17 的结果 - 因为我们没有在更新实例字段中设置它,所以它应该保持不变,让我们检查一下。 让我们开始吧: 测试是绿色的,可以呼气了)) 所以,我们总结一下:测试提高了代码的质量,让开发过程更加灵活可靠。想象一下,当我们重新设计具有数百个类文件的软件时,我们需要花费多少精力。一旦我们为所有这些类编写了单元测试,我们就可以充满信心地进行重构。最重要的是,它可以帮助我们轻松发现开发过程中的错误。伙计们,这就是我今天的全部内容:点赞,写评论)))
GO TO FULL VERSION