LLT(Low Level Test)通常由開發(fā)人員自測,它包括單元測試(Unit Test)委可、集成測試(Integration Test)、模塊系統(tǒng)測試(Module System Test)腊嗡、系統(tǒng)集成測試(BBIT)着倾,一般我們最關(guān)注的是UT(單元測試)和IT(集成測試)。
測試替身
Test Double(測試替身)包含了dummy, fake, mock, stub, spy 五種不同的類型燕少,這里我們引用Martin Fowler的經(jīng)典論述:
- Dummy objects are passed around but never actually used. Usually they are just used to fill parameter lists.
Fake objects actually have working implementations, but usually take some shortcut which makes them not suitable for production (an in memory database is a good example).- Stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test.
- Spies are stubs that also record some information based on how they were called. One form of this might be an email service that records how many messages it was sent.
- Mocks are what we are talking about here: objects pre-programmed with expectations which form a specification of the calls they are expected to receive.
簡單總結(jié)一下:
- Dummy:被用來僅僅作為填充參數(shù)列表的對象卡者,實際上不會用到它們,對測試結(jié)果也沒有任何影響棺亭;
- Fake:對一些系統(tǒng)進行裁剪之后形成的可運行的一套實現(xiàn)虎眨,跟源系統(tǒng)相比有一些(甚至很大)區(qū)別,不能上生產(chǎn)環(huán)境镶摘,但是作為測試使用非常適合嗽桩,能提前暴露很多問題,例如一個內(nèi)存版的數(shù)據(jù)庫凄敢;
- Stub:為被測試對象提供數(shù)據(jù)碌冶,沒有任何行為,往往是測試對象依賴關(guān)系的上游涝缝;
- Spy:被依賴對象的代理扑庞,行為往往由被代理的真實對象提供,代理的目的是為了斷言程序運行的正確性拒逮。比如我們針對一個發(fā)郵件服務(wù)做 spy罐氨,調(diào)用結(jié)束后,需要看下調(diào)用了幾次滩援,這時候就用到了這份信息栅隐;
- Mock:重點在于 expectation!也就是說,我們對于這次調(diào)用在遇到各種情況時應(yīng)該怎么處理租悄,提前指定好規(guī)范)谨究。
其中,Dummy 和 Fake 好理解泣棋,一個是沒啥用胶哲,只是占位符,另一個是基本上啥都能干潭辈,比真實的系統(tǒng)差點意思鸯屿,但基本上能覆蓋大部分場景。而對于 spy萎胰,通常我們不太區(qū)分它和 stub碾盟,可以一起理解。
那么問題來了技竟,Mock 說的是你要明確你對每次調(diào)用的 expectation冰肴,需要寫代碼來指明什么情況下要怎么做,而 Fake 好像也是這個意思榔组,區(qū)別在于這個代碼可能不用你寫(因為開源社區(qū)有一些現(xiàn)成可用的)熙尉。那么它們根本區(qū)別是什么呢?
把握住這三點即可:
- Fake => working implementations
- Mock => predefined behavior
- Stub => predefined values
In state verification you have the object under testing perform a certain operation, after being supplied with all necessary collaborators. When it ends, you examine the state of the object and/or the collaborators, and verify it is the expected one.
In behaviour verification, on the other hand, you specify exactly which methods are to be invoked on the collaboratos by the SUT, thus verifying not that the ending state is correct, but that the sequence of steps performed was correct.
Mock 和 Stub 的區(qū)別在于搓扯,前者是行為检痰,后者是狀態(tài)∠峭疲基于 Mock 做的是 behavior-based verification, 基于 Stub 做的是 State-based verification铅歼,這跟驗證的方法有關(guān)。而 Mock 和 Fake 的區(qū)別在于换可,你在寫單測的時候椎椰,需不需要構(gòu)建出一個 working implementation,還是說只要預(yù)設(shè)一些行為的響應(yīng)即可沾鳄。
當(dāng)然啦慨飘,這些概念都是一些陽春白雪的東西,實際工作中译荞,還是需要適當(dāng)接地氣一些瓤的,比如:跟普通碼農(nóng)打交道的時候,說Mock就夠了吞歼,跟普通測試人員就說”打樁“圈膏。
常用工具
JUnit5 + Hamcrest + Mockito
在面向Spring編程的年代,這就是最佳組合拳篙骡,其他諸如TestNG本辐、PowerMock等等桥帆,了解一下就好了医增,畢竟學(xué)習(xí)成本擺在那里慎皱。MockServer
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-netty</artifactId>
<version>5.15.0</version>
</dependency>
MockServer就是上文提到的Stub,使用場景舉例:模擬一個微信公眾號的后臺響應(yīng)叶骨。
- Embedded Redis
<dependency>
<groupId>it.ozimov</groupId>
<artifactId>embedded-redis</artifactId>
<version>0.7.3</version>
</dependency>
這是一個典型的Fake茫多,能夠?qū)崿F(xiàn)基本的Redis操作,但是某些高階特性忽刽,比如Stream還是無法實現(xiàn)天揖。
- Wix Embedded MySQL
<dependency>
<groupId>com.wix</groupId>
<artifactId>wix-embedded-mysql</artifactId>
<version>4.6.2</version>
</dependency>
比H2強大不少,可以指定MySQL版本跪帝,5.7非常好用今膊,8.0.x貌似有bug,推薦MariaDB4j伞剑。
- MariaDB4j
<dependency>
<groupId>ch.vorburger.mariaDB4j</groupId>
<artifactId>mariaDB4j-springboot</artifactId>
<version>3.0.1</version>
<scope>test</scope>
</dependency>
又一個非常好用的一個Fake斑唬,目前默認數(shù)據(jù)庫版本是10.2.11,與Flyway等數(shù)據(jù)庫工具配合使用時需要注意版本支持情況黎泣,推薦使用8.5.x版本恕刘。
- Test Containers
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.19.1</version>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mysql</artifactId>
<version>1.19.1</version>
</dependency>
基于Docker鏡像,可以實現(xiàn)各種Fake抒倚,有興趣的童鞋可以自行了解下褐着。
- Flapdoodle Embedded MongoDB
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo.spring31x</artifactId>
<version>4.9.3</version>
</dependency>
MongoDB的一個老牌Fake了,注意區(qū)分2x和3x版本托呕。
知識擴展
Unit test: Specify and test one point of the contract of single method of a class. This should have a very narrow and well defined scope. Complex dependencies and interactions to the outside world are stubbed or mocked.
Integration test: Test the correct inter-operation of multiple subsystems. There is whole spectrum there, from testing integration between two classes, to testing integration with the production environment.
-
Smoke test (aka sanity check): 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 testing is both an analogy with electronics, where the first test occurs when powering up a circuit (if it smokes, it's bad!)...
- ... and, apparently, with plumbing, where a system of pipes is literally filled by smoke and then checked visually. If anything smokes, the system is leaky.
Regression test: A test that was written when a bug was fixed. It ensures that this specific bug will not occur again. The full name is "non-regression test". It can also be a test made prior to changing an application to make sure the application provides the same outcome
Acceptance test: Test that a feature or use case is correctly implemented. It is similar to an integration test, but with a focus on the use case to provide rather than on the components involved.
System test: Tests a system as a black box. Dependencies on other systems are often mocked or stubbed during the test (otherwise it would be more of an integration test).
Pre-flight check: Tests that are repeated in a production-like environment, to alleviate the 'builds on my machine' syndrome. Often this is realized by doing an acceptance or smoke test in a production like environment.