程序測(cè)試對(duì)保障應(yīng)用程序來(lái)說(shuō),其重要性不可忽視昵慌。JUnit是我們必須掌握的測(cè)試框架棍苹,大多數(shù)測(cè)試框架和工具都是在此基礎(chǔ)上擴(kuò)展而來(lái)兴垦。直接使用JUnit測(cè)試基于Spring的應(yīng)用存在諸多不便肤视,我們需要花費(fèi)大量的經(jīng)理去應(yīng)付測(cè)試現(xiàn)場(chǎng)恢復(fù)档痪、訪問(wèn)測(cè)試數(shù)據(jù)操作結(jié)果等工作。但是DbUnit的出現(xiàn)邢滑,這些問(wèn)題有了很好的解決方案腐螟,DbUnit對(duì)測(cè)試DAO層提供了強(qiáng)大的支持,大大提高了編寫(xiě)測(cè)試用例的效率和質(zhì)量殊鞭。(文章文字摘自《Spring3.X企業(yè)應(yīng)用開(kāi)發(fā)實(shí)踐--陳雄華、林開(kāi)雄 著》)
1. 單元測(cè)試的好處
編寫(xiě)代碼的過(guò)程中尼桶,總是反復(fù)調(diào)試保證能編譯通過(guò)操灿,但是代碼編譯通過(guò),只能說(shuō)明語(yǔ)法正確泵督,無(wú)法保證邏輯是正確的趾盐。單元測(cè)試可以作保證,有了單元測(cè)試,可以自信地提交代碼救鲤,減少后顧之憂久窟。帶來(lái)好處如下:
- 是軟件質(zhì)量最簡(jiǎn)單、最有效的包裝;
- 是目標(biāo)代碼最清晰的本缠、最有效的文檔;
- 可以?xún)?yōu)化目標(biāo)代碼的設(shè)計(jì)
- 是代碼重構(gòu)的保障;
- 是回歸測(cè)試和持續(xù)集成的基石
2.單元測(cè)試的誤解
- 誤解一:影響開(kāi)發(fā)進(jìn)度
一旦編碼完成斥扛,開(kāi)發(fā)人員總數(shù)迫切的希望進(jìn)行軟件的集成工作,這樣就可以看到系統(tǒng)實(shí)際運(yùn)行效果丹锹。而單元測(cè)試這樣的活動(dòng)稀颁,就被看成是影響進(jìn)度的原因之一,推遲整個(gè)系統(tǒng)進(jìn)行集成測(cè)試的時(shí)間楣黍。但是這樣一意孤行導(dǎo)致的結(jié)果經(jīng)常是:軟件無(wú)法運(yùn)行匾灶,更進(jìn)一步說(shuō),編程人員不得不花費(fèi)大量的時(shí)間去追蹤這些隱藏在獨(dú)立單元內(nèi)簡(jiǎn)單的BUG上租漂,有些有報(bào)錯(cuò)信息還好說(shuō)阶女,但是有些屬于邏輯錯(cuò)誤,這是機(jī)器無(wú)法通知編程人員的哩治。
在實(shí)際工作中秃踩,進(jìn)行完整計(jì)劃的單元測(cè)試和編寫(xiě)實(shí)際的代碼所花費(fèi)的時(shí)間和精力應(yīng)該是一樣的。一旦完成單元測(cè)試工作锚扎,許多BUG都被糾正了吞瞪,開(kāi)發(fā)人員也就能進(jìn)行系統(tǒng)集成工作。
- 誤解二:增加開(kāi)發(fā)成本
如果不重視程序中那些未被發(fā)現(xiàn)的BUG可能帶來(lái)的后果驾孔,嚴(yán)重程度可以從一個(gè)BUG引起的用戶(hù)體驗(yàn)不適到系統(tǒng)崩潰芍秆。這種后果可能被軟件開(kāi)發(fā)人員忽視(PS:畢竟很多公司都是測(cè)試和開(kāi)發(fā)是同一人做的,同時(shí)做兩份工作換我也不開(kāi)心:( )翠勉,BUG發(fā)現(xiàn)的越早妖啥,維護(hù)的費(fèi)用越少。對(duì)比復(fù)雜而曠日持久的集成測(cè)試來(lái)說(shuō)对碌,單元測(cè)試所需的費(fèi)用是很低的荆虱。
- 誤解三:用System.out.print跟蹤和運(yùn)行程序就夠了
很多懶人使用Ssytem.out.print來(lái)輸出結(jié)果,判斷程序代碼是否正確朽们,不想寫(xiě)測(cè)試用例怀读,除了懶,另外就是沒(méi)有時(shí)間骑脱,但是用這種方式輸出結(jié)果來(lái)進(jìn)行測(cè)試菜枷,不僅效率低下,而且容易發(fā)生錯(cuò)誤叁丧、
- 誤解四:存在太多無(wú)法測(cè)試的東西
在編碼的時(shí)候啤誊,確實(shí)存在一些看起來(lái)比較難測(cè)試的代碼岳瞭,但是有些并不是無(wú)法測(cè)試,大多數(shù)時(shí)候主要是前期做系統(tǒng)設(shè)計(jì)的時(shí)候沒(méi)有考慮到測(cè)試方面的問(wèn)題蚊锹,導(dǎo)致內(nèi)部耦合過(guò)緊瞳筏,過(guò)于依賴(lài)運(yùn)行環(huán)境,進(jìn)而表現(xiàn)出代碼很難被測(cè)試牡昆。
- 誤解五:測(cè)試代碼隨意寫(xiě)
編寫(xiě)測(cè)試代碼的時(shí)候總會(huì)抱著那些隨意的態(tài)度姚炕,沒(méi)去弄清測(cè)試的意圖,編寫(xiě)測(cè)試代碼是為了應(yīng)付人員而已迁杨,而編寫(xiě)的內(nèi)容和測(cè)試根本八輩子打不到一塊钻心,這樣子的單元測(cè)試跟沒(méi)測(cè)試沒(méi)什么區(qū)別。
3. 單元測(cè)試案例(DbUnit測(cè)試)
- 準(zhǔn)備~開(kāi)始
測(cè)試工具是由孔浩老師的視頻處的來(lái)的铅协,各位可以修改下然后應(yīng)用到自己的項(xiàng)目?jī)?nèi)
測(cè)試環(huán)境:
GitHub查看-
測(cè)試Bean:Branch
@Entity
@Table(name = "branch")
public class Branch {
private int id;
private String name;
private Subject subject;public Branch() { } public Branch(int id, String name) { this.id = id; this.name = name; } public Branch( int id, String name,Subject subject) { this.id = id; this.name = name; this.subject = subject; } @Id @GeneratedValue public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @OneToOne @JoinColumn(name = "sub_id",referencedColumnName = "id") public Subject getSubject() { return subject; } public void setSubject(Subject subject) { this.subject = subject; } }
3.測(cè)試DAO:BranchDao(其它方法略)
@Repository("branchDao")
public class BranchDao extends BaseDao<Branch> implements IBranchDao{
@Override
public Branch loadByName(String name) {
String hql="select branch from Branch branch where branch.name=?";
return (Branch)this.queryObject(hql,name);
}
}
測(cè)試的數(shù)據(jù)庫(kù)生成文件
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<branch id="1" name="測(cè)試1" sub_id="1"/>
<branch id="2" name="測(cè)試2" sub_id="1"/>
<branch id="3" name="測(cè)試3" sub_id="1"/>
<branch id="4" name="測(cè)試4" sub_id="1"/>
<branch id="5" name="測(cè)試5" sub_id="1"/>
<branch id="6" name="測(cè)試6" sub_id="1"/>
</dataset>-
單元測(cè)試類(lèi):BranchDaoTest
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/beans.xml")
public class BranchDaoTest extends AbstractDbUnitTestCase {
@Inject
private SessionFactory sessionFactory;
@Inject
private IBranchDao branchDao;
@Inject
private ISubjectDao subjectDao;@Before public void setUp() throws SQLException, IOException, DatabaseUnitException { //此時(shí)最好不要使用Spring的Transactional來(lái)管理捷沸,因?yàn)閐bunit是通過(guò)jdbc來(lái)處理connection,再使用spring在一些編輯操作中會(huì)造成事務(wù)shisu Session s = sessionFactory.openSession(); TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(s)); this.backupAllTable(); IDataSet ds = createDateSet("t_beans"); DatabaseOperation.CLEAN_INSERT.execute(dbunitCon,ds); } @After public void tearDown() throws DatabaseUnitException, SQLException, IOException { SessionHolder holder = (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); Session s = holder.getSession(); s.flush(); TransactionSynchronizationManager.unbindResource(sessionFactory); this.resumeTable(); } @Test public void testLoadByName(){ Branch branch=branchDao.loadByName("測(cè)試一"); assertEquals(branch.getId(), 1); } }
測(cè)試結(jié)果