單元測試,整合測試概念與各種工具介紹
我們在工作中會聽到很多關於測試的名詞,如單元測試UT,整合測試Integration Test,端到端測試end-to-end 等等。
我認為,大部分人其實都僅僅是有一個模糊的認識。他們並未真正清楚每種測試的含義。
當領導強調要做xx測試時,就隨意的把概念扔給下屬,其實自己都不清楚為什麼要做,怎麼做。
而據我所知,國內大部分公司更是糟糕。每個開發人員都完全投入到寫程式碼中,甚至不願意花時間寫最最最基本的單元測試。
同時測試的重要性已經不僅僅是對質量的保證了。在流行敏捷開發的今天,測試已經成為敏捷開發的重要組成部分。
因此我們是否真的明白測試的含義。它們的必要性,應該如何定義它們,它們之間有嚴格的界限嗎,每種測試型別適合的工具是什麼。
這些都是我們必須思考的問題。
現在,對常見測試做一個探討,如果有什麼建議或不認同,歡迎拍磚。
單元測試 Unit Test
單元測試的定義:
A unit test is a test written by the programmer to verify that a relatively small piece of code is doing what it is intended to do. They are narrow in scope, they should be easy to write and execute, and their effectiveness depends on what the programmer considers to be useful. The tests are intended for the use of the programmer, they are not directly useful to anybody else, though, if they do their job, testers and users downstream should benefit from seeing less bugs.
上面的定義強調了
1,UT應由開發人員寫,給開發人員使用。
2,UT測試的應該是小段程式碼,目的是開發人員確定原始碼做了希望它做的事。
再來看看TestNG的作者是如何概括UT的:
unit testing (testing a class in isolation of the others)
首先他強調了,一個UT case只能針對一個類。不應該在一個UT case中測試一個以上的類。
其次一個重點是:一個類的UT必須是完全獨立的,不應再和其他部分程式碼有任何互動。這也是我們要使用Mockito等mock框架的原因,這樣在測試程式碼時,當必須和其他物件互動時,就使用mock物件來保持UT的獨立性。
此外,UT 可以是自動的,也可以是手動的。
單元測試的工具:
大家熟知的JUnit,以及TestNG;.net專用的NUnit;VS2010開始直接提供的測試框架。
c/c++專用的CppUnit,PHP專用的PHPUnit。
Mockito,easyMock等mock框架也是重要組成部分。
另外,單元測試有一個覆蓋率的問題。就是指一個UT呼叫了被測試類中多少程式碼。一個不負責任的程式設計師寫的UT也許只測試了類中的一個方法,而負責的程式設計師寫的UT應該覆蓋掉被測試類中所有主要方法。
Cobertura是自動檢測UT覆蓋率並生成報告的一個開源工具。 將這個工具整合到每天的daily build中是一種很好的做法。
要求覆蓋率要達到100%是無理且無意義的,通常80~90%就行了。原因有兩點:
1,是否UT必須覆蓋所有的程式碼?
讓我們來看看JUnit官網的回答:
No, just test everything that could reasonably break.
Be practical and maximize your testing investment. Remember that investments in testing are equal investments in design. If defects aren't being reported and your design responds well to change, then you're probably testing enough. If you're spending a lot of time fixing defects and your design is difficult to grow, you should write more tests.
If something is difficult to test, it's usually an opportunity for a design improvement. Look to improve the design so that it's easier to test, and by doing so a better design will usually emerge.
2,對於一般專案,到達到100%的覆蓋率基本不可能。
儘管我們現在有各種測試框架,各種mock框架,但在很多可能的情況下都會遇到難於,甚至無法測試的程式碼。
例如private方法,唯一測試的方式就是利用反射機制。
如果測試覆蓋率變成了一個目標,而不是提高程式碼質量,防範bug的手段,那就是本末倒置了。
但是,如果為了方便測試而修改原始碼,並不是不可理喻的行為。如果你發現程式碼難於測試,則有可能是程式碼存在一些問題。
例如,在另一篇關於Mockito的文章中提到了,為了將mock物件傳遞到被測試的物件中,需要原始碼提供get/set方法,否則測試起來就很麻煩。
在實踐中我們會慢慢發現,程式碼不夠獨立,缺乏應有的get/set方法,結構不合理,方法應該被重構等等問題,會導致難於被測試。
因此UT也是對原始碼的一次review。一個有責任心的程式設計師,應該從UT中看到更多。永遠記住一條:我們不是在為了測試而測試,我們應該思考的更多。
3, 對於每一個測試方法來說,所謂的獨立到底是什麼?
最近遇到了關於UT的這樣一個問題:每一個測試方法到底要多麼“獨立”?
例如,在一個被測試類中有兩個方法:
public class app{
public void method1(){
String testString = method2(); //在方法一中呼叫了方法二。
}
public String method2(){
}
}
於是問題是,當我們寫第一個方法method1的測試方法method1Test時,我們應該mock的是什麼?我們應該mock方法二?還是不需要mock?
也就是說,當我們測試方法一時,應該把方法二看做外部的東西,通過mock方法二,僅僅測試方法一。
或是不使用mock,直接呼叫方法一(實際同時也呼叫了方法二)。因此相當於同時測試了方法一和方法二的行為。
我本來是傾向於第一個做法,將方法一作為完全獨立的被測試的部分,通過mock方法二,讓方法一在被測試時,和其他類或方法完全沒有關聯。
但是和同事討論以後,看法發生了變化。看起來第二種做法,同時測試了方法一和二。但是在一個類中,各個方法本來就不是完全獨立的。
因此有成員變數的存在。成員變數的值天生的就可以在各個方法中傳遞。
因此結論是:UT的獨立範圍,永遠是一個類。測試時,當呼叫了其他類的程式碼時,就應該使用mock將被測試類和其他類隔離開。
測試時,如果呼叫了同一個類的其他方法時,是不需要使用mock的。因為呼叫的是同一個類中的程式碼。
整合測試 Integration Test
整合測試的定義:
An integration test is done to demonstrate that different pieces of the system work together. Integration tests cover whole applications, and they require much more effort to put together. They usually require resources like database instances and hardware to be allocated for them. The integration tests do a more convincing job of demonstrating the system works (especially to non-programmers) than a set of unit tests can, at least to the extent the integration test environment resembles production.
因此,
1,通常會在指定的環境執行整合測試。
2,整合測試的目的旨在測試各個元件間是否能互相配合,正常工作。和UT一樣,整合測試也是為了看程式碼是否按"設計或期望的方式"工作。
3,整合測試的範圍比較寬泛:範圍可以小到,如果在一個UT case中涉及了多個類,就可以認為這是整合測試。大到整個系統,從後臺到前端所有元件。Integration tests can be performed at the unit level or the system level.
整合測試往往會涉及外部元件,如資料庫,硬體,網路等等。
4,整合測試通常應由專門的測試人員完成。
HP's (mercury) QTP or Borland's Silktest 都可以做自動化的整合測試和迴歸測試。
在敏捷開發的今天,一個概念變得更重要了:CI (Continuous Integration)
本文並不打算探討CI,因為這又是一個很大的話題。CI 和整合測試經常結合的很緊密。
CC(CruiseContoller), 是現在常用的CI工具之一。
冒煙測試 Smoke Test
我很喜歡這個名字,它的來源很有意思。Smoke Test是從電子硬體測試來的,表示第一次對硬體原型prototype加電時,如果硬體冒煙了,就表示產品有問題。
從smoke test名字的來由我們就可以看出,smoke test是比較初級的測試(a quick test),僅僅是為了檢查各個元件是否能一起工作,而並不去深究功能上是否正確。
A simple integration test where we just check that when the system under test is invoked it returns normally and does not blow up.
注意smoke test一般是大範圍的整合測試。通常可以是整個系統/端到端的測試。
例如,一個專案:客服人員在終端上輸入使用者的資訊,終端使用者可以在網際網路上查詢到自己的資訊。
此時的smoke test就可以是在終端上輸入某個使用者的資訊,然後查詢通過web程式是否可以找到這個使用者的資訊,而不必關注該使用者資訊的正確性。
因此有人認為它是測試的第一步:first tests on which testers can conclude if they will continue testing. 但並非是必須的。
迴歸測試 Regression Test
A test that was written when a bug was fixed. It ensure that this specific bug will not occur again. The full name is "non-regression test".
A regression test re-runs previous tests against the changed software to ensure that the changes made in the current software do not affect the functionality of the existing software."
上面兩種觀點並不一致:一種認為迴歸測試是為了覆蓋fix的bug,另一種認為迴歸測試是覆蓋新新增的功能。
但是我們可以看到這兩種說法的統一之處:
迴歸測試是為了覆蓋系統新發生的變化而進行的測試。
迴歸測試可以由測試人員編寫,也可以由開發人員編寫。
形式上可以就是一個覆蓋新功能或者bug的UT,或者測試人員寫的專用測試工具。
端到端測試 End-To-End Test
validates the entire application to ensure that it satisfies previously established acceptance criteria and performs as an integrated system. The purpose of system testing is not to test all possible positive and negative conditions (reserved for functional and integration testing), but to instead execute business functions and infrastructure management (i.e.; batch processing, system security features, backup and recovery etc.) in an isolated and controlled environment to validate that a quality system is ready for production.
因此端到端測試強調的是全面的,包含硬體環境等等的測試。
最後一句話很重要:是為了上production。
因為覆蓋面廣,因此一般都是人工測試。通常也沒有專用工具來做端到端測試。
功能測試 Functional Test
Functional tests are related to integration tests, but refer more specifically to tests that test an entire system or application with all of the code running together, almost a super integration test.
非功能測試 Non-funtional Test
非功能測試在我公司,主要指除程式主要功能以外的附屬功能。如GMI,log,report,failover等,為了監控程式執行,實現程式穩定性而附加的功能。
acceptance tests 不知道中文是什麼
總的來說,acceptance tests 和 Functional Test是非常接近的,甚至有些地方認為二者完全一樣。二者的異同在於:
functional testing: This is a verification activity; did we build the thing right? Does the software meet the business requirements?
For this type of testing we have test cases that cover all the possible scenarios we can think of, even if that scenario is unlikely to exist "in the real world". When doing this type of testing, we aim for maximum code coverage. We use any test environment we can grab at the time, it doesn't have to be "production" caliber, so long as it's usable.
acceptance testing: This is a validation activity; did we build the right thing? Is this what the customer really needs?
This is usually done in cooperation with the customer, or by an internal customer proxy (product owner). For this type of testing we use test cases that cover the typical scenarios under which we expect the software to be used. This test must be conducted in a "production-like" environment, on hardware that is the same as, or close to, what a customer will use. This is when we test our "ilities":
-
Reliability, Availability: Validated via a stress test.
-
Scalabilitiy: Validated via a load test.
-
Usability: Validated via an inspection and demonstration to the customer. Is the UI configured to their liking? Did we put the customer branding in all the right places? Do we have all the fields/screens they asked for?
-
Security (aka, Securability, just to fit in): Validated via demonstration. Sometimes a customer will hire an outside firm to do a security audit and/or intrusion testing.
-
Maintainability: Validated via demonstration of how we will deliver software updates/patches.
-
Configurability: Validated via demonstration of how the customer can modify the system to suit their needs.
acceptance testing傾向測試的是儘量真實的使用者體驗。測試是否完成使用者的實際需求,在完全和production相同或儘量類似的環境中。
同時,二者都應該是完全的黑盒測試。
下面這句話很重要:
Acceptance Tests/Criteria (in Agile Software Development) are usually created by business customers and expressed in abusiness domain language. These are high-level tests to test the completeness of a user story or stories 'played' during any sprint/iteration.
a business domain language指非開發人員可以理解的類自然語言。現在比較流行的框架是JBehave。
Acceptance test cards are ideally created during sprint planning or iteration planning meeting, before development begins so that the developers have a clear idea of what to develop. Sometimes (due to bad planning!) acceptance tests may span multiple stories (that are not implemented in the same sprint) and there are different ways to test them out during actual sprints. One popular technique is to mock external interfaces or data to mimic other stories which might not be played out during an iteration (as those stories may have been relatively lower business priority). A user story is not considered complete until the acceptance tests have passed.
也就是說在一個sprint中,如何確定開發人員是否完成了一個功能?標準就是對應的acceptance test通過。
這也就是Acceptance-Test Driven Development的來由。
再總結一下白盒和黑盒測試:
White box testing means that you know the input, you know the inner workings of the mechanism and can inspect it and you know the output.
With black box testing you only know what the input is and what the output should be.
因此通常情況下,UT是白盒測試,但是有時候也可以是黑盒。例如現在流行的測試驅動開發,UT是根據需求先於程式碼被創建出來的,此時的UT只知道我們有什麼,然後希望看到什麼,所以就是黑盒。
而其他的測試則大部分是黑盒測試。但是整合測試和迴歸測試也可以是白盒。
前面提到的,測試對原始碼覆蓋率的問題,理論上只有對白盒測試,這個指標才有意義。對與黑盒測試,程式碼覆蓋率根本就不make sense。
對與某些領導提的,“我們這個整合測試要100%覆蓋原始碼”,基本就是腦子被門擠了。
但是需要說明的是,黑盒測試一樣可以計算程式碼覆蓋率,雖然我認為沒有什麼意義。
使用cobertura注入後,就可以方便的得到任何一個整合測試對原始碼的覆蓋率。具體可以參考我寫的cobertura的介紹。
最後提一下目前流行的開源mock框架(因為我是developer所以尤其關注UT相關的工具)
- 較早的時候EasyMock是最流行的工具之一。
- Mocktio
EasyMock之後流行的mock工具。特點是句法結構清晰,易理解。文件比較全,由於比較流行所以遇到問題容易找到解答。
Mockito足以解決大部分UT的mock需求。總得來說是非常不錯的mock工具。
- PowerMock
其實這個工具是在EasyMock和Mockito上擴展出來的,目的是為了解決EasyMock和Mockito不能解決的問題。
我在介紹PowerMock的文章中詳細說明了這個工具可用於哪些特殊mock需求。
缺點是該工具文件較少,例子不全,使用時遇到問題不容易解決。同時PowerMock需要和EasyMock或Mockito配合一起使用。
因此必須先掌握EasyMock或Mockito。
- Jmokit
http://code.google.com/p/jmockit/
另外一個類似PowerMock的工具。這個工具我不準備介紹了(最近看mock工具看的審美疲勞了)。
該工具實現和PowerMock相同的功能因此如果不喜歡PowerMock可以選擇這個工具。
相對PowerMock, 這個工具文件比較全,適於學習。
另外還有Jmock等等mock工具,非常之多也很雜。此外也有一些專用Mock工具,比如MockFTPServer。
開源的世界很有意思呀~