1. 程式人生 > >JavaSE(八)JUnit單元測試、正則表示式

JavaSE(八)JUnit單元測試、正則表示式

JUnit單元測試、正則表示式

JUnit

JUnit測試

單元測試

1)單元測試是針對最小的功能單元編寫測試程式碼
2)Java程式最小的功能單元是方法
3)單元測試就是針對單個Java方法的測試

測試驅動開發TDD:Test-Driven Development
在這裡插入圖片描述
JUnit的設計:

1)TestCase:一個TestCase表示一個測試
2)TestSuite:一個TestSuite包含一組TestCase,表示一組測試
3)TestFixture:一個TestFixture表示一個測試環境
4)TestResult:用於收集測試結果
5)TestRunner:用於執行測試
6)TestListener:用於監聽測試過程,收集測試資料
7)Assert:用於斷言測試結果是否正確
版本 JUnit 3.x JUnit 4.x JUnit 5.x
JDK <1.5 >=1.5 >=1.8
class class MyTest extends TestCass{} class MyTest{} class MyTest{}
method public testAbc(){} @Test public abc(){} @Test public abc(){}

使用Assert斷言

1)assertEquals
2)assertArrayEquals
3)assertNull
4)assertTrue
5)assertFalse
6)assertNotEquals
7)assertNotNull

如何編寫單元測試

1)一個TestCase包含一組相關的測試方法
2)每個測試方法必須完全獨立
3)測試程式碼必須非常簡單
4)不能為測試程式碼再編寫測試
5)測試需要覆蓋各種輸入條件,特別是邊界條件

JUnit使用

使用Before和After

Test Fixture:初始化測試資源稱為Fixture
@Before和@After

1)在@Before方法中初始化測試資源
2)在@After方法中釋放測試資源

@BeforeClass和@AfterClass靜態方法

1)在執行所有@Test方法前執行@BeforeClass靜態方法
2)在執行所有@Test方法後執行@AfterClass靜態方法
靜態欄位的狀態會影響到所有的@Test

理解JUnit執行測試的生命週期

invokeBeforeClass(CalculatorTest.class); // @BeforeClass
for(Method testMethod : findTestMethods(CalculatorTest.class)){
	CalculatorTest test = new CalculatorTest(); // new 
	test.setUp(); // @Before
	testMethod.invoke(test); // @Test
	test.tearDown(); // @After
}
invokeAfterClass(CalculatorTest.class); // @AfterClass
@Before:初始化測試物件,例如:input = new FileInputStream();
@After:銷燬@Before建立的測試物件,例如:input.close();
@BeforeClass:初始化非常耗時的資源,例如:建立資料庫
@AfterClass:清理@BeforeClass建立的資源,例如:刪除資料庫

異常測試

測試異常可以使用@Test(excepted=Exception.class)

1)如果丟擲了指定型別的異常,測試成功
2)如果沒有丟擲指定型別的異常,或者丟擲的異常型別不對,測試失敗

對可能發生的每種型別的異常進行測試

引數化測試

Parameterized Test:引數化測試可以把測試資料統一管理
可以用測試資料對同一個測試方法反覆測試

@RunWith(Parameterized.class)
public class AbsTest{
	@Parameters
	public static Collection<?> data(){
		return Arrays.asList(new Object[][]{
			{0, 0}, {1, 1}, {-1, 1}
		});
	}
	int input;
	int expected;
	public AbsTest(int input, int expected){
		this.input = input;
		this.expected = expected;
	}
	@Test
	public void testAbs(){
		int r = Math.abs(this.input);
		assertEquals(this.expected, r);
	}
}

超時測試

可以為JUnit的單個測試設定超時:

@Test(timeout=1000)
public void testTimeCost(){}

超時測試不能取代效能測試和壓力測試

正則表示式

簡介

