汉阴做网站,推动高质量发展为主题,最佳的资源磁力搜索引擎,网络维护需要会什么在编写单元测试时#xff0c;您会遇到许多协作者#xff0c;并且他们都有非常特殊的行为#xff0c;知道在正确的时间必须使用哪种测试两倍可以使您的生活更轻松。 假 第一个是Dummy对象#xff0c;它是最简单的一个#xff0c;Dummy只是您为满足构造函数而传递的对象您会遇到许多协作者并且他们都有非常特殊的行为知道在正确的时间必须使用哪种测试两倍可以使您的生活更轻松。 假 第一个是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 ); ListItem 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 ); ListItem 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 ListPaymentRequest paymentRequestSent new ArrayList(); private ListPaymentRequest 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 ); ListItem 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 ListPaymentRequest 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返回0Objects返回null对于ListMap或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技巧命令查询分离的艺术 blikiCommandQuerySeparation 决定在哪里使用存根和模拟的一个好的经验法则是遵循“命令查询分离”原则您可以在其中 指令 他们没有返回值 用于在您的类中对数据进行突变。 使用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