受GeeCON会议期间@tkaczanowski演讲的启发,我决定仔细研究AssertJ库的自定义断言。
在我的“骰子”游戏中,我创建了一个“机会”,它是骰子的任意组合,其分数是所有骰子的总和。 这是相对简单的对象:
class Chance implements Scorable {
@Override
public Score getScore(Collection<Dice> dice) {
int sum = dice.stream()
.mapToInt(die -> die.getValue())
.sum();
return scoreBuilder(this)
.withValue(sum)
.withCombination(dice)
.build();
}
}
public interface Scorable {
Score getScore(Collection<Dice> dice);
}
在我的测试中,我想看看如何计算不同骰子组合的分数。 我从简单开始(实际上只有一个):
public class ChanceTest {
private Chance chance = new Chance();
@Test
@Parameters
public void chance(Collection<Dice> rolled, int scoreValue) {
// arrange
Collection<Dice> rolled = dice(1, 1, 3, 3, 3);
// act
Score score = chance.getScore(rolled);
// assert
assertThat(actualScore.getScorable()).isNotNull();
assertThat(actualScore.getValue()).isEqualTo(expectedScoreValue);
assertThat(actualScore.getReminder()).isEmpty();
assertThat(actualScore.getCombination()).isEqualTo(rolled);
}
}
测试中验证了单个概念(得分对象)。 为了提高分数验证的可读性和可重用性,我将创建一个自定义断言。 我希望我的断言像其他任何AssertJ断言一样被使用,如下所示:
public class ChanceTest {
private Chance chance = new Chance();
@Test
public void scoreIsSumOfAllDice() {
Collection<Dice> rolled = dice(1, 1, 3, 3, 3);
Score score = chance.getScore(rolled);
ScoreAssertion.assertThat(score)
.hasValue(11)
.hasNoReminder()
.hasCombination(rolled);
}
}
为了实现这一点,我需要创建一个从org.assertj.core.api.AbstractAssert
扩展的ScoreAssertion
类。 该类应具有公共的静态工厂方法和所有必需的验证方法。 最后,实现可能如下图所示。
class ScoreAssertion extends AbstractAssert<ScoreAssertion, Score> {
protected ScoreAssertion(Score actual) {
super(actual, ScoreAssertion.class);
}
public static ScoreAssertion assertThat(Score actual) {
return new ScoreAssertion(actual);
}
public ScoreAssertion hasEmptyReminder() {
isNotNull();
if (!actual.getReminder().isEmpty()) {
failWithMessage("Reminder is not empty");
}
return this;
}
public ScoreAssertion hasValue(int scoreValue) {
isNotNull();
if (actual.getValue() != scoreValue) {
failWithMessage("Expected score to be <%s>, but was <%s>",
scoreValue, actual.getValue());
}
return this;
}
public ScoreAssertion hasCombination(Collection<Dice> expected) {
Assertions.assertThat(actual.getCombination())
.containsExactly(expected.toArray(new Dice[0]));
return this;
}
}
创建这样的断言的动机是拥有更多可读性和可重用性的代码。 但是它要付出一些代价–需要创建更多代码。 在我的示例中,我知道我很快就会创建更多的Scorables
并且需要验证它们的评分算法,因此创建额外的代码是合理的。 增益将可见。 例如,我创建了一个NumberInARow
类,该类计算给定骰子组合中所有连续数字的分数。 分数是具有给定值的所有骰子的总和:
class NumberInARow implements Scorable {
private final int number;
public NumberInARow(int number) {
this.number = number;
}
@Override
public Score getScore(Collection<Dice> dice) {
Collection<Dice> combination = dice.stream()
.filter(value -> value.getValue() == number)
.collect(Collectors.toList());
int scoreValue = combination
.stream()
.mapToInt(value -> value.getValue())
.sum();
Collection<Dice> reminder = dice.stream()
.filter(value -> value.getValue() != number)
.collect(Collectors.toList());
return Score.scoreBuilder(this)
.withValue(scoreValue)
.withReminder(reminder)
.withCombination(combination)
.build();
}
}
我从连续检查两个5的测试开始,但是我已经错过了断言( hasReminder
,因此改进了ScoreAssertion
。 我继续通过其他测试更改断言,直到获得可以在测试中使用的非常完善的DSL:
public class NumberInARowTest {
@Test
public void twoFivesInARow() {
NumberInARow numberInARow = new NumberInARow(5);
Collection<Dice> dice = dice(1, 2, 3, 4, 5, 5);
Score score = numberInARow.getScore(dice);
// static import ScoreAssertion
assertThat(score)
.hasValue(10)
.hasCombination(dice(5, 5))
.hasReminder(dice(1, 2, 3, 4));
}
@Test
public void noNumbersInARow() {
NumberInARow numberInARow = new NumberInARow(5);
Collection<Dice> dice = dice(1, 2, 3);
Score score = numberInARow.getScore(dice);
assertThat(score)
.isZero()
.hasReminder(dice(1, 2, 3));
}
}
public class TwoPairsTest {
@Test
public void twoDistinctPairs() {
TwoPairs twoPairs = new TwoPairs();
Collection<Dice> dice = dice(2, 2, 3, 3, 1, 4);
Score score = twoPairs.getScore(dice);
assertThat(score)
.hasValue(10)
.hasCombination(dice(2, 2, 3, 3))
.hasReminder(dice(1, 4));
}
}
更改后的断言如下所示:
class ScoreAssertion extends AbstractAssert<ScoreAssertion, Score> {
protected ScoreAssertion(Score actual) {
super(actual, ScoreAssertion.class);
}
public static ScoreAssertion assertThat(Score actual) {
return new ScoreAssertion(actual);
}
public ScoreAssertion isZero() {
hasValue(Score.ZERO);
hasNoCombination();
return this;
}
public ScoreAssertion hasValue(int scoreValue) {
isNotNull();
if (actual.getValue() != scoreValue) {
failWithMessage("Expected score to be <%s>, but was <%s>",
scoreValue, actual.getValue());
}
return this;
}
public ScoreAssertion hasNoReminder() {
isNotNull();
if (!actual.getReminder().isEmpty()) {
failWithMessage("Reminder is not empty");
}
return this;
}
public ScoreAssertion hasReminder(Collection<Dice> expected) {
isNotNull();
Assertions.assertThat(actual.getReminder())
.containsExactly(expected.toArray(new Dice[0]));
return this;
}
private ScoreAssertion hasNoCombination() {
isNotNull();
if (!actual.getCombination().isEmpty()) {
failWithMessage("Combination is not empty");
}
return this;
}
public ScoreAssertion hasCombination(Collection<Dice> expected) {
isNotNull();
Assertions.assertThat(actual.getCombination())
.containsExactly(expected.toArray(new Dice[0]));
return this;
}
}
我真的很喜欢自定义AssertJ断言的想法。 在某些情况下,它们将提高我的代码的可读性。 另一方面,我很确定不能在所有情况下使用它们。 特别是在那些可重用机会很小的地方。 在这种情况下,可以使用带有分组断言的私有方法。
你有什么意见?
资源资源
- https://github.com/joel-costigliola/assertj-core/wiki/Creating-specific-assertions
- @tkaczanowski的断言演变
翻译自: https://www.javacodegeeks.com/2014/05/spice-up-your-test-code-with-custom-assertions.html