1. 单元测试
1.1 测试对象。
1) OracleResource 是一个 Enterprise JavaBeans (EJB) 3.1 bean,用(REST) 来公开他的预言。
@Path("javafuture")
@Stateless
public class OracleResource {
@Inject
Instance<Consultant> company;
@Inject
Event<Result> eventListener;
@GET
@Produces(MediaType.TEXT_PLAIN)
public String predictFutureOfJava(){
checkConsultantAvailability();
Consultant consultant = getConsultant();
Result prediction = consultant.predictFutureOfJava();
eventListener.fire(prediction);
if(JAVA_IS_DEAD.equals(prediction)){
throw new IllegalStateException("Please perform a sanity / reality check");
}
return prediction.name();
}
void checkConsultantAvailability(){
if(company.isUnsatisfied()){
throw new IllegalStateException("No consultant to ask!");
}
}
Consultant getConsultant(){
for (Consultant consultant : company) {
return consultant;
}
return null;
}
}
2)
Consultant 是一个 Java 接口, Blogger是一个实现。
public class Blogger implements Consultant{
@Override
public Result predictFutureOfJava() {
return Result.JAVA_IS_DEAD;
}
}
3)
PredictionAudit 也是EJB 3.1 bean,保存所有成功的和失败的预言。
@Stateless
public class PredictionAudit {
@PersistenceContext
EntityManager em;
public void onSuccessfulPrediction(@Observes(during= TransactionPhase.AFTER_SUCCESS) Result result){
persistDecision(result,true);
}
public void onFailedPrediction(@Observes(during= TransactionPhase.AFTER_FAILURE) Result result){
persistDecision(result,false);
}
void persistDecision(Result result,boolean success) {
Prediction prediction = new Prediction(result,success);
em.persist(prediction);
}
public List<Prediction> allDecisions(){
return this.em.createNamedQuery(Prediction.findAll).getResultList();
}
}
4)
Prediction是JPA 2 实体。
@Entity
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@NamedQuery(name=Prediction.findAll,query="Select d from Prediction d")
public class Prediction {
public final static String PREFIX = "com.abien.testing.oracle.entity.Prediction.";
public final static String findAll = PREFIX + "findAll";
@Id
@GeneratedValue
@XmlTransient
private long id;
@Column(name="prediction_result")
@Enumerated(EnumType.STRING)
private Result result;
@Temporal(TemporalType.TIME)
private Date predictionDate;
private boolean success;
public Prediction() {
this.predictionDate = new Date();
}
public Prediction(Result result, boolean success) {
this();
this.result = result;
this.success = success;
}
//bookkeeping methods omitted
}
1.2 单元测试。
1) 选用Mockito来“模拟”各种难以实现的类、资源或服务,原理是它能够从类或接口创建“智能代理”(也称为模拟)
2) 要精确测试 PredictionAudit 类,必须“模拟”EntityManager。之后测试 EntityManager#persist 方法是否接收正确参数,但这里我们不测试是否真正保存 Prediction 实体。
import static org.mockito.Mockito.*;
public class PredictionAuditTest {
private PredictionAudit cut;
@Before
public void initializeDependencies(){
cut = new PredictionAudit();
cut.em = mock(EntityManager.class);
}
@Test
public void savingSuccessfulPrediction(){
final Result expectedResult = Result.BRIGHT;
Prediction expected = new Prediction(expectedResult, true);
this.cut.onSuccessfulPrediction(expectedResult);
verify(cut.em,times(1)).persist(expected);
}
@Test
public void savingRolledBackPrediction(){
final Result expectedResult = Result.BRIGHT;
Prediction expected = new Prediction(expectedResult, false);
this.cut.onFailedPrediction(expectedResult);
verify(cut.em,times(1)).persist(expected);
}
}
3)精确测试
OracleResource类,需要模拟 javax.enterprise.event.Event 和 javax.enterprise.inject.Instance。
public class OracleResourceTest {
private OracleResource cut;
@Before
public void initializeDependencies(){
this.cut = new OracleResource();
this.cut.company = mock(Instance.class);
this.cut.eventListener = mock(Event.class);
}
如果未找到 Consultant 实现,我们希望生成 IllegalStateException。
@Test(expected=IllegalStateException.class)
public void checkConsultantAvailabilityWithoutConsultant(){
when(this.cut.company.isUnsatisfied()).thenReturn(true);
this.cut.checkConsultantAvailability();
}
模拟 javax.enterprise.inject.Instance 返回的 Iterator:
Iterator mockIterator(Consultant consultant) {
Iterator iterator = mock(Iterator.class);
when(iterator.next()).thenReturn(consultant);
when(iterator.hasNext()).thenReturn(true);
return iterator;
}
通过上面的Iterator,枚举到Consultant 接口的 Blogger 实现则会返回 JAVA_IS_DEAD,这将引发 IllegalStateException。
@Test(expected=IllegalStateException.class)
public void unreasonablePrediction(){
Consultant consultant = new Blogger();
Iterator iterator = mockIterator(consultant);
when(this.cut.company.iterator()).thenReturn(iterator);
this.cut.predictFutureOfJava();
}
模拟 Event 实例,验证方法调用。
@Test
public void unreasonablePredictionFiresEvent(){
Consultant consultant = new Blogger();
Result expectedResultToFire = consultant.predictFutureOfJava();
Iterator iterator = mockIterator(consultant);
when(this.cut.company.iterator()).thenReturn(iterator);
try{
this.cut.predictFutureOfJava();
}catch(IllegalStateException e){}
verify(this.cut.eventListener,times(1)).fire(expectedResultToFire);
}
下面
PredictionArchiveResource 使用了直接注入的 PredictionAudit 类。
@Path("predictions")
@Stateless
public class PredictionArchiveResource {
@EJB
PredictionAudit audit;
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<Prediction> allPredictions(@DefaultValue("5") @QueryParam("max") int max) {
List<Prediction> allPredictions = audit.allPredictions();
if (allPredictions.size() <= max) {
return allPredictions;
} else {
return allPredictions.subList(0, max);
}
}
}
尽管 PredictionAudit 是一个类而不是一个接口,但也可以轻松地模拟它。为了测试返回列表的大小限制,对 PredictionAudit 类进行了全面模拟
public class PredictionArchiveResourceTest {
PredictionArchiveResource cut;
@Before
public void initialize(){
this.cut = new PredictionArchiveResource();
this.cut.audit = mock(PredictionAudit.class);
}
@Test
public void allDecisionsWithMaxLesserReturn() throws Exception {
int expectedSize = 2;
List<Prediction> prediction = createDecisions(expectedSize);
when(this.cut.audit.allPredictions()).thenReturn(prediction);
List<Prediction> allDecisions = this.cut.allPredictions(3);
assertThat(allDecisions.size(), is(expectedSize));
}
@Test
public void allDecisionsWithMaxGreaterReturn() throws Exception {
int max = 5;
int expected = 3;
List<Prediction> prediction = createDecisions(max);
when(this.cut.audit.allPredictions()).thenReturn(prediction);
List<Prediction> allDecisions = this.cut.allPredictions(expected);
assertThat(allDecisions.size(), is(expected));
}
@Test
public void allDecisionsWithMaxEqualReturn() throws Exception {
//obvious code omitted
}
List<Prediction> createDecisions(final int nr) {
return new ArrayList<Prediction>(){{
for (int i = 0; i < nr; i++) {
add(new Prediction(Result.BRIGHT, true));
}
}};
}
}
原帖(http://www.oracle.com/technetwork/cn/articles/java/unittesting-455385-zhs.html)
原帖代码:https://kenai.com/projects/javaee-patterns/sources/hg/show/TestingEJBAndCDI