正則表示式(Regular Expression):一套規則,用來匹配字串
使用正則表示式可以快速判斷給定的字串是否符合匹配規則
Java內建正則表示式引擎java.util.regex

// 判斷年份是否是19xx年
//     /19\d\d/    或者   /19\d{2}/
// 在java中
System.out.println("1912".matches("19\\d\\d")); // true
System.out.println("1990".matches("19\\d{2}")); // true
// 編寫方法和測試用例
public class Is1900s {
	public static boolean is19xx(String s){
		if(s == null){
			return false;
		}
//		return s.matches("19\\d\\d");
		return s.matches("19\\d{2}");
	}
}
public class Is1900sTest {
	@Test
	public void testIs19xx() {
		assertTrue(Is1900s.is19xx("1900"));
		assertTrue(Is1900s.is19xx("1901"));
		assertTrue(Is1900s.is19xx("1911"));
		assertTrue(Is1900s.is19xx("1932"));
		assertTrue(Is1900s.is19xx("1949"));
		assertTrue(Is1900s.is19xx("1998"));
		assertTrue(Is1900s.is19xx("1999"));
		
		assertFalse(Is1900s.is19xx(null));
		assertFalse(Is1900s.is19xx(""));
		assertFalse(Is1900s.is19xx(" "));
		assertFalse(Is1900s.is19xx("19"));
		assertFalse(Is1900s.is19xx("190A"));
		assertFalse(Is1900s.is19xx("19001"));
		assertFalse(Is1900s.is19xx("1900s"));
		assertFalse(Is1900s.is19xx("2900"));
		assertFalse(Is1900s.is19xx("A900"));
	}
}

匹配規則

從左向右進行匹配

1)精確匹配
2)\d:0-9
3)\D:非\d
4)\s:空格,Tab鍵
5)\S:非\s
6)\w:a-z、A-Z、0-9、_
7)\W:非\w
8)*:任意字元
9)+:1到多,至少一個字元
10)?:0個或1個字元
11){n}:n個字元
12){m, n}:大於等於m個,小於等於n個字元
13){n, }:至少n個字元

練習

編寫正則表示式判斷使用者輸入QQ號是否合法(合法QQ號是5~10位數字)

// 先寫出正則  /^[1-9]\d{4, 9}/
@Test
public void testIsQQ(){
	assertTrue(isQQ("12345"));
	assertTrue(isQQ("123456"));
	assertTrue(isQQ("5642145"));
	assertTrue(isQQ("42341551"));
	assertTrue(isQQ("897900564"));
	assertTrue(isQQ("8979005643"));
	
	assertFalse(isQQ(null));
	assertFalse(isQQ(""));
	assertFalse(isQQ(" "));
	assertFalse(isQQ("12"));
	assertFalse(isQQ("123"));
	assertFalse(isQQ("1234"));
	assertFalse(isQQ("12345678910"));
	assertFalse(isQQ("02345910"));
	assertFalse(isQQ("a278910"));
	assertFalse(isQQ(" 278910"));
	assertFalse(isQQ("_278910"));
}

public boolean isQQ(String s){
	//   /^[1-9]\d{4, 9}/
	if(s == null){
		return false;
	}
	return s.matches("^[1-9]\\d{4,9}$");// {4,9}中不能寫空格例如{4, 9}
}

複雜匹配規則

1)^:字串開頭(區分[^]中的取反)
2)$:字串結束
3)[ABC]:字符集(ABC)內任意字元
4)[^A-F]:A到F範圍之外任意字元
5)|:或匹配,例如A|B意思是匹配A或者B字元

分組匹配規則

(…) 可以用來分組,例如“(\d{4})-(\d{1,2})-(\d{1,2})”
正則表示式分組可以通過Matcher物件快速提取子串

