受GeeCON会议期间@tkaczanowski演讲的启发,我决定仔细研究AssertJ库的自定义断言。
在我的“骰子”游戏中,我创建了一个“机会”,它是骰子的任意组合,其分数是所有骰子的总和。 这是相对简单的对象:
class Chance implements Scorable {@Overridepublic 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@Parameterspublic void chance(Collection<Dice> rolled, int scoreValue) {// arrangeCollection<Dice> rolled = dice(1, 1, 3, 3, 3);// actScore score = chance.getScore(rolled);// assertassertThat(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();@Testpublic 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;}@Overridepublic 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 {@Testpublic void twoFivesInARow() {NumberInARow numberInARow = new NumberInARow(5);Collection<Dice> dice = dice(1, 2, 3, 4, 5, 5);Score score = numberInARow.getScore(dice);// static import ScoreAssertionassertThat(score).hasValue(10).hasCombination(dice(5, 5)).hasReminder(dice(1, 2, 3, 4));}@Testpublic 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 {@Testpublic 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