测试双打简介
在編寫單元測試時,您會遇到許多協作者,并且他們都有非常特殊的行為,知道在正確的時間必須使用哪種測試兩倍可以使您的生活更輕松。
假
第一個是Dummy對象,它是最簡單的一個,Dummy只是您為滿足構造函數而傳遞的對象,它不會實現任何方法,也不會實現。
在測試課程時,我們不想使用記錄器做任何事情,那么我們該怎么辦?
例如,有一個帶有記錄器的PaymentService :
public interface Logger { void append(String text); } public class PaymentService { private Logger logger; public PaymentService(Logger logger) { this .logger = logger; } public PaymentRequest createPaymentRequest(Sale sale, CreditCard creditCard) { logger.append( "Creating payment for sale " + sale.toString()); throw new UnsupportedOperationException(); } }在開始編寫測試之前,我們必須滿足Logger類的依賴性,但是實際的實現對單元測試不利,日志可能會保存到文本文件中或將日志發送到其他地方,這破壞了隔離在測試中,我們也不想檢查日志中的任何內容,它們與我們擁有的業務邏輯無關,因此我們將為此實現一個Dummy。
public class LoggerDummy implements Logger { @Override public void append(String text) {} }就是它? 虛擬內部沒有代碼。 對于這種情況,我們內部不需要任何實現,并且我們準備編寫測試。
PaymentServiceShould { class PaymentServiceShould { @Test void create_payment_request() { LoggerDummy loggerDummy = new LoggerDummy(); Customer customer= new Customer( "name" , "address" ); Item item = new Item( "item" , 1000 ); List<Item> items= asList(item); Sale sale = new Sale(customer, items); CreditCard creditCard = new CreditCard(customer, "1" ); PaymentService paymentService = new PaymentService(loggerDummy); PaymentRequest actual = paymentService.createPaymentRequest(sale, creditCard); assertEquals( new PaymentRequest( 1000 , "1" ), actual); } }存根
存根稍微復雜一點,它們為我們的呼叫提供罐頭應答,它們仍然沒有任何邏輯,但是它們不會拋出錯誤,而是返回一個預定義的值。
在進行測試時,您希望測試具有確定性和可重復性,因此由于合作者的更改,測試不會在一段時間后停止工作。
現在, PaymentRequest必須包含信用卡操作員費用,該費用的費率由信用卡操作員定義,該費用由卡的前四位數字定義。要實現此目的,您必須創建一個存根并添加必要的內容更改PaymentService 。 第一步是實現存根和生產代碼所需的接口,這是您預先進行一些設計的部分,考慮存根中應該包含哪些參數以及應該返回什么,而不用考慮內部實現,但與該協作者的合同是:
public interface OperatorRate { int feeRate(String operator) }使用定義的接口,我們可以開始編寫存根:
public class OperatorRateStub implements OperatorRate { private int rate; public OperatorRateStub( int rate){ this .rate = rate; } @Override public int feeRate(String operator) { return rate; } }存根將始終返回在構造函數中傳遞的值,我們對存根具有完全控制權,并且它與生產代碼完全隔離。 現在,測試代碼已實現
@Test void create_payment_request() { LoggerDummy loggerDummy = new LoggerDummy(); Customer customer= new Customer( "name" , "address" ); Item item = new Item( "item" , 1000 ); List<Item> items= asList(item); Sale sale = new Sale(customer, items); CreditCard creditCard = new CreditCard(customer, "1" ); OperatorRate operatorRate = new OperatorRateStub( 10 ); PaymentService paymentService = new PaymentService(loggerDummy, operatorRate); PaymentRequest actual = paymentService.createPaymentRequest(sale, creditCard); assertEquals( new PaymentRequest( 1000 , "1" , 100 ), actual); }cks
嘲笑是您可以說出他們期望收到的東西的對象。 它們用于驗證被測系統及其協作者之間的行為。
您設置期望值,調用SUT的方法,并驗證是否在最后調用了該方法。
隨著我們正在維護的系統的發展,我們需要完成一個新的用戶故事,客戶希望每超過1000磅的PaymentRequest發送一封電子郵件給管理部門。 隔離發送電子郵件有兩個原因:
- 發送電子郵件是一種與外界交流的活動,我們不能在每次運行測試時都發送電子郵件,這會降低測試速度,而且確實很煩人。
- PaymentService應該不知道電子郵件發件人的實現,將這兩件事混合會造成耦合,并使維護服務或更改我們發送電子郵件的方式更加困難,這就是電子郵件發件人自己獲得服務的原因。
我們需要遵循的步驟是:
- 創建一個界面
- 創建一個實現接口的模擬
- 寫我們的測試
界面:
public interface PaymentEmailSender { void send(PaymentRequest paymentRequest); }然后我們必須實現我們的模擬:
public class PaymentServiceMock implements PaymentEmailSender { private List<PaymentRequest> paymentRequestSent = new ArrayList<>(); private List<PaymentRequest> expectedPaymentRequest = new ArrayList<>(); @Override public void send(PaymentRequest paymentRequest) { paymentRequestSent.add(paymentRequest); } public void expect(PaymentRequest paymentRequest) { expectedPaymentRequest.add(paymentRequest); } public void verify() { assertEquals(paymentRequestSent, expectedPaymentRequest); } }這是一個非常簡單的模仿對象,但它會做的工作,我們實現接口我們剛剛創建的,我們所做的send方法商店PaymentRequest ,我們添加了兩種方法來設置模擬, expect和verify ,在verify方法使用jUnit assertEqual方法將期望值與SUT傳遞的值進行比較。
我們針對新的用戶故事編寫測試:
@Test void send_email_to_the_administration_if_sale_is_over_1000() { EmailSenderMock emailSender = new EmailSenderMock(); LoggerDummy loggerDummy = new LoggerDummy(); OperatorRate operatorRate = new OperatorRateStub( 10 ); PaymentService paymentService = new PaymentService(loggerDummy, operatorRate, emailSender); PaymentRequest paymentRequest = new PaymentRequest( 1000 , "1" , 100 ); Customer customer= new Customer( "name" , "address" ); Item item = new Item( "item" , 1000 ); List<Item> items = asList(item); Sale sale = new Sale(customer, items); CreditCard creditCard = new CreditCard(customer, "1" ); paymentService.createPaymentRequest(sale, creditCard); emailSender.expect(paymentRequest); emailSender.verify(); }測試結果為:
org.opentest4j.AssertionFailedError: Expected :[] Actual :[PaymentRequest{total= 2500 , cardNumber= '1234123412341234' , gatewayFee= 250 }]然后,我們執行生產代碼:
public class PaymentService { private Logger logger; private OperatorRate operatorRate; private final EmailSender emailSender; public PaymentService(Logger logger, OperatorRate operatorRate, EmailSender emailSender) { this .logger = logger; this .operatorRate = operatorRate; this .emailSender = emailSender; } public PaymentRequest createPaymentRequest(Sale sale, CreditCard creditCard) { logger.append( "Creating payment for sale: " + sale); int feeRate = operatorRate.feeRate(creditCard.cardNumber); int fee = (feeRate * sale.total()) / 100 ; PaymentRequest paymentRequest = new PaymentRequest(sale.total(), creditCard.cardNumber, fee); if (sale.total() >= 1000 ) { emailSender.send(paymentRequest); } return paymentRequest; } }測試通過,我們就完成了故事。
間諜
可以像間諜一樣,將某個間諜滲透到您的SUT中并記錄他的一舉一動,就像電影間諜一樣。 與模擬不同,間諜是沉默的,它取決于您根據他提供的數據進行斷言。
當您不確定自己的協作對象會調用什么時,可以使用間諜,因此您可以記錄所有內容并斷言間諜是否調用了所需數據。
對于此示例,我們可以使用為模擬創建的相同接口,并使用間諜實施新測試。
public class PaymentEmailSpy implements PaymentEmailSender { private List<PaymentRequest> paymentRequests = new ArrayList<>(); @Override public void send(PaymentRequest paymentRequest) { paymentRequests.add(paymentRequest); } public int timesCalled() { return paymentRequests.size(); } public boolean calledWith(PaymentRequest paymentRequest) { return paymentRequests.contains(paymentRequest); } }Spy的實現接近于模擬,但是與其給出我們期望的調用,我們只是記錄了類的行為,然后我們進行了測試,然后可以聲明我們需要的東西。
PaymentServiceShould { class PaymentServiceShould { private OperatorRate operatorRate; private EmailSenderMock emailSender; private PaymentService paymentService; private LoggerDummy loggerDummy; public static final Customer BOB = new Customer( "Bob" , "address" ); public static final Item IPHONE = new Item( "iPhone X" , 1000 ); public static final CreditCard BOB_CREDIT_CARD = new CreditCard BOB_CREDIT_CARD = CreditCard(BOB, "1" ); @BeforeEach void setUp() { loggerDummy = new LoggerDummy(); operatorRate = new OperatorRateStub( 10 ); emailSender = new EmailSenderMock(); paymentService = new PaymentService(loggerDummy, operatorRate, emailSender); } @Test void not_send_email_for_sales_under_1000() { Item iphoneCharger = new Item( "iPhone Charger" , 50 ); Sale sale = new Sale(BOB, asList(iphoneCharger)); EmailSenderSpy emailSpy = new EmailSenderSpy(); PaymentService spiedPaymentService = new PaymentService(loggerDummy, operatorRate, emailSpy); spiedPaymentService.createPaymentRequest(sale, BOB_CREDIT_CARD); assertEquals( 0 , emailSpy.timesCalled()); } }假貨
我們使用間諜創建一個PaymentService ,進行必要的調用,然后可以根據間諜提供的數據進行斷言。
偽造與我們擁有的所有其他示例不同,偽造具有簡化的業務邏輯,而不是固定的響應或僅記錄呼叫。
Fake的一個示例是InMemory存儲庫,我們可以在其中存儲,檢索甚至進行一些查詢,但是它沒有背后的真實數據庫,實際上所有內容都可以存儲在列表中,或者您可以偽造諸如API之類的外部服務。
在這種情況下,我們可以創建一個偽造品來模擬連接到支付網關的API,并用來測試我們對OperatorRate生產實現。
在這種情況下,我們的生產實現將通過信用卡運營商將Json發送到網關,并以比率返回Json,然后將進行正確的解析并返回Json中的值。
因此,我們開始為實現OperatorRate CreditCardRate類編寫測試
public class CreditCardRateShould { @Test void return_rate_for_credit_card_payment() { PaymentGateway fakeCreditCardGateway = new FakeCreditCardGateway(); CreditCardRate creditCardRate = new CreditCardRate(fakeCreditCardGateway); String operator = "1234123412341234" ; int result = creditCardRate.feeRate(operator); assertEquals( 10 , result); } }被測試的類與外部服務對話,該服務被FakeCreditCardGateway偽造。
偽網關正在解析Json并應用一些非常簡單的邏輯并返回另一個Json。
public class FakeCreditCardGateway implements PaymentGateway { @Override public String rateFor(String cardOperator) { String operator = parseJson(cardOperator); int rate = 15 ; if (operator.startsWith( "1234" )) { rate = 10 ; } if (operator.startsWith( "1235" )) { rate = 8 ; } return jsonFor(rate); } private String jsonFor( int rate) { return new JsonObject() .add( "rate" , rate) .toString(); } private String parseJson(String cardOperator) { JsonObject payload = Json.parse(cardOperator).asObject(); return payload.getString( "operator" , "" ); } }最后是CreditCardRate類的生產代碼
public class CreditCardRate implements OperatorRate { private PaymentGateway paymentGateway; public CreditCardRate(PaymentGateway paymentGateway) { this .paymentGateway = paymentGateway; } @Override public int feeRate(String operator) { String payload = jsonFor(operator); String rateJson = paymentGateway.rateFor(payload); return parse(rateJson); } private int parse(String rateJson) { return Json.parse(rateJson).asObject() .getInt( "rate" , 0 ); } private String jsonFor(String operator) { return new JsonObject() .add( "operator" , operator) .toString(); } }使用此偽造品,我們可以測試要發送到網關的Json是否正確,具有某種邏輯,以便偽造品網關可以回答不同的速率,最后可以測試我們是否正確解析了響應Json。
這是一個非常臨時的實現,無需處理HTTP請求,但是我們可以對如何將其轉換為現實世界有所了解。 如果您想編寫集成測試以進行真正的HTTP調用,則可以看看WireMock和嘲笑jay-server之類的東西 。
Mockito和鴨子綜合癥
不僅Mockito,而且大多數嘲笑框架都具有這種鴨子綜合癥,在鴨子綜合癥中他們可以做很多事情,鴨子可以游泳,飛行和行走。 這些框架的作品具有虛擬,模擬,間諜和存根。
那么我們如何知道在使用框架進行模擬時正在使用什么呢? 為了解決這個問題,我們將使用手動測試雙打編寫的測試,并將其重構為使用Mockito。
PaymentServiceShould { class PaymentServiceShould { private OperatorRate operatorRate; private EmailSenderMock emailSender; private PaymentService paymentService; private LoggerDummy loggerDummy; public static final Customer BOB = new Customer( "Bob" , "address" ); public static final Item IPHONE = new Item( "iPhone X" , 1000 ); public static final CreditCard BOB_CREDIT_CARD = new CreditCard BOB_CREDIT_CARD = CreditCard(BOB, "1" ); @BeforeEach void setUp() { loggerDummy = new LoggerDummy(); operatorRate = new OperatorRateStub( 10 ); emailSender = new EmailSenderMock(); paymentService = new PaymentService(loggerDummy, operatorRate, emailSender); } @Test void create_payment_request() { Sale sale = new Sale(BOB, asList(IPHONE)); PaymentRequest actual = paymentService.createPaymentRequest(sale, BOB_CREDIT_CARD); assertEquals( new PaymentRequest( 1000 , "1" , 100 ), actual); } @Test void send_email_to_the_administration_if_sale_is_over_1000() { Sale sale = new Sale(BOB, asList(IPHONE)); paymentService.createPaymentRequest(sale, BOB_CREDIT_CARD); emailSender.expect( new PaymentRequest( 1000 , "1" , 100 )); emailSender.verify(); } @Test void not_send_email_for_sales_under_1000() { Item iphoneCharger = new Item( "iPhone Charger" , 50 ); Sale sale = new Sale(BOB, asList(iphoneCharger)); EmailSenderSpy emailSpy = new EmailSenderSpy(); PaymentService spiedPaymentService = new PaymentService(loggerDummy, operatorRate, emailSpy); spiedPaymentService.createPaymentRequest(sale, BOB_CREDIT_CARD); assertEquals( 0 , emailSpy.timesCalled()); } @Test void send_email_to_hmrs_for_sales_over_10_thousand() { Item reallyExpensiveThing = new Item( "iPhone Charger" , 50000 ); Sale sale = new Sale(BOB, asList(reallyExpensiveThing)); EmailSenderSpy emailSpy = new EmailSenderSpy(); PaymentService spiedPaymentService = new PaymentService(loggerDummy, operatorRate, emailSpy); spiedPaymentService.createPaymentRequest(sale, BOB_CREDIT_CARD); assertEquals( 2 , emailSpy.timesCalled()); } }假
創建Mockito模擬時,該對象是Dummy,它沒有任何行為,因此我們可以開始重構測試并更改LoggerDummy以使用Mockito對象。
PaymentServiceShould { class PaymentServiceShould { private OperatorRate operatorRate; private EmailSenderMock emailSender; private PaymentService paymentService; - private LoggerDummy loggerDummy; + private Logger logger; public static final Customer BOB = new Customer( "Bob" , "address" ); public static final Item IPHONE = new Item( "iPhone X" , 1000 ); public static final CreditCard BOB_CREDIT_CARD = new CreditCard BOB_CREDIT_CARD = CreditCard(BOB, "1" ); @BeforeEach void setUp() { LoggerDummy(); - loggerDummy = new LoggerDummy(); + logger = mock(Logger. class ); operatorRate = new OperatorRateStub( 10 ); emailSender = new EmailSenderMock(); PaymentService(loggerDummy, operatorRate, emailSender); - paymentService = new PaymentService(loggerDummy, operatorRate, emailSender); + paymentService = new PaymentService(logger, operatorRate, emailSender); } @Test @@ - 48 , 7 + 49 , 7 @@ class PaymentServiceShould { Item iphoneCharger = new Item( "iPhone Charger" , 50 ); Sale sale = new Sale(BOB, asList(iphoneCharger)); EmailSenderSpy emailSpy = new EmailSenderSpy(); - PaymentService spiedPaymentService = new PaymentService(loggerDummy, operatorRate, emailSpy); + PaymentService spiedPaymentService = new PaymentService(logger, operatorRate, emailSpy); spiedPaymentService.createPaymentRequest(sale, BOB_CREDIT_CARD); @@ - 60 , 7 + 61 , 7 @@ class PaymentServiceShould { Item reallyExpensiveThing = new Item( "iPhone Charger" , 50000 ); Sale sale = new Sale(BOB, asList(reallyExpensiveThing)); EmailSenderSpy emailSpy = new EmailSenderSpy(); - PaymentService spiedPaymentService = new PaymentService(loggerDummy, operatorRate, emailSpy); + PaymentService spiedPaymentService = new PaymentService(logger, operatorRate, emailSpy); spiedPaymentService.createPaymentRequest(sale, BOB_CREDIT_CARD);所有測試都通過了,我們不必使用我們擁有的LoggerDummy實現。
存根
現在我們必須開始對模擬進行某些操作,并按照手動測試雙打的相同順序,必須將Mockito對象轉換為存根,因為Mockito具有given()方法,可以在其中設置值退回。
對于基元,Mockito返回0,Objects返回null,對于List,Map或Set這樣的集合返回空集合。
given()以下列方式工作:
given(<method to be called>).willReturn(returnValue);并且我們在測試中更改了實現。
import static java.util.Arrays.asList; import static org.junit.jupiter.api.Assertions.assertEquals; + import static org.mockito.ArgumentMatchers.anyString; + import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; @@ - 20 , 9 + 22 , 10 @@ class PaymentServiceShould { @BeforeEach void setUp() { logger = mock(Logger. class ); - operatorRate = new OperatorRateStub( 10 ); + operatorRate = mock(OperatorRate. class ); emailSender = new EmailSenderMock(); paymentService = new PaymentService(logger, operatorRate, emailSender); + given(operatorRate.feeRate(BOB_CREDIT_CARD.cardNumber)).willReturn( 10 ); }現在,該模擬的行為就像存根,測試正在通過。
嘲弄和間諜
在我們創建的上一個測試中,我們仍在使用創建的PaymentEmailMock ,現在我們可以在Mockito中更改它。
@@ - 8 , 11 + 8 , 12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; + import static org.mockito.Mockito.verify; PaymentServiceShould { class PaymentServiceShould { private OperatorRate operatorRate; - private EmailSenderMock emailSender; + private EmailSender emailSender; private PaymentService paymentService; private Logger logger; public static final Customer BOB = new Customer( "Bob" , "address" ); @@ - 23 , 7 + 24 , 7 @@ class PaymentServiceShould { void setUp() { logger = mock(Logger. class ); operatorRate = mock(OperatorRate. class ); - emailSender = new EmailSenderMock(); + emailSender = mock(EmailSender. class ); paymentService = new PaymentService(logger, operatorRate, emailSender); given(operatorRate.feeRate(BOB_CREDIT_CARD.cardNumber)).willReturn( 10 ); } @@ - 43 , 8 + 44 , 8 @@ class PaymentServiceShould { paymentService.createPaymentRequest(sale, BOB_CREDIT_CARD); - emailSender.expect( new PaymentRequest( 1000 , "1" , 100 )); - emailSender.verify(); + PaymentRequest paymentRequest = new PaymentRequest( 1000 , "1" , 100 ); + verify(emailSender).send(paymentRequest); }所有測試都通過了,很棒,但是Mockito的存根和我們創建的存根之間是有區別的。 這次我們不必指定期望的內容,我們直接進入驗證步驟。 那就是Mockito再次扮演多個角色,由Mockito創建的模擬程序將像間諜一樣記錄所有收到的呼叫。
我們仍然有使用間諜的測試,我們可以將測試更改為僅使用模仿。
PaymentServiceShould { class PaymentServiceShould { void not_send_email_for_sales_under_1000() { Item iphoneCharger = new Item( "iPhone Charger" , 50 ); Sale sale = new Sale(BOB, asList(iphoneCharger)); - EmailSenderSpy emailSpy = new EmailSenderSpy(); - PaymentService spiedPaymentService = new PaymentService(logger, operatorRate, emailSpy); - spiedPaymentService.createPaymentRequest(sale, BOB_CREDIT_CARD); + paymentService.createPaymentRequest(sale, BOB_CREDIT_CARD); , emailSpy.timesCalled()); - assertEquals( 0 , emailSpy.timesCalled()); + verify(emailSender, never()).send(any(PaymentRequest. class )); } @Test void send_email_to_hmrs_for_sales_over_10_thousand() { Item reallyExpensiveThing = new Item( "iPhone Charger" , 50000 ); Sale sale = new Sale(BOB, asList(reallyExpensiveThing)); - EmailSenderSpy emailSpy = new EmailSenderSpy(); - PaymentService spiedPaymentService = new PaymentService(logger, operatorRate, emailSpy); - spiedPaymentService.createPaymentRequest(sale, BOB_CREDIT_CARD); + paymentService.createPaymentRequest(sale, BOB_CREDIT_CARD); , emailSpy.timesCalled()); - assertEquals( 2 , emailSpy.timesCalled()); + PaymentRequest paymentRequest = new PaymentRequest( 50000 , "1" , 5000 ); + verify(emailSender, times( 2 )).send(paymentRequest); } }verify具有多個修飾符,例如:
- atLeast(int)
- atLeastOnce()
- atMost(int)
- times(int)
同樣,我們有具有多個功能的模擬對象,這次有一個模擬和一個間諜。
假貨呢?
偽造品是內部具有邏輯的對象,我們無法使用Mockito來實現,但這不是問題,在大多數情況下,您不需要偽造品,通常偽造品會增長,并且您將結束測試以查看您的偽造品是否正常正確地。
正如鮑伯叔叔所說的那樣,他的帖子是“小嘲笑”:
是的,嗯。 我不常寫假貨。 確實,我三十多年沒有寫過一篇。
良好做法和氣味。
CQS,存根和模擬
如果您不熟悉CQS,請繼續閱讀以下內容:
OO技巧:命令查詢分離的藝術
bliki:CommandQuerySeparation
決定在哪里使用存根和模擬的一個好的經驗法則是遵循“命令查詢分離”原則,您可以在其中:
指令
- 他們沒有返回值
- 用于在您的類中對數據進行突變。
- 使用Mockito進行模擬時,請使用verify() 。
查詢
- 是從類中查詢數據
- 不要產生任何副作用
- 只返回數據。
- 在使用Mockito進行模擬時使用named given()
您擁有的僅模擬/存根類
關于模擬,我們必須了解的一件事是,不僅涉及測試,而且還涉及設計我們的SUT及其協作者的工作方式,要找到不使用第三方庫的應用程序將非常困難,但是這并不意味著您必須嘲笑它??們,實際上您絕對不應該那樣做。 模擬第三方庫的主要內容是您需要對其進行更改,更改簽名會破壞所有模擬您的測試。
解決方案? 使用模擬工具圍繞該庫編寫一個瘦包裝器,您可以設計一個僅接收和返回必要信息的瘦包裝器,但是我們如何測試包裝器呢?
在這種情況下,可以根據您所具有的依賴性來測試包裝器,如果您有數據庫層的包裝器,則可以在另一個源集中進行集成測試,因此您可以運行單元測試而不必擔心集成測試的速度變慢你失望。
不要嘲笑數據結構。
當您擁有自己的數據結構時,不必模擬它,您可以簡單地用所需的數據實例化,以防難以實例化數據結構或需要多個對象時可以使用Builder模式。
您可以在此處了解Builder模式。
使您的測試變得簡約
使用模擬對象進行測試時,請務必不要使測試過于脆弱,這一點很重要,重要的是,您可以重構代碼庫而不會造成測試的煩惱,如果發生這種情況,您可能需要對模擬進行檢查,這可能是一些超額規定的事情,如果在多個測試中都發生這種情況,則最終會減慢開發速度。 解決方案是重新檢查代碼,看看是否需要更改規范或代碼。
想象一下,在開始的示例中,不是使用Dummy作為記錄器,而是使用了模擬。 然后,模擬將驗證記錄器通過的所有消息,并進行任何更改都會破壞測試。 沒有人愿意僅僅因為他們修復了日志中的錯字而導致測試失敗。
不要使用模擬/存根來測試邊界/隔離的對象
沒有協作者的對象不必使用模擬對象進行測試,像這樣的對象只需要在返回或存儲的值中聲明即可。 聽起來似乎很明顯,但是加強它是很好的。
對于像JSON解析器這樣的依賴項,您可以測試包裝器是否具有真正的依賴項。 您可以在Fake的示例中看到這一點,而不是模擬Json庫,而是使用真實的庫,可以使用類似包裝器的方式進行轉換,然后我們必須使用真實的Json測試包裝器庫并查看創建的json是否正確,在這種情況下,我們永遠不會嘲笑該依賴項。
不要添加行為
模擬是測試雙打,您不應該在測試雙打中增加復雜性,您的偽造包含一些邏輯,但是除此之外,測試雙打都不應該包含邏輯,這是您放錯了責任的癥狀。
這個問題的一個例子是一個返回另一個模擬的模擬,如果您有一個類似服務的東西可以返回另一個服務,那么您可能想再看一下應用程序的設計。
僅嘲笑/與你的近鄰打樁
一個可能具有多個依賴關系的復雜對象可能很難測試,從中我們可以看到的一個癥狀是測試的設置很復雜,并且測試也很難閱讀。 單元測試應該專注于同時測試一件事,并且應該只為鄰居設定期望值(認為是Demeter法則)。 您可能必須引入角色來橋接對象及其周圍環境。
太多的模擬/存根
您的SUT可能有多個合作者,并且您的測試開始變得更加復雜且難以閱讀,就像在我們看到的其他情況下一樣,SUT可能承擔了太多的責任,以至于您不得不破壞對象成為更專注的小公司。
因此,如果您的服務在構造函數中具有多個類,例如:
public ReadCommand(UserRepository userRepository, MessageRepository messageRepository, MessageFormatter messageFormatter, Console console, String username) { this .userRepository = userRepository; this .messageRepository = messageRepository; this .messageFormatter = messageFormatter; this .console = console; this .username = username; }您可以將其重構為:
public ReadCommand(UserRepository userRepository, MessageRepository messageRepository, MessagePrinter messagePrinter, String username) { this .userRepository = userRepository; this .messageRepository = messageRepository; this .messagePrinter = messagePrinter; this .username = username; }現在, MessagePrinter具有MessageFormatter和Console一起工作,因此,當您測試ReadCommand類時,只需要驗證是否調用了打印方法即可。
翻譯自: https://www.javacodegeeks.com/2019/04/introduction-to-test-doubles.html
總結
- 上一篇: 昂科威安卓手机映射怎么用(昂科威安卓)
- 下一篇: 骆驼祥子大事件时间轴_骆驼中的事件处理