ALL is Well!

          敏捷是一條很長(zhǎng)的路,摸索著前進(jìn)著

            BlogJava :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            30 隨筆 :: 23 文章 :: 71 評(píng)論 :: 0 Trackbacks

          淺談TDD

          測(cè)試驅(qū)動(dòng)開發(fā),它是敏捷開發(fā)的最重要的部分。方法主要是先根據(jù)客戶的需求編寫測(cè)試程序,然后再編碼使其通過測(cè)試。在敏捷開發(fā)實(shí)施中,開發(fā)人員主要從兩個(gè)方面去理解測(cè)試驅(qū)動(dòng)開發(fā)。

          a)在測(cè)試的輔助下,快速實(shí)現(xiàn)客戶需求的功能。通過編寫測(cè)試用例,對(duì)客戶需求的功能進(jìn)行分解,并進(jìn)行系統(tǒng)設(shè)計(jì)。我們發(fā)現(xiàn)從使用角度對(duì)代碼的設(shè)計(jì)通常更符合后期開發(fā)的需求??蓽y(cè)試的要求,對(duì)代碼的內(nèi)聚性的提高和復(fù)用都非常有益。
          b)在測(cè)試的保護(hù)下,不斷重構(gòu)代碼,提高代碼的重用性,從而提高軟件產(chǎn)品的質(zhì)量。可見測(cè)試驅(qū)動(dòng)開發(fā)實(shí)施的好壞確實(shí)極大的影響軟件產(chǎn)品的質(zhì)量,貫穿了軟件開發(fā)的始終。
          在測(cè)試驅(qū)動(dòng)開發(fā)中,為了保證測(cè)試的穩(wěn)定性,被測(cè)代碼接口的穩(wěn)定性是非常重要的。否則,變化的成本就會(huì)急劇的上升。所以,自動(dòng)化測(cè)試將會(huì)要求您的設(shè)計(jì)依賴于接口,而不是具體的類。進(jìn)而推動(dòng)設(shè)計(jì)人員重視接口的設(shè)計(jì),體現(xiàn)系統(tǒng)的可擴(kuò)展性和抗變性。


          JUnit4的用法介紹

          Java 語(yǔ)言現(xiàn)在支持泛型、枚舉、可變長(zhǎng)度參數(shù)列表和注釋,這些特性為可重用的框架設(shè)計(jì)帶來(lái)了新的可能。
          JUnit4利用 Java 5 的新特性(尤其是注釋)的優(yōu)勢(shì),使得單元測(cè)試比起用最初的 JUnit 來(lái)說更加簡(jiǎn)單。

          測(cè)試方法 @Test

          以前所有版本的 JUnit 都使用命名約定和反射來(lái)定位測(cè)試。例如,下面的代碼測(cè)試 1 + 1 等于 2:

           

          import junit.framework.TestCase;
          public class AdditionTest extends TestCase {
          private int x = 1;
          private int y = 1;
          public void testAddition() {
          int z = x + y;
          assertEquals(
          2, z);
          }

          }


          而在 JUnit4 中,測(cè)試是由 @Test 注釋來(lái)識(shí)別的,如下所示:

          import org.junit.Test;
          import junit.framework.TestCase;
          public class AdditionTest {
          private int x = 1;
          private int y = 1;
          @Test
          public void testAddition() {
          int z = x + y;
          assertEquals(
          2, z);
          }

          }

          使用注釋來(lái)識(shí)別測(cè)試方法的優(yōu)點(diǎn)是不再需要將所有的方法命名為 testAddition()、testXXX()的形式等等。
          例如,下面的方法也可以工作:

          import org.junit.Test;
          import junit.framework.TestCase;
          public class AdditionTest {
          private int x = 1;
          private int y = 1;
          @Test
          public void addition() {
          int z = x + y;
          assertEquals(
          2, z);
          }

          }

          使用這種方法的好處是:
          a)允許我們遵循最適合的應(yīng)用程序的命名約定。
          我們可以將測(cè)試方法使用與被測(cè)試的類相同的名稱(由開發(fā)組規(guī)范約定)。例如,LoginAction.login() 由 LoginActionTest.login()方法測(cè)試、LoginAction.check()由LoginActionTest.check()方法測(cè)試等等。
          b)使用JUnit4后,測(cè)試用例類可以不繼承TestCase類,所以我們也就可以擴(kuò)展被測(cè)試類了。
          這種方法使得測(cè)試受保護(hù)的方法非常容易,我們只要將測(cè)試用例類繼承被測(cè)試類,就可以測(cè)試受保護(hù)方法了。

          @Before(SetUp)

          JUnit 3 測(cè)試運(yùn)行程序會(huì)在運(yùn)行每個(gè)測(cè)試之前自動(dòng)調(diào)用 setUp() 方法。該方法一般會(huì)初始化字段、準(zhǔn)備數(shù)據(jù)等等。例如下面的 setUp() 方法,用于設(shè)定要加載的路由文件:

           

          public void setUp() {
          // 加載此測(cè)試用例的servicerouting配置文件
          ServiceRouting.loadConfig("com/demo/servicerouting.conf");
          // 
          }



          在 JUnit4 中,我們?nèi)匀豢梢栽诿總€(gè)測(cè)試方法運(yùn)行之前初始化字段或準(zhǔn)備數(shù)據(jù)。然而,完成這些操作的方法不再需要叫做 setUp(),只要用 @Before 注釋來(lái)指示該方法即可,如下所示:

           

          @Before
          public void initialize() {
          // 加載此測(cè)試用例的servicerouting配置文件
          ServiceRouting.loadConfig("com/demo/servicerouting.conf");
          // 
          }



          JUnit4允許我們使用 @Before 來(lái)注釋多個(gè)方法,這些方法都在每個(gè)測(cè)試之前運(yùn)行:

           

          @Before
          public void initialize() {
          // 加載此測(cè)試用例的servicerouting配置文件
          ServiceRouting.loadConfig("com/demo/servicerouting.conf");
          // 
          }

          @Before
          public void prepareRetData() {
          //
          }



          @After(TearDown)

          清除方法與初始化方法類似。在 JUnit3 中,我們要將方法命名為 tearDown() 才可以實(shí)現(xiàn)清除方法,但在JUnit4中,只要給方法添加@After標(biāo)注即可。
          例如:

           

              @After
          public static void clearContext() {
          ActionContext.getContext().put(StrutsStatics.HTTP_REQUEST, 
          null);
          ActionContext.setContext(
          null);
          }



          測(cè)試方法結(jié)束后清除為此測(cè)試用例準(zhǔn)備的一些數(shù)據(jù)。
          與 @Before 一樣,也可以用 @After 來(lái)注釋多個(gè)清除方法,這些方法都在每個(gè)測(cè)試之后運(yùn)行。
          最后,我們不再需要顯式調(diào)用在超類中的初始化和清除方法,只要它們不被覆蓋,測(cè)試運(yùn)行程序?qū)⒏鶕?jù)需要自動(dòng)為您調(diào)用這些方法。
          超類中的 @Before 方法在子類中的 @Before 方法之前被調(diào)用(這反映了構(gòu)造函數(shù)調(diào)用的順序)。
          @After 方法以反方向運(yùn)行:子類中的方法在超類中的方法之前被調(diào)用。否則,多個(gè) @Before 或 @After 方法的相對(duì)順序就得不到保證。


          @Before和@After小結(jié)

          假設(shè)測(cè)試類中有如下方法定義:

           

          @Before
          public void init(){}
          @After
          public void destroy(){}

          則Before、After方法的執(zhí)行流程如圖所示:

          淺談TDD

          測(cè)試驅(qū)動(dòng)開發(fā),它是敏捷開發(fā)的最重要的部分。方法主要是先根據(jù)客戶的需求編寫測(cè)試程序,然后再編碼使其通過測(cè)試。在敏捷開發(fā)實(shí)施中,開發(fā)人員主要從兩個(gè)方面去理解測(cè)試驅(qū)動(dòng)開發(fā)。

          a)在測(cè)試的輔助下,快速實(shí)現(xiàn)客戶需求的功能。通過編寫測(cè)試用例,對(duì)客戶需求的功能進(jìn)行分解,并進(jìn)行系統(tǒng)設(shè)計(jì)。我們發(fā)現(xiàn)從使用角度對(duì)代碼的設(shè)計(jì)通常更符合后期開發(fā)的需求。可測(cè)試的要求,對(duì)代碼的內(nèi)聚性的提高和復(fù)用都非常有益。
          b)在測(cè)試的保護(hù)下,不斷重構(gòu)代碼,提高代碼的重用性,從而提高軟件產(chǎn)品的質(zhì)量??梢姕y(cè)試驅(qū)動(dòng)開發(fā)實(shí)施的好壞確實(shí)極大的影響軟件產(chǎn)品的質(zhì)量,貫穿了軟件開發(fā)的始終。
          在測(cè)試驅(qū)動(dòng)開發(fā)中,為了保證測(cè)試的穩(wěn)定性,被測(cè)代碼接口的穩(wěn)定性是非常重要的。否則,變化的成本就會(huì)急劇的上升。所以,自動(dòng)化測(cè)試將會(huì)要求您的設(shè)計(jì)依賴于接口,而不是具體的類。進(jìn)而推動(dòng)設(shè)計(jì)人員重視接口的設(shè)計(jì),體現(xiàn)系統(tǒng)的可擴(kuò)展性和抗變性。


          JUnit4的用法介紹

          Java 語(yǔ)言現(xiàn)在支持泛型、枚舉、可變長(zhǎng)度參數(shù)列表和注釋,這些特性為可重用的框架設(shè)計(jì)帶來(lái)了新的可能。
          JUnit4利用 Java 5 的新特性(尤其是注釋)的優(yōu)勢(shì),使得單元測(cè)試比起用最初的 JUnit 來(lái)說更加簡(jiǎn)單。

          測(cè)試方法 @Test

          以前所有版本的 JUnit 都使用命名約定和反射來(lái)定位測(cè)試。例如,下面的代碼測(cè)試 1 + 1 等于 2:

           

          import junit.framework.TestCase;
          public class AdditionTest extends TestCase {
          private int x = 1;
          private int y = 1;
          public void testAddition() {
          int z = x + y;
          assertEquals(
          2, z);
          }

          }


          而在 JUnit4 中,測(cè)試是由 @Test 注釋來(lái)識(shí)別的,如下所示:

           

          import org.junit.Test;
          import junit.framework.TestCase;
          public class AdditionTest {
          private int x = 1;
          private int y = 1;
          @Test
          public void testAddition() {
          int z = x + y;
          assertEquals(
          2, z);
          }

          }


          使用注釋來(lái)識(shí)別測(cè)試方法的優(yōu)點(diǎn)是不再需要將所有的方法命名為 testAddition()、testXXX()的形式等等。
          例如,下面的方法也可以工作:

           

          import org.junit.Test;
          import junit.framework.TestCase;
          public class AdditionTest {
          private int x = 1;
          private int y = 1;
          @Test
          public void addition() {
          int z = x + y;
          assertEquals(
          2, z);
          }

          }


          使用這種方法的好處是:
          a)允許我們遵循最適合的應(yīng)用程序的命名約定。
          我們可以將測(cè)試方法使用與被測(cè)試的類相同的名稱(由開發(fā)組規(guī)范約定)。例如,LoginAction.login() 由 LoginActionTest.login()方法測(cè)試、LoginAction.check()由LoginActionTest.check()方法測(cè)試等等。
          b)使用JUnit4后,測(cè)試用例類可以不繼承TestCase類,所以我們也就可以擴(kuò)展被測(cè)試類了。
          這種方法使得測(cè)試受保護(hù)的方法非常容易,我們只要將測(cè)試用例類繼承被測(cè)試類,就可以測(cè)試受保護(hù)方法了。

          @Before(SetUp)

          JUnit 3 測(cè)試運(yùn)行程序會(huì)在運(yùn)行每個(gè)測(cè)試之前自動(dòng)調(diào)用 setUp() 方法。該方法一般會(huì)初始化字段、準(zhǔn)備數(shù)據(jù)等等。例如下面的 setUp() 方法,用于設(shè)定要加載的路由文件:

           

          public void setUp() {
          // 加載此測(cè)試用例的servicerouting配置文件
          ServiceRouting.loadConfig("com/demo/servicerouting.conf");
          // 
          }


          在 JUnit4 中,我們?nèi)匀豢梢栽诿總€(gè)測(cè)試方法運(yùn)行之前初始化字段或準(zhǔn)備數(shù)據(jù)。然而,完成這些操作的方法不再需要叫做 setUp(),只要用 @Before 注釋來(lái)指示該方法即可,如下所示:

           

          @Before
          public void initialize() {
          // 加載此測(cè)試用例的servicerouting配置文件
          ServiceRouting.loadConfig("com/demo/servicerouting.conf");
          // 
          }


          JUnit4允許我們使用 @Before 來(lái)注釋多個(gè)方法,這些方法都在每個(gè)測(cè)試之前運(yùn)行:

           

          @Before
          public void initialize() {
          // 加載此測(cè)試用例的servicerouting配置文件
          ServiceRouting.loadConfig("com/demo/servicerouting.conf");
          // 
          }

          @Before
          public void prepareRetData() {
          //
          }



          @After(TearDown)

          清除方法與初始化方法類似。在 JUnit3 中,我們要將方法命名為 tearDown() 才可以實(shí)現(xiàn)清除方法,但在JUnit4中,只要給方法添加@After標(biāo)注即可。
          例如: 

             @After
          public static void clearContext() {
          ActionContext.getContext().put(StrutsStatics.HTTP_REQUEST, 
          null);
          ActionContext.setContext(
          null);
          }


          測(cè)試方法結(jié)束后清除為此測(cè)試用例準(zhǔn)備的一些數(shù)據(jù)。
          與 @Before 一樣,也可以用 @After 來(lái)注釋多個(gè)清除方法,這些方法都在每個(gè)測(cè)試之后運(yùn)行。
          最后,我們不再需要顯式調(diào)用在超類中的初始化和清除方法,只要它們不被覆蓋,測(cè)試運(yùn)行程序?qū)⒏鶕?jù)需要自動(dòng)為您調(diào)用這些方法。
          超類中的 @Before 方法在子類中的 @Before 方法之前被調(diào)用(這反映了構(gòu)造函數(shù)調(diào)用的順序)。
          @After 方法以反方向運(yùn)行:子類中的方法在超類中的方法之前被調(diào)用。否則,多個(gè) @Before 或 @After 方法的相對(duì)順序就得不到保證。


          @Before和@After小結(jié)

          假設(shè)測(cè)試類中有如下方法定義:

           

          @Before
          public void init(){}
          @After
          public void destroy(){}

          則Before、After方法的執(zhí)行流程如圖所示:


          這種方法有明顯的缺陷,如果要初始化的是數(shù)據(jù)庫(kù)的鏈接,或者是一個(gè)大的對(duì)象的話,而這些資源恰恰是整個(gè)測(cè)試用例類可以共用的,每次都去申請(qǐng),確實(shí)是種浪費(fèi)。所以JUnit4引入了@BeforeClass和@AfterClass。


          @BeforeClass和@AfterClass

          JUnit4 也引入了一個(gè) JUnit3 中沒有的新特性:類范圍的 setUp() 和 tearDown() 方法。任何用 @BeforeClass 注釋的方法都將在該類中的測(cè)試方法運(yùn)行之前剛好運(yùn)行一次,而任何用 @AfterClass 注釋的方法都將在該類中的所有測(cè)試都運(yùn)行之后剛好運(yùn)行一次。
          例如,假設(shè)類中的每個(gè)測(cè)試都使用一個(gè)數(shù)據(jù)庫(kù)連接、一個(gè)非常大的數(shù)據(jù)結(jié)構(gòu),或者申請(qǐng)其他一些資源。不要在每個(gè)測(cè)試之前都重新創(chuàng)建它,您可以創(chuàng)建它一次,用完后將其銷毀清除。該方法將使得有些測(cè)試案例運(yùn)行起來(lái)快得多。
          注意:被注釋為 BeforeClass和AfterClass 的方法必須為static方法。
          用法如下:

              @BeforeClass
          public static void classInit() {
          Map callRet 
          = new HashMap();
          List
          <ErrorCodeMessageBean> list = new ArrayList<ErrorCodeMessageBean>();
          list.add(createMsgBean(
          "TDE0001""第一個(gè)錯(cuò)誤消息"));
          list.add(createMsgBean(
          "TDP9999""格式化{0}{1}"));
          list.add(createMsgBean(
          "TDE1000~TDF0001""區(qū)間錯(cuò)誤消息"));
          list.add(createMsgBean(
          "TDG0001~""有下限的區(qū)間錯(cuò)誤消息"));
          list.add(createMsgBean(
          "~TDD0001""有上限的區(qū)間錯(cuò)誤消息"));
          list.add(createMsgBean(
          "~""默認(rèn)的消息"));
          callRet.put(ErrorCodeMessageBean.codeMsgBeanKey, list);
          ServiceCall.expectLastCallReturn(callRet);
          }

          @Test
          public void oneTestMethod() {
          //.
          }

          @AfterClass
          public static void classDestroy() {
          ServiceCall.expectLastCallReturn(
          null);
          }

          這個(gè)特定雖然很好,但是一定要小心對(duì)待這個(gè)特性。它有可能會(huì)違反測(cè)試的獨(dú)立性,并引入非預(yù)期的混亂。如果一個(gè)測(cè)試在某種程度上改變了 @BeforeClass 所初始化的一個(gè)對(duì)象,那么它有可能會(huì)影響其他測(cè)試的結(jié)果。也就是說,由BeforeClass申請(qǐng)或創(chuàng)建的資源,如果是整個(gè)測(cè)試用例類共享的,那么盡量不要讓其中任何一個(gè)測(cè)試方法改變那些共享的資源,這樣可能對(duì)其他測(cè)試方法有影響。它有可能在測(cè)試套件中引入順序依賴,并隱藏 bug。

          BeforeClass和AfterClass的執(zhí)行流程如下:

          這種方法有明顯的缺陷,如果要初始化的是數(shù)據(jù)庫(kù)的鏈接,或者是一個(gè)大的對(duì)象的話,而這些資源恰恰是整個(gè)測(cè)試用例類可以共用的,每次都去申請(qǐng),確實(shí)是種浪費(fèi)。所以JUnit4引入了@BeforeClass和@AfterClass。


          @BeforeClass和@AfterClass

          JUnit4 也引入了一個(gè) JUnit3 中沒有的新特性:類范圍的 setUp() 和 tearDown() 方法。任何用 @BeforeClass 注釋的方法都將在該類中的測(cè)試方法運(yùn)行之前剛好運(yùn)行一次,而任何用 @AfterClass 注釋的方法都將在該類中的所有測(cè)試都運(yùn)行之后剛好運(yùn)行一次。
          例如,假設(shè)類中的每個(gè)測(cè)試都使用一個(gè)數(shù)據(jù)庫(kù)連接、一個(gè)非常大的數(shù)據(jù)結(jié)構(gòu),或者申請(qǐng)其他一些資源。不要在每個(gè)測(cè)試之前都重新創(chuàng)建它,您可以創(chuàng)建它一次,用完后將其銷毀清除。該方法將使得有些測(cè)試案例運(yùn)行起來(lái)快得多。
          注意:被注釋為 BeforeClass和AfterClass 的方法必須為static方法。
          用法如下:

             @BeforeClass
          public static void classInit() {
          Map callRet 
          = new HashMap();
          List
          <ErrorCodeMessageBean> list = new ArrayList<ErrorCodeMessageBean>();
          list.add(createMsgBean(
          "TDE0001""第一個(gè)錯(cuò)誤消息"));
          list.add(createMsgBean(
          "TDP9999""格式化{0}{1}"));
          list.add(createMsgBean(
          "TDE1000~TDF0001""區(qū)間錯(cuò)誤消息"));
          list.add(createMsgBean(
          "TDG0001~""有下限的區(qū)間錯(cuò)誤消息"));
          list.add(createMsgBean(
          "~TDD0001""有上限的區(qū)間錯(cuò)誤消息"));
          list.add(createMsgBean(
          "~""默認(rèn)的消息"));
          callRet.put(ErrorCodeMessageBean.codeMsgBeanKey, list);
          ServiceCall.expectLastCallReturn(callRet);
          }

          @Test
          public void oneTestMethod() {
          //.
          }

          @AfterClass
          public static void classDestroy() {
          ServiceCall.expectLastCallReturn(
          null);
          }

          這個(gè)特定雖然很好,但是一定要小心對(duì)待這個(gè)特性。它有可能會(huì)違反測(cè)試的獨(dú)立性,并引入非預(yù)期的混亂。如果一個(gè)測(cè)試在某種程度上改變了 @BeforeClass 所初始化的一個(gè)對(duì)象,那么它有可能會(huì)影響其他測(cè)試的結(jié)果。也就是說,由BeforeClass申請(qǐng)或創(chuàng)建的資源,如果是整個(gè)測(cè)試用例類共享的,那么盡量不要讓其中任何一個(gè)測(cè)試方法改變那些共享的資源,這樣可能對(duì)其他測(cè)試方法有影響。它有可能在測(cè)試套件中引入順序依賴,并隱藏 bug。

          BeforeClass和AfterClass的執(zhí)行流程如下:




          測(cè)試異常@Test(expected=XXXException.class)

          異常測(cè)試是 JUnit4 中的最大改進(jìn)。舊式的異常測(cè)試是在拋出異常的代碼中放入 try 塊,然后在 try 塊的末尾加入一個(gè) fail() 語(yǔ)句。
          例如,該方法測(cè)試被零除拋出一個(gè) ArithmeticException:

           

          public void testDivisionByZero() {
          try {
          int n = 2 / 0;
          fail(
          "Divided by zero!");
          }

          catch (ArithmeticException success) {
          assertNotNull(success.getMessage());
          }

          }


          該方法不僅難看,而且寫起來(lái)也繁瑣。在 JUnit 4 中,我們現(xiàn)在可以編寫拋出異常的代碼,并使用注釋來(lái)聲明該異常是預(yù)期的:

           

              @Test(expected = BusinessException.class)
          public void testExecuteNameEmpty() throws Exception {
          BookList bListAction 
          = new BookList();
          bListAction.setName(
          "");
          bListAction.execute();
          }


          附被測(cè)試代碼(如果輸入name為empty,則拋出BusinessException,若name不為"liming",則拋出MessageException異常):

              @Override
          public String execute() throws Exception {
          if (StringUtils.isEmpty(name)) {
          throw new BusinessException("~""name cant't empty.");
          }

          if (!StringUtils.equals("liming", name.trim())) {
          throw new MessageException(name + " have no limits.");
          }

          Map ret 
          = serviceCall.call(JMockService.queryDtlInfo, null);
          orderId 
          = (String) ret.get("OrderId");
          dataList 
          = (List) ret.get("Data");
          return SUCCESS;
          }


          參數(shù)化測(cè)試

          為了保證單元測(cè)試的嚴(yán)謹(jǐn)性,我們經(jīng)常要模擬很多種輸入?yún)?shù),來(lái)確定我們的功能代碼是可以正常工作的,為此我們編寫大量的單元測(cè)試方法。可是這些測(cè)試方法都是大同小異:代碼結(jié)構(gòu)都是相同的,不同的僅僅是測(cè)試數(shù)據(jù)和期望輸出值。
          JUnit4 的參數(shù)化測(cè)試方法給我們提供了更好的方法,將測(cè)試方法中相同的代碼結(jié)構(gòu)提取出來(lái),提高代碼的重用度,減少?gòu)?fù)制粘貼代碼的痛苦。
          例如下面的功能代碼(格式化字符串,將駝峰規(guī)則的字符串以"_"分隔):

           

          public class WordDealUtil {
          public static String wordFormat4DB(String name) {
          if (name == null{
          return null;
          }

          Pattern p 
          = Pattern.compile("[A-Z]");
          Matcher m 
          = p.matcher(name);
          StringBuffer sb 
          = new StringBuffer();
          while (m.find()) {
          if (m.start() != 0{
          m.appendReplacement(sb, (
          "_" + m.group()).toLowerCase());
          }

          }

          return m.appendTail(sb).toString().toLowerCase();
          }

          }


          沒有使用參數(shù)化的測(cè)試用例代碼:

          public class WordDealUtilTest {
          /**
          * 測(cè)試 null 時(shí)的處理情況
          */

          @Test
          public void wordFormat4DBNull() {
          String target 
          = null;
          String result 
          = WordDealUtil.wordFormat4DB(target);
          assertNull(result);
          }

          /**
          * 測(cè)試空字符串的處理情況
          */

          @Test
          public void wordFormat4DBEmpty() {
          String target 
          = "";
          String result 
          = WordDealUtil.wordFormat4DB(target);
          assertEquals(
          "", result);
          }

          /**
          * 測(cè)試當(dāng)首字母大寫時(shí)的情況
          */

          @Test
          public void wordFormat4DBegin() {
          String target 
          = "EmployeeInfo";
          String result 
          = WordDealUtil.wordFormat4DB(target);
          assertEquals(
          "employee_info", result);
          }

          /**
          * 測(cè)試當(dāng)尾字母為大寫時(shí)的情況
          */

          @Test
          public void wordFormat4DBEnd() {
          String target 
          = "employeeInfoA";
          String result 
          = WordDealUtil.wordFormat4DB(target);
          assertEquals(
          "employee_info_a", result);
          }

          /**
          * 測(cè)試多個(gè)相連字母大寫時(shí)的情況
          */

          @Test
          public void wordFormat4DBTogether() {
          String target 
          = "employeeAInfo";
          String result 
          = WordDealUtil.wordFormat4DB(target);
          assertEquals(
          "employee_a_info", result);
          }

          }

          看以上測(cè)試用例代碼,結(jié)構(gòu)相似,只是輸入值與期望輸出不同而已,但我們要拷貝很多代碼。
          使用參數(shù)化的測(cè)試用例代碼:

          @SuppressWarnings("unchecked")
          @RunWith(Parameterized.
          class)
          public class WordDealUtilTestWithParam {
          private String expected;
          private String target;
          @Parameters
          public static Collection words() {
          return Arrays.asList(new Object[][] {
          "employee_info""employeeInfo" },  // 測(cè)試一般的處理情況
          nullnull },                         // 測(cè)試 null 時(shí)的處理情況
          """" },                             // 測(cè)試空字符串時(shí)的處理情況
          "employee_info""EmployeeInfo" },    // 測(cè)試當(dāng)首字母大寫時(shí)的情況
          "employee_info_a""employeeInfoA" }// 測(cè)試當(dāng)尾字母為大寫時(shí)的情況
          "employee_a_info""employeeAInfo" }  // 測(cè)試多個(gè)相連字母大寫時(shí)的情況
          }
          );
          }

          /**
          * 參數(shù)化測(cè)試必須的構(gòu)造函數(shù)
          @param expected     期望的測(cè)試結(jié)果,對(duì)應(yīng)參數(shù)集中的第一個(gè)參數(shù)
          @param target     測(cè)試數(shù)據(jù),對(duì)應(yīng)參數(shù)集中的第二個(gè)參數(shù)
          */

          public WordDealUtilTestWithParam(String expected, String target) {
          this.expected = expected;
          this.target = target;
          }

          /**
          * 測(cè)試將 Java 對(duì)象名稱到數(shù)據(jù)庫(kù)名稱的轉(zhuǎn)換
          */

          @Test
          public void wordFormat4DB() {
          Assert.assertEquals(expected, WordDealUtil.wordFormat4DB(target));
          }

          }

          很明顯,代碼簡(jiǎn)單且很清晰了。在靜態(tài)方法 words 中,我們使用二維數(shù)組來(lái)構(gòu)建測(cè)試所需要的參數(shù)列表,其中每個(gè)數(shù)組中的元素的放置順序并沒有什么要求,只要和構(gòu)造函數(shù)中的順序保持一致就可以了?,F(xiàn)在如果再增加一種測(cè)試情況,只需要在靜態(tài)方法 words 中添加相應(yīng)的數(shù)組即可,不再需要復(fù)制粘貼出一個(gè)新的方法出來(lái)了。
          這種參數(shù)化的測(cè)試用例寫法,很適用于一些共用的功能方法。



          測(cè)試異常@Test(expected=XXXException.class)

          異常測(cè)試是 JUnit4 中的最大改進(jìn)。舊式的異常測(cè)試是在拋出異常的代碼中放入 try 塊,然后在 try 塊的末尾加入一個(gè) fail() 語(yǔ)句。
          例如,該方法測(cè)試被零除拋出一個(gè) ArithmeticException:

           

          public void testDivisionByZero() {
          try {
          int n = 2 / 0;
          fail(
          "Divided by zero!");
          }

          catch (ArithmeticException success) {
          assertNotNull(success.getMessage());
          }

          }


          該方法不僅難看,而且寫起來(lái)也繁瑣。在 JUnit 4 中,我們現(xiàn)在可以編寫拋出異常的代碼,并使用注釋來(lái)聲明該異常是預(yù)期的:

             @Test(expected = BusinessException.class)
          public void testExecuteNameEmpty() throws Exception {
          BookList bListAction 
          = new BookList();
          bListAction.setName(
          "");
          bListAction.execute();
          }

          附被測(cè)試代碼(如果輸入name為empty,則拋出BusinessException,若name不為"liming",則拋出MessageException異常):
              @Override
          public String execute() throws Exception {
          if (StringUtils.isEmpty(name)) {
          throw new BusinessException("~""name cant't empty.");
          }

          if (!StringUtils.equals("liming", name.trim())) {
          throw new MessageException(name + " have no limits.");
          }

          Map ret 
          = serviceCall.call(JMockService.queryDtlInfo, null);
          orderId 
          = (String) ret.get("OrderId");
          dataList 
          = (List) ret.get("Data");
          return SUCCESS;
          }


          參數(shù)化測(cè)試

          為了保證單元測(cè)試的嚴(yán)謹(jǐn)性,我們經(jīng)常要模擬很多種輸入?yún)?shù),來(lái)確定我們的功能代碼是可以正常工作的,為此我們編寫大量的單元測(cè)試方法。可是這些測(cè)試方法都是大同小異:代碼結(jié)構(gòu)都是相同的,不同的僅僅是測(cè)試數(shù)據(jù)和期望輸出值。
          JUnit4 的參數(shù)化測(cè)試方法給我們提供了更好的方法,將測(cè)試方法中相同的代碼結(jié)構(gòu)提取出來(lái),提高代碼的重用度,減少?gòu)?fù)制粘貼代碼的痛苦。
          例如下面的功能代碼(格式化字符串,將駝峰規(guī)則的字符串以"_"分隔):

          public class WordDealUtil {
          public static String wordFormat4DB(String name) {
          if (name == null{
          return null;
          }

          Pattern p 
          = Pattern.compile("[A-Z]");
          Matcher m 
          = p.matcher(name);
          StringBuffer sb 
          = new StringBuffer();
          while (m.find()) {
          if (m.start() != 0{
          m.appendReplacement(sb, (
          "_" + m.group()).toLowerCase());
          }

          }

          return m.appendTail(sb).toString().toLowerCase();
          }

          }


          沒有使用參數(shù)化的測(cè)試用例代碼: 

          public class WordDealUtilTest {
          /**
          * 測(cè)試 null 時(shí)的處理情況
          */

          @Test
          public void wordFormat4DBNull() {
          String target 
          = null;
          String result 
          = WordDealUtil.wordFormat4DB(target);
          assertNull(result);
          }

          /**
          * 測(cè)試空字符串的處理情況
          */

          @Test
          public void wordFormat4DBEmpty() {
          String target 
          = "";
          String result 
          = WordDealUtil.wordFormat4DB(target);
          assertEquals(
          "", result);
          }

          /**
          * 測(cè)試當(dāng)首字母大寫時(shí)的情況
          */

          @Test
          public void wordFormat4DBegin() {
          String target 
          = "EmployeeInfo";
          String result 
          = WordDealUtil.wordFormat4DB(target);
          assertEquals(
          "employee_info", result);
          }

          /**
          * 測(cè)試當(dāng)尾字母為大寫時(shí)的情況
          */

          @Test
          public void wordFormat4DBEnd() {
          String target 
          = "employeeInfoA";
          String result 
          = WordDealUtil.wordFormat4DB(target);
          assertEquals(
          "employee_info_a", result);
          }

          /**
          * 測(cè)試多個(gè)相連字母大寫時(shí)的情況
          */

          @Test
          public void wordFormat4DBTogether() {
          String target 
          = "employeeAInfo";
          String result 
          = WordDealUtil.wordFormat4DB(target);
          assertEquals(
          "employee_a_info", result);
          }

          }


          看以上測(cè)試用例代碼,結(jié)構(gòu)相似,只是輸入值與期望輸出不同而已,但我們要拷貝很多代碼。
          使用參數(shù)化的測(cè)試用例代碼:

          @SuppressWarnings("unchecked")
          @RunWith(Parameterized.
          class)
          public class WordDealUtilTestWithParam {
          private String expected;
          private String target;
          @Parameters
          public static Collection words() {
          return Arrays.asList(new Object[][] {
          "employee_info""employeeInfo" },  // 測(cè)試一般的處理情況
          nullnull },                         // 測(cè)試 null 時(shí)的處理情況
          """" },                             // 測(cè)試空字符串時(shí)的處理情況
          "employee_info""EmployeeInfo" },    // 測(cè)試當(dāng)首字母大寫時(shí)的情況
          "employee_info_a""employeeInfoA" }// 測(cè)試當(dāng)尾字母為大寫時(shí)的情況
          "employee_a_info""employeeAInfo" }  // 測(cè)試多個(gè)相連字母大寫時(shí)的情況
          }
          );
          }

          /**
          * 參數(shù)化測(cè)試必須的構(gòu)造函數(shù)
          @param expected     期望的測(cè)試結(jié)果,對(duì)應(yīng)參數(shù)集中的第一個(gè)參數(shù)
          @param target     測(cè)試數(shù)據(jù),對(duì)應(yīng)參數(shù)集中的第二個(gè)參數(shù)
          */

          public WordDealUtilTestWithParam(String expected, String target) {
          this.expected = expected;
          this.target = target;
          }

          /**
          * 測(cè)試將 Java 對(duì)象名稱到數(shù)據(jù)庫(kù)名稱的轉(zhuǎn)換
          */

          @Test
          public void wordFormat4DB() {
          Assert.assertEquals(expected, WordDealUtil.wordFormat4DB(target));
          }

          }

          很明顯,代碼簡(jiǎn)單且很清晰了。在靜態(tài)方法 words 中,我們使用二維數(shù)組來(lái)構(gòu)建測(cè)試所需要的參數(shù)列表,其中每個(gè)數(shù)組中的元素的放置順序并沒有什么要求,只要和構(gòu)造函數(shù)中的順序保持一致就可以了。現(xiàn)在如果再增加一種測(cè)試情況,只需要在靜態(tài)方法 words 中添加相應(yīng)的數(shù)組即可,不再需要復(fù)制粘貼出一個(gè)新的方法出來(lái)了。
          這種參數(shù)化的測(cè)試用例寫法,很適用于一些共用的功能方法。

          posted on 2010-12-15 15:34 李 明 閱讀(13216) 評(píng)論(1)  編輯  收藏 所屬分類: JUnit

          評(píng)論

          # re: JUnit4用法詳解 2010-12-15 17:12 mashiguang
          先收藏  回復(fù)  更多評(píng)論
            


          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 尼木县| 安仁县| 牡丹江市| 兰考县| 哈巴河县| 宿迁市| 富阳市| 樟树市| 富裕县| 林西县| 尼木县| 中牟县| 汉沽区| 长宁县| 民县| 伊通| 龙岩市| 九龙坡区| 五常市| 揭西县| 和顺县| 广德县| 北辰区| 安平县| 光泽县| 泰宁县| 临澧县| 合山市| 莎车县| 吉木乃县| 外汇| 博客| 杂多县| 安化县| 肥城市| 宽甸| 马尔康县| 神农架林区| 梓潼县| 乌拉特中旗| 民乐县|