使用SpringBoot編寫一個(gè)優(yōu)雅的單元測(cè)試
什么是單元測(cè)試
當(dāng)一個(gè)測(cè)試滿足下面任意一點(diǎn)時(shí),測(cè)試就不是單元測(cè)試 (by Michael Feathers in 2005):
- 與數(shù)據(jù)庫交流
- 與網(wǎng)絡(luò)交流
- 與文件系統(tǒng)交流
- 不能與其他單元測(cè)試在同一時(shí)間運(yùn)行
- 不得不為運(yùn)行它而作一些特別的事
如果一個(gè)測(cè)試做了上面的任何一條,那么它就是一個(gè)集成測(cè)試。
不要用 Spring 編寫單元測(cè)試
@SpringBootTest
class OrderServiceTests {
@Autowired
private OrderRepository orderRepository;
@Autowired
private OrderService orderService;
@Test
void payOrder() {
Order order = new Order(1L, false);
orderRepository.save(order);
Payment payment = orderService.pay(1L, "4532756279624064");
assertThat(payment.getOrder().isPaid()).isTrue();
assertThat(payment.getCreditCardNumber()).isEqualTo("4532756279624064");
}
}這是一個(gè)單元測(cè)試嗎?首先@SpringBootTest注解加載了整個(gè)應(yīng)用上下文,而僅僅是為了注入兩個(gè) Bean。
另一個(gè)問題是我們需要讀取和寫入訂單到數(shù)據(jù)庫,這也是集成測(cè)試的范疇。
Spring Framework 文檔對(duì)于單元測(cè)試的描述
真正的單元測(cè)試運(yùn)行的非??欤?yàn)椴恍枰\(yùn)行時(shí)去裝配基礎(chǔ)設(shè)施。強(qiáng)調(diào)將真正的單元測(cè)試作為開發(fā)方法的一部分可以提高你的生產(chǎn)力。
編寫 “可單元測(cè)試” 的 Service
Spring Framework 文檔對(duì)于單元測(cè)試的另一描述
依賴注入可以讓你的代碼減少依賴。POJO 可以讓你的應(yīng)用可以通過new操作符在 JUnit 或 TestNG 上進(jìn)行測(cè)試,不需要任何的 Spring 和其他容器
考慮如果編寫這樣的 Service,它方便進(jìn)行單元測(cè)試嗎?。?/p>
@Service
public class BookService {
@Autowired
private BookRepository repository;
// ... service methods
}不方便,因?yàn)?code>BookRepository通過@Autowired被注入到 Service 中,并且repository是一個(gè)私有變量,這就限定了外界只能通過 Spring 或其它依賴注入容器(或反射)設(shè)置這個(gè)值,那么單元測(cè)試如果不想加載整個(gè) Spring 容器,那么它就無法使用這個(gè) Service。
而如果這樣寫,使用構(gòu)造方法注入,外界也可以通過new去自行傳遞Repository,這樣即使沒有 Spring,外界也能進(jìn)行快速的測(cè)試。這可能也是 Spring 不推薦屬性注入的原因。
@Service
public class BookService {
private BookRepository repository;
@Autowired
public BookService(BookRepository repository) {
this.repository = repository;
}
}編寫單元測(cè)試
Mockito 介紹
前面的知識(shí)表明,單元測(cè)試就是對(duì)一個(gè)系統(tǒng)中的某個(gè)最小單元的邏輯正確性的測(cè)試,通常是對(duì)一個(gè)方法來進(jìn)行測(cè)試,因?yàn)橹粶y(cè)試邏輯正確性,所以這個(gè)測(cè)試是獨(dú)立的,不與任何外界環(huán)境相關(guān),比如不需要連接數(shù)據(jù)庫,不訪問網(wǎng)絡(luò)和文件系統(tǒng),不依賴其他單元測(cè)試。但是現(xiàn)實(shí)的業(yè)務(wù)邏輯中往往有很多復(fù)雜錯(cuò)綜的依賴關(guān)系,比如你想對(duì) Service 進(jìn)行單元測(cè)試,那么它要依賴一個(gè)數(shù)據(jù)庫持久層的 Repository 對(duì)象,這時(shí)候就難辦了,若創(chuàng)建了一個(gè) Repository 便連接了數(shù)據(jù)庫,連接了數(shù)據(jù)庫便不是一個(gè)獨(dú)立的單元測(cè)試。
Mockito 是一個(gè)用來在單元測(cè)試中快速模擬那些需要與外界環(huán)境溝通的對(duì)象,以便我們快速的、方便的進(jìn)行單元測(cè)試而不用啟動(dòng)整個(gè)系統(tǒng)。
下面的代碼就是 Mockito 的一個(gè)基礎(chǔ)使用,Mock 意為偽造。
// 通過mock方法偽造一個(gè)orderRepository的實(shí)現(xiàn),這個(gè)實(shí)現(xiàn)目前什么都不會(huì)做 orderRepository = mock(OrderRepository.class); // 通過mock方法偽造一個(gè)paymentRepository的實(shí)現(xiàn),這個(gè)實(shí)現(xiàn)目前什么都不會(huì)做 paymentRepository = mock(PaymentRepository.class) // 創(chuàng)建一個(gè)Order對(duì)象以便一會(huì)兒使用 Order order = new Order(1L, false); // 使用when方法,定義當(dāng)orderRepository.findById(1L)被調(diào)用時(shí)的行為,直接返回剛剛創(chuàng)建的order對(duì)象 when(orderRepository.findById(1L)).thenReturn(Optional.of(order)); // 使用when方法,定義當(dāng)paymentRepository.save(任何參數(shù))被調(diào)用時(shí)的行為,直接返回傳入的參數(shù)。 when(paymentRepository.save(any())).then(returnsFirstArg());
單元測(cè)試
class OrderServiceTests {
private OrderRepository orderRepository;
private PaymentRepository paymentRepository;
private OrderService orderService;
@BeforeEach
void setupService() {
orderRepository = mock(OrderRepository.class);
paymentRepository = mock(PaymentRepository.class);
orderService = new OrderService(orderRepository, paymentRepository);
}
@Test
void payOrder() {
Order order = new Order(1L, false);
when(orderRepository.findById(1L)).thenReturn(Optional.of(order));
when(paymentRepository.save(any())).then(returnsFirstArg());
Payment payment = orderService.pay(1L, "4532756279624064");
assertThat(payment.getOrder().isPaid()).isTrue();
assertThat(payment.getCreditCardNumber()).isEqualTo("4532756279624064");
}
}現(xiàn)在我們即使不想連接數(shù)據(jù)庫,也可以通過mock來給定一個(gè) Repository 的其他實(shí)現(xiàn),這樣這個(gè)方法可以在毫秒內(nèi)完成。
也可以使用Mockito
@ExtendWith(MockitoExtension.class)
class OrderServiceTests {
@Mock
private OrderRepository orderRepository;
@Mock
private PaymentRepository paymentRepository;
@InjectMocks
private OrderService orderService;
// ...
}以上就是使用SpringBoot編寫一個(gè)優(yōu)雅的單元測(cè)試的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot單元測(cè)試的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java利用Netty時(shí)間輪實(shí)現(xiàn)延時(shí)任務(wù)
時(shí)間輪是一種可以執(zhí)行定時(shí)任務(wù)的數(shù)據(jù)結(jié)構(gòu)和算法。本文將為大家詳細(xì)講解一下Java如何利用Netty時(shí)間輪算法實(shí)現(xiàn)延時(shí)任務(wù),感興趣的小伙伴可以了解一下2022-08-08
win10下定時(shí)運(yùn)行與開機(jī)自啟動(dòng)jar包的方法記錄
這篇文章主要給大家介紹了關(guān)于win10下定時(shí)運(yùn)行與開機(jī)自啟動(dòng)jar包的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
Java無界阻塞隊(duì)列DelayQueue詳細(xì)解析
這篇文章主要介紹了Java無界阻塞隊(duì)列DelayQueue詳細(xì)解析,DelayQueue是一個(gè)支持時(shí)延獲取元素的無界阻塞隊(duì)列,隊(duì)列使用PriorityQueue來實(shí)現(xiàn),隊(duì)列中的元素必須實(shí)現(xiàn)Delayed接口,在創(chuàng)建元素時(shí)可以指定多久才能從隊(duì)列中獲取當(dāng)前元素,需要的朋友可以參考下2023-12-12
Java輕松實(shí)現(xiàn)在Excel中添加超鏈接功能
這篇文章主要為大家詳細(xì)介紹了Java如何輕松實(shí)現(xiàn)在Excel中添加超鏈接功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-01-01
SpringBoot整合OpenCV的實(shí)現(xiàn)示例
這篇文章主要介紹了SpringBoot整合OpenCV的實(shí)現(xiàn)示例。文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
Java代碼實(shí)現(xiàn)隨機(jī)生成漢字的方法
今天小編就為大家分享一篇關(guān)于Java代碼實(shí)現(xiàn)隨機(jī)生成漢字的方法,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-03-03

