1、創建Junit測試類java
1. 右擊test測試包,選擇New-->Other...web
2. 在窗口中找到Junit,選擇Junit Test Casespring
3. 輸入名稱(Name),命名規則通常建議採用:類名+Test。Browse...選擇要測試的類,這裏是StudentService。數據庫
4. 勾選要測試的方法數組
5. 生成後,效果以下:session
這裏import static是引入Assert類中靜態屬性或靜態方法的寫法。原來要Assert.fail(),如今只需直接fial()便可,即省略了Assert。app
其實不經過Junit新建嚮導來創建也能夠,隨便創建一個新類後,只需在方法上加入@Test註解便可。oop
2、核心——斷言性能
斷言是編寫測試用例的核心實現方式,即指望值是多少,測試的結果是多少,以此來判斷測試是否經過。單元測試
1. 斷言核心方法
assertArrayEquals(expecteds, actuals) |
查看兩個數組是否相等。 |
assertEquals(expected, actual) |
查看兩個對象是否相等。相似於字符串比較使用的equals()方法 |
assertNotEquals(first, second) |
查看兩個對象是否不相等。 |
assertNull(object) |
查看對象是否爲空。 |
assertNotNull(object) |
查看對象是否不爲空。 |
assertSame(expected, actual) |
查看兩個對象的引用是否相等。相似於使用「==」比較兩個對象 |
assertNotSame(unexpected, actual) |
查看兩個對象的引用是否不相等。相似於使用「!=」比較兩個對象 |
assertTrue(condition) |
查看運行結果是否爲true。 |
assertFalse(condition) |
查看運行結果是否爲false。 |
assertThat(actual, matcher) |
查看實際值是否知足指定的條件 |
fail() |
讓測試失敗 |
2. 示例
package test;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.util.Arrays;
import org.hamcrest.core.CombinableMatcher;
import org.junit.Test;
public class AssertTests {
@Test
public void testAssertArrayEquals() {
byte[] expected = "trial".getBytes();
byte[] actual = "trial".getBytes();
org.junit.Assert.assertArrayEquals("failure - byte arrays not same", expected, actual);
}
@Test
public void testAssertEquals() {
org.junit.Assert.assertEquals("failure - strings not same", 5l, 5l);
}
@Test
public void testAssertFalse() {
org.junit.Assert.assertFalse("failure - should be false", false);
}
@Test
public void testAssertNotNull() {
org.junit.Assert.assertNotNull("should not be null", new Object());
}
@Test
public void testAssertNotSame() {
org.junit.Assert.assertNotSame("should not be same Object", new Object(), new Object());
}
@Test
public void testAssertNull() {
org.junit.Assert.assertNull("should be null", null);
}
@Test
public void testAssertSame() {
Integer aNumber = Integer.valueOf(768);
org.junit.Assert.assertSame("should be same", aNumber, aNumber);
}
// JUnit Matchers assertThat
@Test
public void testAssertThatBothContainsString() {
org.junit.Assert.assertThat("albumen", both(containsString("a")).and(containsString("b")));
}
@Test
public void testAssertThathasItemsContainsString() {
org.junit.Assert.assertThat(Arrays.asList("one", "two", "three"), hasItems("one", "three"));
}
@Test
public void testAssertThatEveryItemContainsString() {
org.junit.Assert.assertThat(Arrays.asList(new String[] { "fun", "ban", "net" }), everyItem(containsString("n")));
}
// Core Hamcrest Matchers with assertThat
@Test
public void testAssertThatHamcrestCoreMatchers() {
assertThat("good", allOf(equalTo("good"), startsWith("good")));
assertThat("good", not(allOf(equalTo("bad"), equalTo("good"))));
assertThat("good", anyOf(equalTo("bad"), equalTo("good")));
assertThat(7, not(CombinableMatcher.<Integer> either(equalTo(3)).or(equalTo(4))));
assertThat(new Object(), not(sameInstance(new Object())));
}
}
3、核心——註解
1. 說明
@Before |
初始化方法 |
@After |
釋放資源 |
@Test |
測試方法,在這裏能夠測試指望異常和超時時間 |
@Ignore |
忽略的測試方法 |
@BeforeClass |
針對全部測試,只執行一次,且必須爲static void |
@AfterClass |
針對全部測試,只執行一次,且必須爲static void |
@RunWith |
指定測試類使用某個運行器 |
@Parameters |
指定測試類的測試數據集合 |
@Rule |
容許靈活添加或從新定義測試類中的每一個測試方法的行爲 |
@FixMethodOrder |
指定測試方法的執行順序 |
2. 執行順序
一個測試類單元測試的執行順序爲:
@BeforeClass –> @Before –> @Test –> @After –> @AfterClass
每個測試方法的調用順序爲:
@Before –> @Test –> @After
3. 示例
package test;
import static org.junit.Assert.*;
import org.junit.*;
public class JDemoTest {
@BeforeClass
public static void setUpBeforeClass() throws Exception {
System.out.println("in BeforeClass================");
}
@AfterClass
public static void tearDownAfterClass() throws Exception {
System.out.println("in AfterClass=================");
}
@Before
public void before() {
System.out.println("in Before");
}
@After
public void after() {
System.out.println("in After");
}
@Test(timeout = 10000)
public void testadd() {
JDemo a = new JDemo();
assertEquals(6, a.add(3, 3));
System.out.println("in Test ----Add");
}
@Test
public void testdivision() {
JDemo a = new JDemo();
assertEquals(3, a.division(6, 2));
System.out.println("in Test ----Division");
}
@Ignore
@Test
public void test_ignore() {
JDemo a = new JDemo();
assertEquals(6, a.add(1, 5));
System.out.println("in test_ignore");
}
@Test
public void teest_fail() {
fail();
}
}
class JDemo extends Thread {
int result;
public int add(int a, int b) {
try {
sleep(1000);
result = a + b;
} catch (InterruptedException e) {
}
return result;
}
public int division(int a, int b) {
return result = a / b;
}
}
執行結果:
in BeforeClass================
in Before
in Test ----Add
in After
in Before
in Test ----Division
in After
in AfterClass=================
圖中左上紅框中部分表示Junit運行結果,5個成功(1個忽略),1個錯誤,1個失敗。(注意錯誤和失敗不是一回事,錯誤說明代碼有錯誤,而失敗表示該測試方法測試失敗)
左下紅框中則表示出了各個測試方法的運行狀態,能夠看到成功、錯誤、失敗、失敗各自的圖標是不同的,還能夠看到運行時間。
右邊部分則是異常堆棧,可查看異常信息。
4、實例總結
1. 參數化測試
有時一個測試方法,不一樣的參數值會產生不一樣的結果,那麼咱們爲了測試全面,會把多個參數值都寫出 來並一一斷言測試,這樣有時不免費時費力,這是咱們即可以採用參數化測試來解決這個問題。參數化測試就比如把一個「輸入值,指望值」的集合傳入給測試方 法,達到一次性測試的目的。
package test;
import static org.junit.Assert.*;
import java.util.Arrays;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class FibonacciTest {
@Parameters(name = "{index}: fib({0})={1}")
public static Iterable<Object[]> data() {
return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 },
{ 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } });
}
private int input;
private int expected;
public FibonacciTest(int input, int expected) {
this.input = input;
this.expected = expected;
}
@Test
public void test() {
assertEquals(expected, Fibonacci.compute(input));
}
}
class Fibonacci {
public static int compute(int input) {
int result;
switch (input) {
case 0:
result = 0;
break;
case 1:
case 2:
result = 1;
break;
case 3:
result = 2;
break;
case 4:
result = 3;
break;
case 5:
result = 5;
break;
case 6:
result = 8;
break;
default:
result = 0;
}
return result;
}
}
@Parameters註解參數name,實際是測試方法名稱。因爲一個test()方法就完成了全部測試,那假如某一組測試數據有問題,那在Junit的結果頁面裏該如何呈現?所以採用name實際上就是區分每一個測試數據的測試方法名。以下圖:
2. 打包測試
一樣,若是一個項目中有不少個測試用例,若是一個個測試也很麻煩,所以打包測試就是一次性測試完成包中含有的全部測試用例。
package test;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses({ AssertTests.class, FibonacciTest.class, JDemoTest.class })
public class AllCaseTest {
}
這個功能也須要使用一個特殊的Runner ,須要向@RunWith註解傳遞一個參數Suite.class 。同時,咱們還須要另一個註解@Suite.SuiteClasses,來代表這個類是一個打包測試類。並將須要打包的類做爲參數傳遞給該註解就能夠 了。至於AllCaseTest隨便起一個類名,內容爲空既可。運行AllCaseTest類便可完成打包測試
3. 異常測試
異常測試與普通斷言測試不一樣,共有三種方法,其中最爲靈活的是第三種,能夠與斷言結合使用
第一種:
@Test(expected= IndexOutOfBoundsException.class)
public void empty() {
new ArrayList<Object>().get(0);
}
第二種:
@Test
public void testExceptionMessage() {
try {
new ArrayList<Object>().get(0);
fail("Expected an IndexOutOfBoundsException to be thrown");
} catch (IndexOutOfBoundsException anIndexOutOfBoundsException) {
assertThat(anIndexOutOfBoundsException.getMessage(), is("Index: 0, Size: 0"));
}
}
第三種:
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void shouldTestExceptionMessage() throws IndexOutOfBoundsException {
List<Object> list = new ArrayList<Object>();
thrown.expect(IndexOutOfBoundsException.class);
thrown.expectMessage("Index: 0, Size: 0");
list.get(0);
Assert.assertEquals(1, list.get(0));
}
在上述幾種方法中,不管是expected仍是expect都表示指望拋出的異常,假如某一方 法,當參數爲某一值時會拋出異常,那麼使用第一種方法就必須爲該參數單獨寫一個測試方法來測試異常,而沒法與其餘參數值一同寫在一個測試方法裏,因此顯得 累贅。第二種方法雖然解決這個問題,可是寫法不只繁瑣也不利於理解。而第三種犯法,不只能動態更改指望拋出的異常,與斷言語句結合的也很是好,所以推薦使 用該方法來測試異常。
4. 限時測試
有時爲了防止出現死循環或者方法執行過長(或檢查方法效率),而須要使用到限時測試。顧名思義,就是超出設定時間即視爲測試失敗。共有兩種寫法。
第一種:
@Test(timeout=1000)
public void testWithTimeout() {
...
}
第二種:
public class HasGlobalTimeout {
public static String log;
@Rule
public Timeout globalTimeout = new Timeout(10000); // 10 seconds max per method tested
@Test
public void testInfiniteLoop1() {
log += "ran1";
for (;;) {
}
}
@Test
public void testInfiniteLoop2() {
log += "ran2";
for (;;) {
}
}
}
其中,第二種方法與異常測試的第三種方法的寫法相似。也是推薦的寫法。
1、會用Spring測試套件的好處
在開發基於Spring的應用時,若是你還直接使用Junit進行單元測試,那你就錯過了Spring爲咱們所提供的饕餮大餐了。使用Junit直接進行單元測試有如下四大不足:
1)致使屢次Spring容器初始化問題
根據JUnit測試方法的調用流程,每執行一個測試方法都會建立一個測試用例的實例並調用 setUp()方法。因爲通常狀況下,咱們在setUp()方法中初始化Spring容器,這意味着若是測試用例有多少個測試方法,Spring容器就會 被重複初始化屢次。雖然初始化Spring容器的速度並不會太慢,但因爲可能會在Spring容器初始化時執行加載Hibernate映射文件等耗時的操 做,若是每執行一個測試方法都必須重複初始化Spring容器,則對測試性能的影響是不容忽視的;
使用Spring測試套件,Spring容器只會初始化一次
2)須要使用硬編碼方式手工獲取Bean
在測試用例類中咱們須要經過ctx.getBean()方法從Spirng容器中獲取須要測試的目標Bean,而且還要進行強制類型轉換的造型操做。這種乏味的操做迷漫在測試用例的代碼中,讓人以爲煩瑣不堪;
使用Spring測試套件,測試用例類中的屬性會被自動填充Spring容器的對應Bean,無須在手工設置Bean!
3)數據庫現場容易遭受破壞
測試方法對數據庫的更改操做會持久化到數據庫中。雖然是針對開發數據庫進行操做,但若是數據操做 的影響是持久的,可能會影響到後面的測試行爲。舉個例子,用戶在測試方法中插入一條ID爲1的User記錄,第一次運行不會有問題,第二次運行時,就會因 爲主鍵衝突而致使測試用例失敗。因此應該既可以完成功能邏輯檢查,又可以在測試完成後恢復現場,不會留下「後遺症」;
使用Spring測試套件,Spring會在你驗證後,自動回滾對數據庫的操做,保證數據庫的現場不被破壞,所以重複測試不會發生問題!
4)不方便對數據操做正確性進行檢查
假如咱們向登陸日誌表插入了一條成功登陸日誌,但是咱們卻沒有對t_login_log表中是否 確實添加了一條記錄進行檢查。通常狀況下,咱們多是打開數據庫,肉眼觀察是否插入了相應的記錄,但這嚴重違背了自動測試的原則。試想在測試包括成千上萬 個數據操做行爲的程序時,如何用肉眼進行檢查?
只要你繼承Spring的測試套件的用例類,你就能夠經過jdbcTemplate(或Dao等)在同一事務中訪問數據庫,查詢數據的變化,驗證操做的正確性!
Spring提供了一套擴展於Junit測試用例的測試套件,使用這套測試套件徹底解決了以上四個問題,讓咱們測試Spring的應用更加方便。這個測試套件主要由org.springframework.test包下的若干類組成,使用簡單快捷,方便上手。
2、使用方法
1)基本用法
package com.test;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:config/applicationContext-*.xml", "classpath:services/ext/service-*.xml" })
public class UserServiceTest {
@Resource
private IUserService userService;
@Test
public void testAddOpinion1() {
userService.downloadCount(1);
System.out.println(1);
}
@Test
public void testAddOpinion2() {
userService.downloadCount(2);
System.out.println(2);
}
}
@RunWith(SpringJUnit4ClassRunner.class) 用於配置spring中測試的環境
@ContextConfiguration(locations = { "classpath:config/applicationContext-*.xml", "classpath:services/ext/service-*.xml" })用於指定配置文件所在的位置
@Resource注入Spring容器Bean對象,注意與@Autowired區別
2)事務用法
package com.test;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Transactional;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:config/applicationContext-*.xml", "classpath:services/ext/service-*.xml" })
@Transactional
@TransactionConfiguration(transactionManager = "transactionManager")
//@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
public class UserServiceTest {
@Resource
private IUserService userService;
@Test
// @Transactional
public void testAddOpinion1() {
userService.downloadCount(1);
System.out.println(1);
}
@Test
@Rollback(false)
public void testAddOpinion2() {
userService.downloadCount(2);
System.out.println(2);
}
}
@TransactionConfiguration(transactionManager="transactionManager") 讀取Spring配置文件中名爲transactionManager的事務配置,defaultRollback爲事務回滾默認設置。該註解是可選的, 可以使用@Transactional與@Rollback配合完成事務管理。固然也可使用@Transactional與 @TransactionConfiguration配合。
@Transactional開啓事務。可放到類或方法上,類上做用於全部方法。
@Rollback事務回滾配置。只能放到方法上。
3)繼承AbstractTransactionalJUnit4SpringContextTests
package com.test;
import javax.annotation.Resource;
import org.junit.Test;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import org.springframework.test.context.transaction.TransactionConfiguration;
@ContextConfiguration(locations = { "classpath:config/applicationContext-*.xml", "classpath:services/ext/service-*.xml" })
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = false)
public class UserServiceTest extends AbstractTransactionalJUnit4SpringContextTests {
@Resource
private IUserService userService;
@Test
public void testAddOpinion1() {
userService.downloadCount(1);
System.out.println(1);
}
@Test
public void testAddOpinion2() {
userService.downloadCount(2);
System.out.println(2);
}
}
AbstractTransactionalJUnit4SpringContextTests: 這個類爲咱們解決了在web.xml中配置OpenSessionInview所解決的session生命週期延長的問題,因此要繼承這個類。該類已經在 類級別預先配置了好了事物支持,所以沒必要再配置@Transactional和@RunWith
4)繼承
package com.test;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import org.springframework.test.context.transaction.TransactionConfiguration;
@ContextConfiguration(locations = { "classpath:config/applicationContext-*.xml", "classpath:services/ext/service-*.xml" })
@TransactionConfiguration(transactionManager = "transactionManager")
public class BaseTestCase extends AbstractTransactionalJUnit4SpringContextTests {
}
package com.test;
import javax.annotation.Resource;
import org.junit.Test;
import org.springframework.test.annotation.Rollback;
public class UserServiceTest extends BaseTestCase {
@Resource
private IUserService userService;
@Test
public void testAddOpinion1() {
userService.downloadCount(1);
System.out.println(1);
}
@Test
@Rollback(false)
public void testAddOpinion2() {
userService.downloadCount(2);
System.out.println(2);
}
}
5)綜合
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@TransactionConfiguration
@Transactional
public class PersonDaoTransactionUnitTest extends AbstractTransactionalJUnit4SpringContextTests {
final Logger logger = LoggerFactory.getLogger(PersonDaoTransactionUnitTest.class);
protected static int SIZE = 2;
protected static Integer ID = new Integer(1);
protected static String FIRST_NAME = "Joe";
protected static String LAST_NAME = "Smith";
protected static String CHANGED_LAST_NAME = "Jackson";
@Autowired
protected PersonDao personDao = null;
/**
* Tests that the size and first record match what is expected before the transaction.
*/
@BeforeTransaction
public void beforeTransaction() {
testPerson(true, LAST_NAME);
}
/**
* Tests person table and changes the first records last name.
*/
@Test
public void testHibernateTemplate() throws SQLException {
assertNotNull("Person DAO is null.", personDao);
Collection<Person> lPersons = personDao.findPersons();
assertNotNull("Person list is null.", lPersons);
assertEquals("Number of persons should be " + SIZE + ".", SIZE, lPersons.size());
for (Person person : lPersons) {
assertNotNull("Person is null.", person);
if (ID.equals(person.getId())) {
assertEquals("Person first name should be " + FIRST_NAME + ".", FIRST_NAME, person.getFirstName());
assertEquals("Person last name should be " + LAST_NAME + ".", LAST_NAME, person.getLastName());
person.setLastName(CHANGED_LAST_NAME);
personDao.save(person);
}
}
}
/**
* Tests that the size and first record match what is expected after the transaction.
*/
@AfterTransaction
public void afterTransaction() {
testPerson(false, LAST_NAME);
}
/**
* Tests person table.
*/
protected void testPerson(boolean beforeTransaction, String matchLastName) {
List<Map<String, Object>> lPersonMaps = simpleJdbcTemplate.queryForList("SELECT * FROM PERSON");
assertNotNull("Person list is null.", lPersonMaps);
assertEquals("Number of persons should be " + SIZE + ".", SIZE, lPersonMaps.size());
Map<String, Object> hPerson = lPersonMaps.get(0);
logger.debug((beforeTransaction ? "Before" : "After") + " transaction. " + hPerson.toString());
Integer id = (Integer) hPerson.get("ID");
String firstName = (String) hPerson.get("FIRST_NAME");
String lastName = (String) hPerson.get("LAST_NAME");
if (ID.equals(id)) {
assertEquals("Person first name should be " + FIRST_NAME + ".", FIRST_NAME, firstName);
assertEquals("Person last name should be " + matchLastName + ".", matchLastName, lastName);
}
}
}
@BeforeTransaction在事務以前執行
@AfterTransaction在事務以後執行
@NotTransactional不開啓事務