@FunctionalInterface public interface Scoreable { int getScore(); }
import java.util.*; public class ScoreCollection { private List<Scoreable> scores = new ArrayList<>(); public void add(Scoreable scoreable) { scores.add(scoreable); } public int arithmeticMean() { int total = scores.stream().mapToInt(Scoreable::getScore).sum(); return total / scores.size(); } }
import static org.junit.Assert.*; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.hamcrest.CoreMatchers.*; public class ScoreCollectionTest { @Before public void setUp() throws Exception { } @After public void tearDown() throws Exception { } @Test public void answersArithmeticMeanOfTwoNumbers() { // Arrange ScoreCollection collection = new ScoreCollection(); collection.add(() -> 5); collection.add(() -> 7); // Act int actualResult = collection.arithmeticMean(); // Assert assertThat(actualResult, equalTo(6)); } @Test public void testArithmeticMean() { fail("Not yet implemented"); } }
真實案例java
面試問卷調查系統面試
public class Answer { private int i; private Question question; public Answer(Question question, int i) { this.question = question; this.i = i; } public Answer(Question characteristic, String matchingValue) { this.question = characteristic; this.i = characteristic.indexOf(matchingValue); } public String getQuestionText() { return question.getText(); } @Override public String toString() { return String.format("%s %s", question.getText(), question.getAnswerChoice(i)); } public boolean match(int expected) { return question.match(expected, i); } public boolean match(Answer otherAnswer) { return question.match(i, otherAnswer.i); } public Question getCharacteristic() { return question; } }
public enum Bool { False(0), True(1); public static final int FALSE = 0; public static final int TRUE = 1; private int value; private Bool(int value) { this.value = value; } public int getValue() { return value; } }
public class BooleanQuestion extends Question { public BooleanQuestion(int id, String text) { super(id, text, new String[] { "No", "Yes" }); } @Override public boolean match(int expected, int actual) { return expected == actual; } }
import java.util.*; public class Criteria implements Iterable<Criterion> { private List<Criterion> criteria = new ArrayList<>(); public void add(Criterion criterion) { criteria.add(criterion); } @Override public Iterator<Criterion> iterator() { return criteria.iterator(); } public int arithmeticMean() { return 0; } public double geometricMean(int[] numbers) { int totalProduct = Arrays.stream(numbers).reduce(1, (product, number) -> product * number); return Math.pow(totalProduct, 1.0 / numbers.length); } }
public class Criterion implements Scoreable { private Weight weight; private Answer answer; private int score; public Criterion(Answer answer, Weight weight) { this.answer = answer; this.weight = weight; } public Answer getAnswer() { return answer; } public Weight getWeight() { return weight; } public void setScore(int score) { this.score = score; } public int getScore() { return score; } }
public class PercentileQuestion extends Question { public PercentileQuestion(int id, String text, String[] answerChoices) { super(id, text, answerChoices); } @Override public boolean match(int expected, int actual) { return expected <= actual; } }
import java.util.*; import java.util.stream.*; public class Person { private List<Question> characteristics = new ArrayList<>(); public void add(Question characteristic) { characteristics.add(characteristic); } public List<Question> getCharacteristics() { return characteristics; } public List<Question> withCharacteristic(String questionPattern) { return characteristics.stream().filter(c -> c.getText().endsWith(questionPattern)).collect(Collectors.toList()); } } /* // your answer // their answer // how important is it to you me very organized you very organized very important me no you no irrelevant 0 little 1 10 50 mandatory 250 how much did other person satisfy? multiply scores take nth root .98 * .94 take sqrt (2 questions) (geometric mean) */
import java.util.*; public class Profile { private Map<String,Answer> answers = new HashMap<>(); private int score; private String name; public Profile(String name) { this.name = name; } public String getName() { return name; } public void add(Answer answer) { answers.put(answer.getQuestionText(), answer); } public boolean matches(Criteria criteria) { score = 0; boolean kill = false; boolean anyMatches = false; for (Criterion criterion: criteria) { Answer answer = answers.get( criterion.getAnswer().getQuestionText()); boolean match = criterion.getWeight() == Weight.DontCare || answer.match(criterion.getAnswer()); if (!match && criterion.getWeight() == Weight.MustMatch) { kill = true; } if (match) { score += criterion.getWeight().getValue(); } anyMatches |= match; } if (kill) return false; return anyMatches; } public int score() { return score; } }
public abstract class Question { private String text; private String[] answerChoices; private int id; public Question(int id, String text, String[] answerChoices) { this.id = id; this.text = text; this.answerChoices = answerChoices; } public String getText() { return text; } public String getAnswerChoice(int i) { return answerChoices[i]; } public boolean match(Answer answer) { return false; } abstract public boolean match(int expected, int actual); public int indexOf(String matchingAnswerChoice) { for (int i = 0; i < answerChoices.length; i++) if (answerChoices[i].equals(matchingAnswerChoice)) return i; return -1; } }
public enum Weight { MustMatch(Integer.MAX_VALUE), VeryImportant(5000), Important(1000), WouldPrefer(100), DontCare(0); private int value; Weight(int value) { this.value = value; } public int getValue() { return value; } }
其餘文件和前面相似。ide
單元測試關注分支和潛在影響數據變化的代碼。關注循環,if語句和複雜的條件句。若是值null或零?數據值如何影響的條件判斷。單元測試
好比上面的Profile類須要關注:測試
for (Criterion criterion: criteria) 須要考慮Criteria無對象、不少對象的狀況。
Answer answer = answers.get(criterion.getAnswer().getQuestionText()); 返回null的狀況。 criterion.getAnswer()返回null或 criterion.getAnswer().getQuestionText()。
30行match返回true/false。相似的有3四、3七、4二、44行。this
兩個分支的測試實例以下:spa
import org.junit.*; import static org.junit.Assert.*; public class ProfileTest { private Profile profile; private BooleanQuestion question; private Criteria criteria; @Before public void create() { profile = new Profile("Bull Hockey, Inc."); question = new BooleanQuestion(1, "Got bonuses?"); criteria = new Criteria(); } @Test public void matchAnswersFalseWhenMustMatchCriteriaNotMet() { profile.add(new Answer(question, Bool.FALSE)); criteria.add( new Criterion(new Answer(question, Bool.TRUE), Weight.MustMatch)); boolean matches = profile.matches(criteria); assertFalse(matches); } @Test public void matchAnswersTrueForAnyDontCareCriteria() { profile.add(new Answer(question, Bool.FALSE)); criteria.add( new Criterion(new Answer(question, Bool.TRUE), Weight.DontCare)); boolean matches = profile.matches(criteria); assertTrue(matches); } }
測試行爲與方法:關注行爲而不是方法,測試要儘可能基於業務。
測試代碼和產品代碼分開。私有內容也儘可能要測試。
單個測試方法的測試內容要儘可能單一。
測試文檔化;名字要能看出目的。好比doingSomeOperationGeneratesSomeResult、someResultOccursUnderSomeCondition、givenSomeContextWhenDoingSomeBehaviorThenSomeResultOccurs等
• Improve any local-variable names.
• Introduce meaningful constants.
• Prefer Hamcrest assertions.
• Split larger tests into smaller, more-focused tests.
• Move test clutter to helper methods and @Before methods.
另外有基於類的BeforeClass and AfterClass @Ignore能夠忽略用例。rest