1)group(0)表示匹配的整個字串
2)group(1)表示第一個子串
3)group(2)表示第二個子串
。。。以此類推
Pattern pattern = Pattern.compile("^(\\d{3,4})\\-(\\d{6,8})$");
Matcher matcher = pattern.matcher("010-12345678");
if(matcher.matches()){
	String whole = matcher.group(0); // 010-12345678
	String areaCode = matcher.group(1); // 010  第一個子串
	String telNumber = matcher.group(2); // 12345678  第二個子串
}

練習

編寫正則,可以提取合法時間字串的時、分、秒,或者當前字串不合法是返回false

// 例如   18 : 43 : 56
// 正則表示式為: /^([0-1][0-9]|2[0-3]) \: ([0-5][0-9]) \: ([0-5][0-9])$/
@Test
public void testIsQQ(){
	assertTrue(isLegalTime("08 : 23 : 16"));
	assertTrue(isLegalTime("18 : 43 : 56"));
	assertTrue(isLegalTime("23 : 01 : 06"));
	
	assertFalse(isLegalTime(null));
	assertFalse(isLegalTime(""));
	assertFalse(isLegalTime(" "));
	assertFalse(isLegalTime("12 :"));
	assertFalse(isLegalTime("08 : 04"));
	assertFalse(isLegalTime("-1 : 04 : 25"));
	assertFalse(isLegalTime("38 : 23 : 16"));
	assertFalse(isLegalTime("18 : -2 : 06"));
	assertFalse(isLegalTime("16 : 223 : 16"));
	assertFalse(isLegalTime("18 : 12 : 156"));
	assertFalse(isLegalTime("21 : 43 : 6"));
}
public boolean isLegalTime(String s){
	//   /^([0-1][0-9]|2[0-3]) \: ([0-5][0-9]) \: ([0-5][0-9])$/
	if(s == null){
		return false;
	}
	return s.matches("^([0-1][0-9]|2[0-3]) \\: ([0-5][0-9]) \\: ([0-5][0-9])$");
}

非貪婪匹配

正則表示式預設使用貪婪匹配
使用?表示對某一規則進行非貪婪匹配
注意區分?含義,例如\d??
// 例如 匹配1230000時
// /^(\d+)(0*)$/    匹配結果子項1是  "1230000"   子項2是""
// /^(\d+?)(0*)$/   匹配結果子項1是  "123"    子項2是0000
// 再例如 匹配9999時
// /^(\d?)(9*)$/    匹配結果子項1是  "9"   子項2是"999"
// /^(\d??)(9*)$/    匹配結果子項1是  ""   子項2是"9999"

搜尋和替換

1)分割字串:String.split()
2)搜尋子串:Matcher.find()
3)替換字串:String.replaceAll()

練習

模板引擎是指,定義一個字串作為模板:
Hello, ${name}! You are learning l a n g ! {lang}! 其中,以 {key}表示的是變數,也就是將要被替換的內容
當傳入一個Map<String, Object>給模板後,需要把對應的key替換為Map的value。
例如,傳入Map為:
{
“name”: “Bob”,
“lang”: “Java”
}
然後, n a m e M a p &quot; B o b {name}被替換為Map對應的值&quot;Bob”, {lang}被替換為Map對應的值"Java",最終輸出的結果為:
Hello, Bob! You are learning Java!
請編寫一個簡單的模板引擎,利用正則表示式實現這個功能。

public class Main {
	public static void main(String[] args) {
		Map<String, String> map = new HashMap<>();
		map.put("name", "Bob");
		map.put("lang", "Java");
		String content = "Hello, ${name}! You are learning ${lang}!";
		System.out.println(templateFormat(content, map));
	}
	public static String templateFormat(String content, Map<String, String> map){
		for(Entry<String, String> entry : map.entrySet()){
			String reg = "\\$\\{" + entry.getKey() +"\\}";
			Pattern p = Pattern.compile(reg);
			Matcher m = p.matcher(content);
			content = m.replaceAll(entry.getValue());
		}
		return content;
	}
}
// 輸出 Hello, Bob! You are learning Java!