在 Java 中断言内部行为

为什么你的测试应该只验证可观察的行为,而不是实现细节

在 Java 中断言内部行为

文章插图
 
在本文中,我们将考虑我们的测试到底应该(不)验证什么以防止误报,以及为什么有时越少越好 。为了更好地理解这个主题,我们将仔细研究脆性测试和可观察行为的定义,以便我们能够检测设计不良的测试并使其抵抗重构 。
让我们开始吧!
 
当您的测试想知道太多时
回到过去,在我深入研究自动化测试这个主题之前,它已经发生在我身上很多次了 。究竟是什么?好吧,以防万一,我想确保我的测试验证了比必要的更多的东西 。我曾经相信我的测试包含的断言和类似的陈述越多,它们带来的价值就越大 。
虽然上述方法看起来很合理,但从长远来看,选择它会让开发人员的生活变得困难 。当我自己的测试让我不得不比我预期的更频繁地回到他们身边时,我很难发现这一点 。一个理由?事实证明,这些测试与实现细节有关,而不是可观察到的行为,因此,在重构时,即使功能仍然可以正常工作,它们也会失败 。
 
脆弱的测试? 可观察的行为? 实施细则?
在我们进一步讨论之前,让我们先定义一下这些神秘短语背后的含义,因为它们对于理解如何编写为我们的项目增加真正价值而不是不必要的包袱的良好测试至关重要 。
 
  • 它们无法承受重构,无论底层功能是否损坏,它们都会变红
换句话说,重构后的功能仍然可以产生正确的结果,但与此同时,如果他们检查某些东西是如何工作的,而不是检查可观察到的行为是什么,你的测试可能会失败 。
 
  • 要使一段代码成为系统可观察行为的一部分,它必须执行以下操作之一:
  • 公开帮助客户实现其目标之一的操作 。操作是一种执行计算或产生副作用或两者兼而有之的方法 。
  • 暴露一种可以帮助客户实现其目标之一的状态 。状态是系统的当前状态 。
  • 任何不做这两件事的代码都是实现细节 。
因此,当您在开发新功能时,请考虑调用您的代码的客户端的真正目标是什么(客户端代码期望从我们的解决方案中获得什么行为,或者您的功能应该涵盖哪些业务案例),然后忘记 暂时您想如何开发该功能(实现细节) 。
这种方法应该让您在可观察的行为和实现细节之间有一个更清晰的区别 。
 
案例研究:排行榜
让我们仔细看看下面用 JAVA 编写的示例:
  • 我们正在为一款游戏开发排行榜,我们称这款游戏为“Chase and Race”
  • 我们希望我们的排行榜根据得分返回最佳玩家
Player 类负责保存玩家的姓名和分数 。分数通过 Player#updateScore 函数更新 。
Leaderboard 类允许我们通过 Leaderboard#addPlayer 函数将玩家添加到排行榜的列表中,并通过 Leaderboard#getBestPlayer 检索游戏中最好的玩家 。
在 LeaderboardTesttest 类中,我们正在检查 Leaderboard#getBestPlayer 方法是否能够返回得分最高的玩家:
package chaseandrace.player;import java.util.ArrayList;import java.util.Comparator;import java.util.List;public class Leaderboard {List<Player> players;public Leaderboard() {players = new ArrayList<>();}public void addPlayer(Player player) {this.players.add(player);}public Player getBestPlayer() {return players.stream().max(Comparator.comparing(Player::getScore)).orElse(null);}}package chaseandrace.player;import static org.junit.jupiter.api.Assertions.assertEquals;import org.junit.jupiter.api.Test;public class LeaderboardTest {@Testvoid getPlayerWithHighestScore() {var playerOne = new Player("I don't know what I'm doing here");var playerTwo = new Player("Chase me");var playerThree = new Player("Okie Dokie");playerOne.updateScore(50);playerTwo.updateScore(90);playerThree.updateScore(85);var board = new Leaderboard();board.addPlayer(playerOne);board.addPlayer(playerTwo);board.addPlayer(playerThree);var bestPlayer = board.getBestPlayer();assertEquals(playerTwo, bestPlayer);assertEquals(bestPlayer, board.players.get(1));}}package chaseandrace.player;public class Player {private String name;private int score;public Player(String name) {this.name = name;}public int getScore() {return score;}public void updateScore(int points) {score += points;}public String getName() {return name;}public void setName(String name) {this.name = name;}}


推荐阅读