Junit 笔记

第一个单元测试

IDE基于Eclipse,基于Java8

测试类一般在类名上添加Test。
执行单元测试:Package Explorer▶right-click▶Run As ▶ JUnit Test

注意Fails和Errors的区别。

@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");
    }
}

真实案例

面试问卷调查系统

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; }
}

其他文件和前面类似。

单元测试关注分支和潜在影响数据变化的代码。关注循环,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。类似的有34、37、42、44行。

两个分支的测试实例如下:

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);
   }
}

断言

断言有JUnit和Hamcrest, 前者的断言如下:


语句 描述


assertTrue([message,] boolean condition) 确认布尔条件为真。
assertFalse([message,] boolean condition) 确认布尔条件为假
assertEquals([message,] expected, actual) 确认返回等于期望,数组等比较的是地址而非内容。
assertEquals([message,] expected, actual, tolerance) 测试float或double相等。 tolerance为容许的误差。
assertNull([message,] object) 对象为null
assertNotNull([message,] object) 对象非null
assertSame([message,] expected, actual) 变量指向同一对象.
assertNotSame([message,] expected, actual) 变量指向不同对象

Hamcrest的断言:assertThat。导入import static org.hamcrest.CoreMatchers.*;

组织测试

AAA(arrange, act, and assert) 可能还有After。
测试行为与方法:关注行为而不是方法,测试要尽量基于业务。
测试代码和产品代码分开。私有内容也尽量要测试。
单个测试方法的测试内容要尽量单一。
测试文档化;名字要能看出目的。比如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可以忽略用例。

你可能感兴趣的:(Junit 笔记)