![測(cè)試驅(qū)動(dòng)的設(shè)計(jì)和開發(fā)課件_第1頁(yè)](http://file4.renrendoc.com/view/381f2b90357ce1a262756270f4984f61/381f2b90357ce1a262756270f4984f611.gif)
![測(cè)試驅(qū)動(dòng)的設(shè)計(jì)和開發(fā)課件_第2頁(yè)](http://file4.renrendoc.com/view/381f2b90357ce1a262756270f4984f61/381f2b90357ce1a262756270f4984f612.gif)
![測(cè)試驅(qū)動(dòng)的設(shè)計(jì)和開發(fā)課件_第3頁(yè)](http://file4.renrendoc.com/view/381f2b90357ce1a262756270f4984f61/381f2b90357ce1a262756270f4984f613.gif)
![測(cè)試驅(qū)動(dòng)的設(shè)計(jì)和開發(fā)課件_第4頁(yè)](http://file4.renrendoc.com/view/381f2b90357ce1a262756270f4984f61/381f2b90357ce1a262756270f4984f614.gif)
![測(cè)試驅(qū)動(dòng)的設(shè)計(jì)和開發(fā)課件_第5頁(yè)](http://file4.renrendoc.com/view/381f2b90357ce1a262756270f4984f61/381f2b90357ce1a262756270f4984f615.gif)
版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
測(cè)試驅(qū)動(dòng)的設(shè)計(jì)和開發(fā)
(TestDrivenDesignandDevelopment)
基礎(chǔ)篇111111測(cè)試驅(qū)動(dòng)的設(shè)計(jì)和開發(fā)
(TestDrivenDesig你的代碼工作嗎?“這段代碼很簡(jiǎn)單,不可能出錯(cuò)”“我試過(guò)了,它是正常工作的呀”“我用Debugger測(cè)試過(guò)了,我遍歷了所有程序分支,內(nèi)存中的值都是對(duì)的”最好的方法是寫一段另外的代碼來(lái)證明它,讓電腦來(lái)告訴我們它是工作的。111112你的代碼工作嗎?“這段代碼很簡(jiǎn)單,不可能出錯(cuò)”111112XP中的測(cè)試UnitTestAcceptanceTest(FunctionalTest)RegressionTestNightlyTestStressTest所有的測(cè)試都應(yīng)該獨(dú)立地自動(dòng)的運(yùn)行111113XP中的測(cè)試UnitTest111113什么是單元測(cè)試(UnitTest)單元測(cè)試是一段能夠放在批處理中自動(dòng)運(yùn)行的,用來(lái)測(cè)試Classes的程序。單元測(cè)試測(cè)試一小段代碼或一個(gè)足夠小的功能。單元測(cè)試程序調(diào)用這小段代碼或功能,并驗(yàn)證返回的結(jié)果是否符合預(yù)先設(shè)定的結(jié)果。每個(gè)單元測(cè)試至少應(yīng)該有兩個(gè)測(cè)試?yán)?TestCase):NegativePositive單元測(cè)試是軟件工程的一個(gè)關(guān)鍵部分。111114什么是單元測(cè)試(UnitTest)單元測(cè)試是一段能夠放在批什么是AcceptanceTestAcceptanceTestareprogramsorscriptsconfiguredtotestthatpackages(groupsofclustersofclasses)meetexternalrequirementsandachievegoals,suchasperformance.Theyincludescreen-drivingprogramsthattestGUIsfromwithout.AcceptanceTest是對(duì)軟件做End-To-End的測(cè)試,衡量軟件是否符合用戶需求的指標(biāo),也就是驗(yàn)收測(cè)試。111115什么是AcceptanceTestAcceptanceT什么是RegressionTest“Regressiontestingistheprocessofvalidatingmodifiedpartsofthesoftwareandensuringthatnonewerrorsareintroducedintopreviouslytestedcode.”一句話,RegresstionTest就是要重新測(cè)試所有的代碼和功能。RegressionTest和DevelopmentTest的不同在于RegressionTest需要重用已經(jīng)建立的所有的測(cè)試單元(UnitTest)和功能測(cè)試套件(FunctionalTest)。RegressionTest的基礎(chǔ)是完整的自動(dòng)單元測(cè)試和功能測(cè)試。111116什么是RegressionTest“Regression什么是NightlyTestNightlyTest就是每晚自動(dòng)運(yùn)行所有的UnitTest和AcceptanceTest。NightlyTest是XP中的ContinuousTest的一個(gè)練習(xí)(Practice)。NightlyTest可以準(zhǔn)確的反映項(xiàng)目開發(fā)的進(jìn)度和質(zhì)量。111117什么是NightlyTestNightlyTest就是每NightlyTestNightlyTest是軟件開發(fā)中一個(gè)保證開發(fā)之質(zhì)量的最有效的方法,也是衡量軟件之質(zhì)量和開發(fā)效率的最好的指標(biāo)。NightlyTest就是每天工作結(jié)束,所有的代碼都Checkin到SourceControl后,自動(dòng)運(yùn)行所有的UnitTest和FunctionTest。測(cè)試的結(jié)果應(yīng)該自動(dòng)分發(fā)給開發(fā)人員和管理層。兩個(gè)指標(biāo)數(shù)值:測(cè)試?yán)拥耐ㄟ^(guò)率–單元測(cè)試必須是100%通過(guò)。FunctionalTest應(yīng)該按計(jì)劃的通過(guò)。單元測(cè)試的覆蓋率–表明有多少Class被測(cè)試過(guò)和測(cè)試的完善程度。111118NightlyTestNightlyTest是軟件開發(fā)中測(cè)試優(yōu)先的編程在寫任何代碼之前,先寫它的UnitTest。“Neverwritealineoffunctionalcodewithoutabrokentestcase”KentBeckTest-FirstProgramming是一種測(cè)試技術(shù)嗎?Test-FirstProgramming首先是一種分析方法。它迫使程序員仔細(xì)思考要做什么和不要做什么(而不是如何具體的實(shí)現(xiàn))。特別是各種例外的情況,并用程序語(yǔ)言正式的寫下來(lái)。這就好像在程序員的任務(wù)和程序員之間簽訂了一個(gè)清晰的正式合同。Test-FirstProgramming是一種設(shè)計(jì)方法。UnitTest測(cè)試的事程序,而不是一個(gè)想法。程序員必須清晰的定義程序的界面才能寫出它的UnitTest。而這時(shí)程序員是不知道(也不需要知道)里面的具體邏輯是如何實(shí)現(xiàn)的。程序員只需要考慮Class的界面和功能(Responsibility)。啊,你在做OO設(shè)計(jì)了。Test-FirstProgramming是一種質(zhì)量控制方法(QualityControl)。如何控制質(zhì)量呢?如何知道我的程序是否運(yùn)行呢?我會(huì)不會(huì)漏了什么?運(yùn)行一下UnitTest。Test-FirstProgramming是一種重構(gòu)和優(yōu)化的方法。我們總希望自己的代碼可以漂亮,運(yùn)行的效率高,所以我們會(huì)不斷地去改進(jìn)??墒侨绾伪WC改進(jìn)和優(yōu)化后的質(zhì)量呢?會(huì)不會(huì)越改越糟?答案還是UnitTest。Test-FirstProgramming不是通常意義上的測(cè)試技術(shù),它的目的也不是僅僅用來(lái)測(cè)試你的代碼。Test-FirstProgramming是一種面向?qū)ο蟮拈_發(fā)方法。111119測(cè)試優(yōu)先的編程在寫任何代碼之前,先寫它的UnitTest。什么是Test-DrivenDesign(TDD)Test-DrivenDesign是一種開發(fā)風(fēng)格,它要求程序員做到:在寫產(chǎn)品代碼之前,先寫它的單元測(cè)試(UnitTests)沒有單元測(cè)試的Class不允許作為產(chǎn)品代碼單元測(cè)試?yán)記Q定了如何寫產(chǎn)品代碼不斷地成功運(yùn)行所有的單元測(cè)試?yán)硬粩嗟耐晟茊卧獪y(cè)試?yán)覶est-DrivenDesign是把需求分析,設(shè)計(jì),質(zhì)量控制量化的過(guò)程!1111110什么是Test-DrivenDesign(TDD)Tes為什么會(huì)出現(xiàn)TDD現(xiàn)實(shí)中的設(shè)計(jì)(Design)和測(cè)試(Testing):面對(duì)一個(gè)新的開發(fā)任務(wù),往往第一個(gè)念頭就是如何去實(shí)現(xiàn)它呢?“好像是這樣做的”感覺上差不多了。抓起任務(wù)就開始編碼,一邊寫,一邊修改和設(shè)計(jì)。哎,時(shí)間很緊。我先把任務(wù)實(shí)現(xiàn)了,然后再好好測(cè)試。還是不工作,時(shí)間不多了。做個(gè)快速但丑陋的修改吧。等有空來(lái)再來(lái)重新整理這些代碼吧。用Debugger運(yùn)行幾次代碼,走完所有的我認(rèn)為可能的分支。我感覺這些代碼應(yīng)該行了。提交吧。哎,我也知道該寫一些自動(dòng)的單元測(cè)試來(lái)把剛才在Debugger中的測(cè)試走一遍??墒悄鞘呛芏嗟幕畎?。這種情況要作自動(dòng)測(cè)試太復(fù)雜了。還是手工作一下測(cè)試好了。1111111為什么會(huì)出現(xiàn)TDD現(xiàn)實(shí)中的設(shè)計(jì)(Design)和測(cè)試(Tes為什么會(huì)出現(xiàn)TDD(Continue)程序員心中的測(cè)試:很郁悶的工作。對(duì)啊,程序員該做些新的,有創(chuàng)意的東西嘛。寫一些新的功能會(huì)更有趣些。我知道這些代碼會(huì)工作的。我的經(jīng)驗(yàn)和感覺都這樣告訴我。只要沒人亂改我的代碼,應(yīng)該就沒問(wèn)題。再說(shuō)這些邊緣情況幾乎不可能出現(xiàn)了。測(cè)試是QA的工作。自動(dòng)測(cè)試太花時(shí)間(我要趕Deadline),不值得。1111112為什么會(huì)出現(xiàn)TDD(Continue)程序員心中的測(cè)試:11如何面對(duì)這些現(xiàn)實(shí)和想法Test-DrivenDesignandDevelopment真的能行?試一試!1111113如何面對(duì)這些現(xiàn)實(shí)和想法Test-DrivenDesign如何做TestDrivenDesignandDevelopment再開發(fā)一個(gè)新的功能之前首先確定你要做什么(不是要如何做?。。?比如說(shuō)一個(gè)論壇的增加用戶的功能,我們需要又一個(gè)method來(lái)增加一個(gè)用戶:
publicvoidaddAccount(Accountaccount) 當(dāng)然包括成功增加一個(gè)用戶(在數(shù)據(jù)庫(kù)中插入一條紀(jì)錄) 還包括如果已經(jīng)由一個(gè)相同的用戶,應(yīng)該返回一個(gè)用戶已存在的消息
OK,我們知道這個(gè)method中的這段代碼要做什么,而且這段代碼也足夠簡(jiǎn)單。1111114如何做TestDrivenDesignandDeve如何做TestDrivenDesignandDevelopment
(Continue)然后為這個(gè)功能(Method)寫單元測(cè)試?yán)?UnitTest) 單元測(cè)試?yán)右采w這個(gè)Method的“做什么”。 所以我們至少有了兩個(gè)測(cè)試?yán)?
TestCase1:測(cè)試成功增加一個(gè)用戶
TestCase2:測(cè)試增加一個(gè)已存在的用戶 其他邊緣情況測(cè)試:
TestCase3:傳入的Account對(duì)象為NULL1111115如何做TestDrivenDesignandDeve如何做TestDrivenDesignandDevelopment
(Continue)寫Production代碼 我們清楚知道這段代碼需要做什么。因?yàn)槲覀冇辛硪欢未a擺在那里,清晰的表明這段代碼的Contracts。
不用多,也不能少,只需要能實(shí)現(xiàn)再UnitTest中的Contracts和能夠通過(guò)它的UnitTest。
1111116如何做TestDrivenDesignandDeve如何做TestDrivenDesignandDevelopment
(Continue)運(yùn)行UnitTest
如果順利通過(guò),你已經(jīng)很好的完成了你的任務(wù)。 如果沒通過(guò),修補(bǔ)代碼直到能通過(guò)UnitTest為止。 如果出現(xiàn)在UnitTest中沒預(yù)先設(shè)定的結(jié)果,在UnitTest中增加一個(gè)TestCase,修補(bǔ)代碼直到通過(guò)所有的TestCase為止。1111117如何做TestDrivenDesignandDeveTDD和PSPPersonalSoftwareProcess的DevelopmentDesignCodeBuildTestTest-DrivenDesignandDevelopmentAnalysisCodeUnitTestCodeBuildRunTestAnalysisDesign1111118TDD和PSPPersonalSoftwareProceXP采用了TDDTDD是ExtremeProgramming中必須遵行的一個(gè)方法。TDD是XP中PairProgramming的工作模式。XP中把測(cè)試驅(qū)動(dòng)的設(shè)計(jì)和開發(fā)做到極致。TDD的整個(gè)流程由兩個(gè)程序員一起執(zhí)行。XP正是因?yàn)椴捎昧薚DD才能夠做到每天的代碼都是ProductionCode和每個(gè)小的Release都能提供具備Production質(zhì)量的代碼并投入使用。有了TDD,XP才能降低風(fēng)險(xiǎn),去擁抱變化。有了TDD,XP才能在計(jì)劃的時(shí)間內(nèi)完成計(jì)劃質(zhì)量的代碼。有了TDD,XP才能減少Code<->Fix環(huán)節(jié),從而減少項(xiàng)目成本。有了TDD,XPTeam才能對(duì)自己的工作充滿自信。1111119XP采用了TDDTDD是ExtremeProgramminTDD防止Over-Engineering在開發(fā)中采用TDD,可以有效的避免過(guò)度設(shè)計(jì)和開發(fā)。如果程序員不愿為一個(gè)Method寫測(cè)試?yán)踊蛘哒J(rèn)為現(xiàn)在沒有必要測(cè)試改Method,那這個(gè)Method多半是現(xiàn)在不需要的。1111120TDD防止Over-Engineering在開發(fā)中采用TDDTDD,程序員和管理層對(duì)程序員來(lái)說(shuō),通過(guò)運(yùn)行UnitTest和FunctionalTest,每天下班的時(shí)候都可以清楚的知道自己的代碼是work的。對(duì)管理層來(lái)說(shuō),通過(guò)NightlyTest的結(jié)果,每天一早都清楚的知道項(xiàng)目的質(zhì)量和開發(fā)進(jìn)度。1111121TDD,程序員和管理層對(duì)程序員來(lái)說(shuō),通過(guò)運(yùn)行UnitTesXP中誰(shuí)來(lái)寫TestsDeveloper:UnitTestAcceptanceTest(FunctionalTest)Customer:AcceptanceTestCustomer為每一個(gè)UserStory寫FunctionalTest。但通常用戶并不具備設(shè)計(jì)和開發(fā)FunctionalTest的能力,需要程序員的幫助。1111122XP中誰(shuí)來(lái)寫TestsDeveloper:1111122什么時(shí)候?qū)慣ests?如果你要寫一個(gè)新的功能,請(qǐng)先寫她的測(cè)試?yán)尤绻阋跊]有經(jīng)過(guò)測(cè)試的代碼上寫新的功能,請(qǐng)先寫目前代碼的測(cè)試?yán)尤绻阋狥ix一個(gè)Bug,請(qǐng)先為這個(gè)Bug寫一個(gè)測(cè)試?yán)尤绻阋猂efactor沒有測(cè)試過(guò)的代碼,請(qǐng)先寫一個(gè)測(cè)試?yán)尤绻惆l(fā)現(xiàn)一個(gè)邊緣例外值,請(qǐng)為她寫一個(gè)測(cè)試?yán)?111123什么時(shí)候?qū)慣ests?如果你要寫一個(gè)新的功能,請(qǐng)先寫她的測(cè)試ExtremeUnit
Junit JavaClass的測(cè)試FrameworkJFCUnit JavaSwingapp的測(cè)試FrameworkCatus JavaServerSide(EJB,Servlet)的測(cè)試FrameworkHTMLUnit HtmlPage的測(cè)試FrameworkHTTPUnit HtmlPage的測(cè)試FrameworkCPPUnit C++測(cè)試Framework.NetUnit .Netapp的測(cè)試Framework……1111124ExtremeUnitJunit JavaClassJunit(Asample)Junit是由kentBeck和ErichGamma編寫的一個(gè)opensource的測(cè)試框架,用來(lái)編寫可重復(fù)的測(cè)試?yán)?。測(cè)試論壇中的增加用戶methodpublicclassAccountDAOmySqlimplementsAccountDAO{ /** *Addauseraccount * *@paramAccount-Aaccountobjectthatcontainstheuserinfo,likeuserName, *password,email */ publicvoidaddAccount(finalAccountaccount)throwsSQLException, AccountAlreadyExistException{ …… }1111125Junit(Asample)Junit是由kentBeJunit(Asample)1.為對(duì)應(yīng)的JavaClass建立一個(gè)TestCase。UnitTestCase應(yīng)該放在和BusinessClass相同的Package中,但在不同的的物理位置。packageorg.redsoft.forum.dao.mysqlimportjunit.framework.TestCase;importjunit.framework.TestSuite;importjunit.framework.Test;publicclassAccountDAOmySqlTestextendsTestCase{ publicAccountDAOmySqlTest(Stringname){ super(name); } }1111126Junit(Asample)1.為對(duì)應(yīng)的JavaClaJunit(Asample)2.OverridesetUp()andtearDown() 如果需要,可以在setUp()中初始化需要的全局變量,資源等(比如DatabaseConnection,FileI/O或MockObjects等) 相應(yīng)的,可以在tearDown()中釋放資源(DatabaseConnection,FileI/O和MockObjects等)publicclassAccountDAOmySqlTestextendsTestCase{
…… privateMysqlFixturemysqlFixtureIns=newMysqlFixture(); publicvoidsetUp()throwsException{ mysqlFixtureIns.setUp(); } publicvoidtearDown()throwsException{ mysqlFixtureIns.tearDown(); }
}1111127Junit(Asample)2.OverridesJunit(Asample)3.為被測(cè)試的Method寫TestCase……publicvoidtestAddAccountNormal(){ AccountDAOmySqldao=newAccountDAOmySql(); Accountaccount=newAccount(USER_NAME,"charles","charles_hhb@"); try{dao.addAccount(account);Accountaccount_new=dao.findByUserName(account.getUserName());assertEquals("Expectingcharles",account.getUserName(),account_new.getUserName());assertEquals("Expectingcharles",account.getPassword(),account_new.getPassword());assertEquals("Expectingcharles@",account.getEmail(),account_new.getEmail());dao.removeAccount(account.getUserName());}catch(finalExceptione){e.printStackTrace();fail("Unexpectedexception::"+e.toString());}}1111128Junit(Asample)3.為被測(cè)試的Method寫Junit(Asample)publicvoidtestAddAccountAlreadyExist(){ AccountDAOmySqldao=null; Accountaccount=null; try{
//AddanAccount ……
dao.addAccount(account); fail("AccountAlreadyExistExceptionexpected"); }catch(finalSQLExceptione){ e.printStackTrace(); fail("Unexpectedexception::"+e.toString()); }catch(finalAccountNotFoundExceptionnotFound){ notFound.printStackTrace(); fail("Unexpectedexception:"+notFound.toString()); }catch(finalAccountAlreadyExistExceptionex){ //Pass try{ dao.removeAccount(account.getUserName()); }catch(finalSQLExceptionsql){ sql.printStackTrace(); fail("Unexpectedexception"); } }}1111129Junit(Asample)publicvoidteJunit(ASample)運(yùn)行這個(gè)UnitTest。Junit提供兩種運(yùn)行界面:Swing(junit.swingui.TestRunner)C:\sandbox\forum>java-classpath./classes;./lib/junit.jar;./lib/mysql_jdbc.jar;./lib/Tidy.jar;./lib/struts.jarjunit.swingui.TestRunnerorg.redsoft.forum.dao.mysql.AccountDAOmySqlTest1111130Junit(ASample)運(yùn)行這個(gè)UnitTest。Junit(ASample)Text界面(junit.textui.TestRunner)1111131Junit(ASample)Text界面(junit.JunitTestSuiteTestSuite用來(lái)運(yùn)行所有的UnitTestsTestSuite的數(shù)型結(jié)構(gòu):org.redsoft.forum.AllTest | |org.redsoft.forum.dao.AllTest | | | |org.redsoft.forum.dao.mysql.AllTest | |org.redsoft.forum.util.AllTest每個(gè)PackageLevel都由一個(gè)AllTestTestSuite在每個(gè)TestSuite中,加入在本packagelevel中的所有單元測(cè)試?yán)?UnitTestCases)加入子Packagelevel中的所有AllTestSuite
1111132JunitTestSuiteTestSuite用來(lái)運(yùn)行JunitTestSuitepackageorg.redsoft.forum.dao;publicclassAllTests{ publicstaticvoidmain(Stringargs[]){ junit.textui.TestRunner.run(suite()); } publicstaticTestsuite(){ TestSuitesuite=newTestSuite();
//加入子package中的AllTestsuite suite.addTest(org.redsoft.forum.dao.mysql.AllTests.suite()); //加入本packagelevel中的UnitTestcase suite.addTestSuite(MysqlDataSourceTest.class); returnsuite; }}//EOC1111133JunitTestSuitepackageorg.reJFCUnit一個(gè)Junit的Extension,用來(lái)測(cè)試Swing-based的Application。一個(gè)最簡(jiǎn)單的Sample:測(cè)試一個(gè)LoginScreen1111134JFCUnit一個(gè)Junit的Extension,用來(lái)測(cè)試JFCUnit代碼片斷:設(shè)置測(cè)試環(huán)境privateLoginScreenloginScreen=null; privateTestHelperhelper=null; publicLoginScreenTest(Stringname) {super(name);} protectedvoidsetUp()throwsException { super.setUp(); helper=newJFCTestHelper(); loginScreen=newLoginScreen("LoginScreenTest:"+getName()); loginScreen.setVisible(true); } protectedvoidtearDown()throwsException{ loginScreen=null;helper.cleanUp(this);super.tearDown(); }代碼片斷:測(cè)試圖形界面JDialogdialog;JButtonexitButton=(JButton)helper.findNamedComponent("ExitButton", loginScreen,0);assertNotNull("CouldnotfindtheExitbutton",exitButton);JButtonenterButton=(JButton)helper.findNamedComponent("EnterButton", loginScreen,0);assertNotNull("CouldnotfindtheEnterbutton",enterButton);JTextFielduserNameField=(JTextField)helper.findNamedComponent("LoginNameTextField", loginScreen,0);assertNotNull("CouldnotfindtheuserNameField",userNameField);assertEquals("Usernamefieldisempty","",userNameField.getText());JTextFieldpasswordField=(JTextField)helper.findNamedComponent("PasswordTextField", loginScreen,0);assertNotNull("CouldnotfindthepasswordField",passwordField);assertEquals("Passwordfieldisempty","",passwordField.getText());
1111135JFCUnit代碼片斷:設(shè)置測(cè)試環(huán)境代碼片斷:測(cè)試圖形界面利用Ant來(lái)做NightlyTest使用Ant中的兩個(gè)Tasks來(lái)完成自動(dòng)運(yùn)行NightlyTest<junitprintsummary="yes"haltonfailure="yes"> <testname="org.redsoft.forum.AllTests”haltonfailure="no"outfile="result"> <formattertype="xml"/> </test></junit>產(chǎn)生一個(gè)XML個(gè)是的結(jié)果報(bào)告再利用JunitReport來(lái)產(chǎn)生一個(gè)可供瀏覽的結(jié)果文件。<junitreporttodir="./reports"> <filesetdir="./reports"> <includename="TEST-*.xml"/> </fileset> <reportformat="frames"todir="./report/html"/></junitreport>1111136利用Ant來(lái)做NightlyTest使用Ant中的兩個(gè)Ta測(cè)試?yán)痈采w率利用NOUnit來(lái)獲得測(cè)試?yán)拥母采w率(/)1111137測(cè)試?yán)痈采w率利用NOUnit來(lái)獲得測(cè)試?yán)拥母采w率1111UnitTests:100%always任何時(shí)候如果UnitTests的出現(xiàn)錯(cuò)誤(Junit的進(jìn)度指示顯示紅色),XPTeam的首要工作就是修補(bǔ)UnitTests直至Junit的進(jìn)度指示為綠色。如果不修復(fù)出錯(cuò)的測(cè)試?yán)樱蜁?huì)出現(xiàn)滾雪球效應(yīng),在未知質(zhì)量代碼基礎(chǔ)上的開發(fā)只會(huì)導(dǎo)致更多的未知質(zhì)量的代碼。軟件質(zhì)量的基石就開始崩潰。
1111138UnitTests:100%always任何時(shí)候如果UnSoftwareQualityManagement
你的項(xiàng)目有SoftwareQualityManagement嗎?如何衡量?CMMLevel4 --軟件產(chǎn)品的質(zhì)量管理是被預(yù)先計(jì)劃的
--測(cè)量方法和目標(biāo)是被預(yù)先定義和計(jì)劃的
--質(zhì)量管理的進(jìn)度是被預(yù)先計(jì)劃的
1111139SoftwareQualityManagement
你的ReferenceKentBeck,ExtremeProgrammingExplained:EmbraceChangeC2.comJ1111140ReferenceKentBeck,ExtremePro測(cè)試驅(qū)動(dòng)的設(shè)計(jì)和開發(fā)
(TestDrivenDesignandDevelopment)
基礎(chǔ)篇1111141測(cè)試驅(qū)動(dòng)的設(shè)計(jì)和開發(fā)
(TestDrivenDesig你的代碼工作嗎?“這段代碼很簡(jiǎn)單,不可能出錯(cuò)”“我試過(guò)了,它是正常工作的呀”“我用Debugger測(cè)試過(guò)了,我遍歷了所有程序分支,內(nèi)存中的值都是對(duì)的”最好的方法是寫一段另外的代碼來(lái)證明它,讓電腦來(lái)告訴我們它是工作的。1111142你的代碼工作嗎?“這段代碼很簡(jiǎn)單,不可能出錯(cuò)”111112XP中的測(cè)試UnitTestAcceptanceTest(FunctionalTest)RegressionTestNightlyTestStressTest所有的測(cè)試都應(yīng)該獨(dú)立地自動(dòng)的運(yùn)行1111143XP中的測(cè)試UnitTest111113什么是單元測(cè)試(UnitTest)單元測(cè)試是一段能夠放在批處理中自動(dòng)運(yùn)行的,用來(lái)測(cè)試Classes的程序。單元測(cè)試測(cè)試一小段代碼或一個(gè)足夠小的功能。單元測(cè)試程序調(diào)用這小段代碼或功能,并驗(yàn)證返回的結(jié)果是否符合預(yù)先設(shè)定的結(jié)果。每個(gè)單元測(cè)試至少應(yīng)該有兩個(gè)測(cè)試?yán)?TestCase):NegativePositive單元測(cè)試是軟件工程的一個(gè)關(guān)鍵部分。1111144什么是單元測(cè)試(UnitTest)單元測(cè)試是一段能夠放在批什么是AcceptanceTestAcceptanceTestareprogramsorscriptsconfiguredtotestthatpackages(groupsofclustersofclasses)meetexternalrequirementsandachievegoals,suchasperformance.Theyincludescreen-drivingprogramsthattestGUIsfromwithout.AcceptanceTest是對(duì)軟件做End-To-End的測(cè)試,衡量軟件是否符合用戶需求的指標(biāo),也就是驗(yàn)收測(cè)試。1111145什么是AcceptanceTestAcceptanceT什么是RegressionTest“Regressiontestingistheprocessofvalidatingmodifiedpartsofthesoftwareandensuringthatnonewerrorsareintroducedintopreviouslytestedcode.”一句話,RegresstionTest就是要重新測(cè)試所有的代碼和功能。RegressionTest和DevelopmentTest的不同在于RegressionTest需要重用已經(jīng)建立的所有的測(cè)試單元(UnitTest)和功能測(cè)試套件(FunctionalTest)。RegressionTest的基礎(chǔ)是完整的自動(dòng)單元測(cè)試和功能測(cè)試。1111146什么是RegressionTest“Regression什么是NightlyTestNightlyTest就是每晚自動(dòng)運(yùn)行所有的UnitTest和AcceptanceTest。NightlyTest是XP中的ContinuousTest的一個(gè)練習(xí)(Practice)。NightlyTest可以準(zhǔn)確的反映項(xiàng)目開發(fā)的進(jìn)度和質(zhì)量。1111147什么是NightlyTestNightlyTest就是每NightlyTestNightlyTest是軟件開發(fā)中一個(gè)保證開發(fā)之質(zhì)量的最有效的方法,也是衡量軟件之質(zhì)量和開發(fā)效率的最好的指標(biāo)。NightlyTest就是每天工作結(jié)束,所有的代碼都Checkin到SourceControl后,自動(dòng)運(yùn)行所有的UnitTest和FunctionTest。測(cè)試的結(jié)果應(yīng)該自動(dòng)分發(fā)給開發(fā)人員和管理層。兩個(gè)指標(biāo)數(shù)值:測(cè)試?yán)拥耐ㄟ^(guò)率–單元測(cè)試必須是100%通過(guò)。FunctionalTest應(yīng)該按計(jì)劃的通過(guò)。單元測(cè)試的覆蓋率–表明有多少Class被測(cè)試過(guò)和測(cè)試的完善程度。1111148NightlyTestNightlyTest是軟件開發(fā)中測(cè)試優(yōu)先的編程在寫任何代碼之前,先寫它的UnitTest?!癗everwritealineoffunctionalcodewithoutabrokentestcase”KentBeckTest-FirstProgramming是一種測(cè)試技術(shù)嗎?Test-FirstProgramming首先是一種分析方法。它迫使程序員仔細(xì)思考要做什么和不要做什么(而不是如何具體的實(shí)現(xiàn))。特別是各種例外的情況,并用程序語(yǔ)言正式的寫下來(lái)。這就好像在程序員的任務(wù)和程序員之間簽訂了一個(gè)清晰的正式合同。Test-FirstProgramming是一種設(shè)計(jì)方法。UnitTest測(cè)試的事程序,而不是一個(gè)想法。程序員必須清晰的定義程序的界面才能寫出它的UnitTest。而這時(shí)程序員是不知道(也不需要知道)里面的具體邏輯是如何實(shí)現(xiàn)的。程序員只需要考慮Class的界面和功能(Responsibility)。啊,你在做OO設(shè)計(jì)了。Test-FirstProgramming是一種質(zhì)量控制方法(QualityControl)。如何控制質(zhì)量呢?如何知道我的程序是否運(yùn)行呢?我會(huì)不會(huì)漏了什么?運(yùn)行一下UnitTest。Test-FirstProgramming是一種重構(gòu)和優(yōu)化的方法。我們總希望自己的代碼可以漂亮,運(yùn)行的效率高,所以我們會(huì)不斷地去改進(jìn)??墒侨绾伪WC改進(jìn)和優(yōu)化后的質(zhì)量呢?會(huì)不會(huì)越改越糟?答案還是UnitTest。Test-FirstProgramming不是通常意義上的測(cè)試技術(shù),它的目的也不是僅僅用來(lái)測(cè)試你的代碼。Test-FirstProgramming是一種面向?qū)ο蟮拈_發(fā)方法。1111149測(cè)試優(yōu)先的編程在寫任何代碼之前,先寫它的UnitTest。什么是Test-DrivenDesign(TDD)Test-DrivenDesign是一種開發(fā)風(fēng)格,它要求程序員做到:在寫產(chǎn)品代碼之前,先寫它的單元測(cè)試(UnitTests)沒有單元測(cè)試的Class不允許作為產(chǎn)品代碼單元測(cè)試?yán)記Q定了如何寫產(chǎn)品代碼不斷地成功運(yùn)行所有的單元測(cè)試?yán)硬粩嗟耐晟茊卧獪y(cè)試?yán)覶est-DrivenDesign是把需求分析,設(shè)計(jì),質(zhì)量控制量化的過(guò)程!1111150什么是Test-DrivenDesign(TDD)Tes為什么會(huì)出現(xiàn)TDD現(xiàn)實(shí)中的設(shè)計(jì)(Design)和測(cè)試(Testing):面對(duì)一個(gè)新的開發(fā)任務(wù),往往第一個(gè)念頭就是如何去實(shí)現(xiàn)它呢?“好像是這樣做的”感覺上差不多了。抓起任務(wù)就開始編碼,一邊寫,一邊修改和設(shè)計(jì)。哎,時(shí)間很緊。我先把任務(wù)實(shí)現(xiàn)了,然后再好好測(cè)試。還是不工作,時(shí)間不多了。做個(gè)快速但丑陋的修改吧。等有空來(lái)再來(lái)重新整理這些代碼吧。用Debugger運(yùn)行幾次代碼,走完所有的我認(rèn)為可能的分支。我感覺這些代碼應(yīng)該行了。提交吧。哎,我也知道該寫一些自動(dòng)的單元測(cè)試來(lái)把剛才在Debugger中的測(cè)試走一遍。可是那是很多的活啊。這種情況要作自動(dòng)測(cè)試太復(fù)雜了。還是手工作一下測(cè)試好了。1111151為什么會(huì)出現(xiàn)TDD現(xiàn)實(shí)中的設(shè)計(jì)(Design)和測(cè)試(Tes為什么會(huì)出現(xiàn)TDD(Continue)程序員心中的測(cè)試:很郁悶的工作。對(duì)啊,程序員該做些新的,有創(chuàng)意的東西嘛。寫一些新的功能會(huì)更有趣些。我知道這些代碼會(huì)工作的。我的經(jīng)驗(yàn)和感覺都這樣告訴我。只要沒人亂改我的代碼,應(yīng)該就沒問(wèn)題。再說(shuō)這些邊緣情況幾乎不可能出現(xiàn)了。測(cè)試是QA的工作。自動(dòng)測(cè)試太花時(shí)間(我要趕Deadline),不值得。1111152為什么會(huì)出現(xiàn)TDD(Continue)程序員心中的測(cè)試:11如何面對(duì)這些現(xiàn)實(shí)和想法Test-DrivenDesignandDevelopment真的能行?試一試!1111153如何面對(duì)這些現(xiàn)實(shí)和想法Test-DrivenDesign如何做TestDrivenDesignandDevelopment再開發(fā)一個(gè)新的功能之前首先確定你要做什么(不是要如何做!?。?比如說(shuō)一個(gè)論壇的增加用戶的功能,我們需要又一個(gè)method來(lái)增加一個(gè)用戶:
publicvoidaddAccount(Accountaccount) 當(dāng)然包括成功增加一個(gè)用戶(在數(shù)據(jù)庫(kù)中插入一條紀(jì)錄) 還包括如果已經(jīng)由一個(gè)相同的用戶,應(yīng)該返回一個(gè)用戶已存在的消息
OK,我們知道這個(gè)method中的這段代碼要做什么,而且這段代碼也足夠簡(jiǎn)單。1111154如何做TestDrivenDesignandDeve如何做TestDrivenDesignandDevelopment
(Continue)然后為這個(gè)功能(Method)寫單元測(cè)試?yán)?UnitTest) 單元測(cè)試?yán)右采w這個(gè)Method的“做什么”。 所以我們至少有了兩個(gè)測(cè)試?yán)?
TestCase1:測(cè)試成功增加一個(gè)用戶
TestCase2:測(cè)試增加一個(gè)已存在的用戶 其他邊緣情況測(cè)試:
TestCase3:傳入的Account對(duì)象為NULL1111155如何做TestDrivenDesignandDeve如何做TestDrivenDesignandDevelopment
(Continue)寫Production代碼 我們清楚知道這段代碼需要做什么。因?yàn)槲覀冇辛硪欢未a擺在那里,清晰的表明這段代碼的Contracts。
不用多,也不能少,只需要能實(shí)現(xiàn)再UnitTest中的Contracts和能夠通過(guò)它的UnitTest。
1111156如何做TestDrivenDesignandDeve如何做TestDrivenDesignandDevelopment
(Continue)運(yùn)行UnitTest
如果順利通過(guò),你已經(jīng)很好的完成了你的任務(wù)。 如果沒通過(guò),修補(bǔ)代碼直到能通過(guò)UnitTest為止。 如果出現(xiàn)在UnitTest中沒預(yù)先設(shè)定的結(jié)果,在UnitTest中增加一個(gè)TestCase,修補(bǔ)代碼直到通過(guò)所有的TestCase為止。1111157如何做TestDrivenDesignandDeveTDD和PSPPersonalSoftwareProcess的DevelopmentDesignCodeBuildTestTest-DrivenDesignandDevelopmentAnalysisCodeUnitTestCodeBuildRunTestAnalysisDesign1111158TDD和PSPPersonalSoftwareProceXP采用了TDDTDD是ExtremeProgramming中必須遵行的一個(gè)方法。TDD是XP中PairProgramming的工作模式。XP中把測(cè)試驅(qū)動(dòng)的設(shè)計(jì)和開發(fā)做到極致。TDD的整個(gè)流程由兩個(gè)程序員一起執(zhí)行。XP正是因?yàn)椴捎昧薚DD才能夠做到每天的代碼都是ProductionCode和每個(gè)小的Release都能提供具備Production質(zhì)量的代碼并投入使用。有了TDD,XP才能降低風(fēng)險(xiǎn),去擁抱變化。有了TDD,XP才能在計(jì)劃的時(shí)間內(nèi)完成計(jì)劃質(zhì)量的代碼。有了TDD,XP才能減少Code<->Fix環(huán)節(jié),從而減少項(xiàng)目成本。有了TDD,XPTeam才能對(duì)自己的工作充滿自信。1111159XP采用了TDDTDD是ExtremeProgramminTDD防止Over-Engineering在開發(fā)中采用TDD,可以有效的避免過(guò)度設(shè)計(jì)和開發(fā)。如果程序員不愿為一個(gè)Method寫測(cè)試?yán)踊蛘哒J(rèn)為現(xiàn)在沒有必要測(cè)試改Method,那這個(gè)Method多半是現(xiàn)在不需要的。1111160TDD防止Over-Engineering在開發(fā)中采用TDDTDD,程序員和管理層對(duì)程序員來(lái)說(shuō),通過(guò)運(yùn)行UnitTest和FunctionalTest,每天下班的時(shí)候都可以清楚的知道自己的代碼是work的。對(duì)管理層來(lái)說(shuō),通過(guò)NightlyTest的結(jié)果,每天一早都清楚的知道項(xiàng)目的質(zhì)量和開發(fā)進(jìn)度。1111161TDD,程序員和管理層對(duì)程序員來(lái)說(shuō),通過(guò)運(yùn)行UnitTesXP中誰(shuí)來(lái)寫TestsDeveloper:UnitTestAcceptanceTest(FunctionalTest)Customer:AcceptanceTestCustomer為每一個(gè)UserStory寫FunctionalTest。但通常用戶并不具備設(shè)計(jì)和開發(fā)FunctionalTest的能力,需要程序員的幫助。1111162XP中誰(shuí)來(lái)寫TestsDeveloper:1111122什么時(shí)候?qū)慣ests?如果你要寫一個(gè)新的功能,請(qǐng)先寫她的測(cè)試?yán)尤绻阋跊]有經(jīng)過(guò)測(cè)試的代碼上寫新的功能,請(qǐng)先寫目前代碼的測(cè)試?yán)尤绻阋狥ix一個(gè)Bug,請(qǐng)先為這個(gè)Bug寫一個(gè)測(cè)試?yán)尤绻阋猂efactor沒有測(cè)試過(guò)的代碼,請(qǐng)先寫一個(gè)測(cè)試?yán)尤绻惆l(fā)現(xiàn)一個(gè)邊緣例外值,請(qǐng)為她寫一個(gè)測(cè)試?yán)?111163什么時(shí)候?qū)慣ests?如果你要寫一個(gè)新的功能,請(qǐng)先寫她的測(cè)試ExtremeUnit
Junit JavaClass的測(cè)試FrameworkJFCUnit JavaSwingapp的測(cè)試FrameworkCatus JavaServerSide(EJB,Servlet)的測(cè)試FrameworkHTMLUnit HtmlPage的測(cè)試FrameworkHTTPUnit HtmlPage的測(cè)試FrameworkCPPUnit C++測(cè)試Framework.NetUnit .Netapp的測(cè)試Framework……1111164ExtremeUnitJunit JavaClassJunit(Asample)Junit是由kentBeck和ErichGamma編寫的一個(gè)opensource的測(cè)試框架,用來(lái)編寫可重復(fù)的測(cè)試?yán)?。測(cè)試論壇中的增加用戶methodpublicclassAccountDAOmySqlimplementsAccountDAO{ /** *Addauseraccount * *@paramAccount-Aaccountobjectthatcontainstheuserinfo,likeuserName, *password,email */ publicvoidaddAccount(finalAccountaccount)throwsSQLException, AccountAlreadyExistException{ …… }1111165Junit(Asample)Junit是由kentBeJunit(Asample)1.為對(duì)應(yīng)的JavaClass建立一個(gè)TestCase。UnitTestCase應(yīng)該放在和BusinessClass相同的Package中,但在不同的的物理位置。packageorg.redsoft.forum.dao.mysqlimportjunit.framework.TestCase;importjunit.framework.TestSuite;importjunit.framework.Test;publicclassAccountDAOmySqlTestextendsTestCase{ publicAccountDAOmySqlTest(Stringname){ super(name); } }1111166Junit(Asample)1.為對(duì)應(yīng)的JavaClaJunit(Asample)2.OverridesetUp()andtearDown() 如果需要,可以在setUp()中初始化需要的全局變量,資源等(比如DatabaseConnection,FileI/O或MockObjects等) 相應(yīng)的,可以在tearDown()中釋放資源(DatabaseConnection,FileI/O和MockObjects等)publicclassAccountDAOmySqlTestextendsTestCase{
…… privateMysqlFixturemysqlFixtureIns=newMysqlFixture(); publicvoidsetUp()throwsException{ mysqlFixtureIns.setUp(); } publicvoidtearDown()throwsException{ mysqlFixtureIns.tearDown(); }
}1111167Junit(Asample)2.OverridesJunit(Asample)3.為被測(cè)試的Method寫TestCase……publicvoidtestAddAccountNormal(){ AccountDAOmySqldao=newAccountDAOmySql(); Accountaccount=newAccount(USER_NAME,"charles","charles_hhb@"); try{dao.addAccount(account);Accountaccount_new=dao.findByUserName(account.getUserName());assertEquals("Expectingcharles",account.getUserName(),account_new.getUserName());assertEquals("Expectingcharles",account.getPassword(),account_new.getPassword());assertEquals("Expectingcharles@",account.getEmail(),account_new.getEmail());dao.removeAccount(account.getUserName());}catch(finalExceptione){e.printStackTrace();fail("Unexpectedexception::"+e.toString());}}1111168Junit(Asample)3.為被測(cè)試的Method寫Junit(Asample)publicvoidtestAddAccountAlreadyExist(){ AccountDAOmySqldao=null; Accountaccount=null; try{
//AddanAccount ……
dao.addAccount(account); fail("AccountAlreadyExistExceptionexpected"); }catch(finalSQLExceptione){ e.printStackTrace(); fail("Unexpectedexception::"+e.toString()); }catch(finalAccountNotFoundExceptionnotFound){ notFound.printStackTrace(); fail("Unexpectedexception:"+notFound.toString()); }catch(finalAccountAlreadyExistExceptionex){ //Pass try{ dao.removeAccount(account.getUserName()); }catch(finalSQLExceptionsql){ sql.printStackTrace(); fail("Unexpectedexception"); } }}1111169Junit(Asample)publicvoidteJunit(ASample)運(yùn)行這個(gè)UnitTest。Junit提供兩種運(yùn)行界面:Swing(junit.swingui.TestRunner)C:\sandbox\forum>java-classpath./classes;./lib/junit.jar;./lib/mysql_jdbc.jar;./lib/Tidy.jar;./lib/struts.jarjunit.swingui.TestRunnerorg.redsoft.forum.dao.mysql.AccountDAOmySqlTest1111170Junit(ASample)運(yùn)行這個(gè)UnitTest。Junit(ASample)Text界面(junit.textui.TestRunner)1111171Junit(ASample)Text界面(junit.JunitTestSuiteTestSuite用來(lái)運(yùn)行所有的UnitTestsTestSuite的數(shù)型結(jié)構(gòu):org.redsoft.forum.AllTest | |org.redsoft.forum.dao.AllTest | | | |org.redsoft.forum.dao.mysql.AllTest | |org.redsoft.forum.util.AllTest每個(gè)PackageLevel都由一個(gè)AllTestTestSuite在每個(gè)TestSuite中,加入在本packagelevel中的所有單元測(cè)試?yán)?UnitTestCases)加入子Packagelevel中的所有AllTestSuite
1111172JunitTestSuiteTestSuite用來(lái)運(yùn)行JunitTestSuitepackageorg.redsoft.forum.dao;publicclassAllTests{ publicstaticvoidmain(Stringargs[]){ junit.textui.TestRunner.run(suite()); } publicstaticTestsuite(){ TestSuitesuite=newTestSuite();
//加入子package中的AllTestsuite suite.addTest(org.redsoft.forum.dao.mysql.AllTests.suite()); //加入本packagelevel中的UnitTestcase suite.addTestSuite(MysqlDataSourceTest.class); returnsuite; }}//EOC1111173JunitTestSuitepackageorg.reJFCUnit一個(gè)Junit的Extension,用來(lái)測(cè)試Sw
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- Module 1 Unit 1 We lived in a small house(說(shuō)課稿)-2023-2024學(xué)年外研版(三起)英語(yǔ)五年級(jí)下冊(cè)
- 2024-2025學(xué)年高中歷史 第八單元 第23課 從局部抗戰(zhàn)到全面抗戰(zhàn)說(shuō)課稿 新人教版必修《中外歷史綱要(上)》
- Unit1 How can I get there?A Let's talk(說(shuō)課稿)-2024-2025學(xué)年人教PEP版英語(yǔ)六年級(jí)上冊(cè)001
- 《加法運(yùn)算律》第二課時(shí)(說(shuō)課稿)-2024-2025學(xué)年四年級(jí)上冊(cè)數(shù)學(xué)西師大版
- 2023-2024學(xué)年北京版(2013)小學(xué)信息技術(shù)第一冊(cè)3.21繪制曲線圖形(說(shuō)課稿)
- 現(xiàn)代辦公環(huán)境的綠色改造與節(jié)能減排技術(shù)
- 惠州2025年廣東惠州龍門縣農(nóng)業(yè)農(nóng)村局招聘編外人員筆試歷年參考題庫(kù)附帶答案詳解
- 現(xiàn)代建筑裝飾的多元化風(fēng)格探討
- 5 電磁鐵 說(shuō)課稿-2024-2025學(xué)年科學(xué)六年級(jí)上冊(cè)教科版
- 德陽(yáng)2025年四川德陽(yáng)廣漢市衛(wèi)生系統(tǒng)事業(yè)單位招聘編外聘用人員44人筆試歷年參考題庫(kù)附帶答案詳解
- 售后服務(wù)經(jīng)理的競(jìng)聘演講
- 新概念英語(yǔ)第2冊(cè)課文(完整版)
- 慢加急性肝衰竭護(hù)理查房課件
- 文件丟失應(yīng)急預(yù)案
- 全球職等系統(tǒng)GGS職位評(píng)估手冊(cè)
- 云南華葉投資公司2023年高校畢業(yè)生招聘1人筆試參考題庫(kù)(共500題)答案詳解版
- 專項(xiàng)法律意見書(私募基金管理人重大事項(xiàng)變更)-詳細(xì)版
- 深圳市社會(huì)保險(xiǎn)參保證明
- 2023年國(guó)家護(hù)理質(zhì)量數(shù)據(jù)平臺(tái)
- 雅思大綱6500詞詞匯表完美打印亂序版
- 《通過(guò)練習(xí)學(xué)習(xí)有機(jī)反應(yīng)機(jī)理》福山透三氫劍魔漢化
評(píng)論
0/150
提交評(píng)論