羽毛球双打区域_测试双打简介

羽毛球双打区域

当您编写单元测试时,您会遇到许多协作者,而且他们都有非常特殊的行为,知道在正确的时间必须使用哪种测试两倍可以使您的生活更轻松。

第一个是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 ,我们添加了两种方法来设置模拟, expectverify ,在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模拟对象时,该对象是虚拟对象,它没有任何行为,因此我们可以开始重构测试并更改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具有named 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具有MessageFormatterConsole一起工作,因此,当您测试ReadCommand类时,只需要验证是否调用了打印方法即可。

翻译自: https://www.javacodegeeks.com/2019/04/introduction-to-test-doubles.html

羽毛球双打区域

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/332396.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

可为空的对象必须具有一个值_前端:这里有8个常见的JavaScript经典问题,总有一个你不会的...

参考前端小智&#xff1a;https://juejin.im/post/5d2d146bf265da1b9163c5c91.了解函数提升使用var关键字声明的变量在JavaScript中会被提升&#xff0c;并在内存中分配值undefined。 但初始化恰发生在你给变量赋值的地方。 另外&#xff0c;var声明的变量是函数作用域的&#…

Struts2的配置文件struts.xml详解

<?xml version"1.0" encoding"UTF-8"?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN""http://struts.apache.org/dtds/struts-2.3.dtd"> <struts><!-- 所有匹…

hibernate教程_Hibernate多对多教程

hibernate教程介绍&#xff1a; 在本教程中&#xff0c;我们将学习使用Hibernate ManyToMany注释定义和使用多对多实体关联。 上下文构建&#xff1a; 为了继续学习本教程&#xff0c;我们假设我们有两个实体– 雇员和资格&#xff1a; 众所周知&#xff0c;一名员工可以拥有…

python常用命令格式_python常用命令有哪些

原标题&#xff1a;python常用命令有哪些 Python是一种计算机程序设计语言。是一种动态的、面向对象的脚本语言。它包含了许多命令来帮助我们实现各种各有的功能&#xff0c;接下来在文章中为大家分享【推荐课程&#xff1a;Python教程】 &#xff08;1&#xff09;打开csv文件…

java.lang.ClassNotFoundException: javax.servlet.jsp.jstl.core.LoopTag

开发工具&#xff1a;IntelliJ IDEA for Mac 开发项目&#xff1a;struts2-demo 构建工件&#xff1a;Maven 问题描述&#xff1a;在 jsp 中使用了 jstl 标签 <c:forEach>&#xff0c;浏览器访问 Action 组件提示错误&#xff1a; java.lang.ClassNotFoundException: ja…

latex 多行公式_Markdown中输入多行并列的公式

本篇旨在以两个Markdown文件编辑器-Typora与Visual Code Studio为例&#xff0c; 说明对于多行并列公式不同的处理方式。Latex中输入多行并列公式在latex文件编辑器中使用align环境输入多行并列的公式&#xff0c; 如下例&#xff1a;begin{align} x & v_0costheta t y &am…

java 常规类的可见性_Java 12常规可用性

java 常规类的可见性马克雷因霍尔德&#xff08;Mark Reinhold&#xff09; 今天宣布 &#xff0c;“ Java 12的参考实现JDK 12现在已全面上市。” Reinhold在该公告中指出&#xff0c;“可以从https://jdk.java.net/12获得来自Oracle的GPL许可的OpenJDK构建&#xff0c;并且“…

python支持复数以及相关的运算吗_Python: 复数的数学运算

写的最新的网络认证方案代码遇到了一个难题&#xff0c;唯一的解决办法就是使用复数空间&#xff0c;需要使用复数来执行一些计算操作。 复数可以用使用函数complex(real, imag) 或者是带有后缀j 的浮点数来指定。 比如&#xff1a; >>> a complex(2, 4) >>>…

分页查询的实现(struts2+jsp+jstl+el)

文章目录MySQL的分页查询语句如何设置《上一页》和《下一页》的有效性呢&#xff1f;示例代码MySQL的分页查询语句 使用 MySQL 的分页查询语句 select * from project limit 5,5&#xff0c;打开表 project&#xff0c;获取全部记录&#xff0c;只要第 5 条记录后的 5 条记录&…

junit 测试执行顺序_JUnit 5中的测试执行顺序

junit 测试执行顺序一般实践认为&#xff0c;自动化测试应能够独立运行且无特定顺序&#xff0c;并且测试结果不应依赖于先前测试的结果。 但是在某些情况下&#xff0c;可以证明特定的测试执行顺序是正确的&#xff0c;尤其是在集成或端到端测试中。 默认情况下&#xff0c;在…

python的起源和发展_Python入门第一课——Python的起源、发展与前景!

我们在做任何一件事情之前&#xff0c;我们都会通过各种渠道去搜集事情的信息&#xff0c;了解事情的来龙去脉&#xff0c;学习一门编程语言也是如此&#xff0c;只有知根知底&#xff0c;我们才能有明确的方向和目标&#xff0c;以及底气去完成这件事情&#xff0c;今天我带大…

MySQL分页查询语句

单表分页查询语句&#xff1a; select * from tbl_name limit start_index, rows_per_page&#xff1b;start_index&#xff1a;每页数据的起始行的索引值&#xff0c;行的索引值从 0 开始 rows_per_page&#xff1a;每页显示的行数 page_num&#xff1a;查询的页码 关系式&a…

spring依赖注入_Spring依赖注入

spring依赖注入介绍&#xff1a; 在设计良好的Java应用程序中&#xff0c;这些类应尽可能独立。 这样的设计提高了组件的可重用性。 它还使对各个组件进行单元测试变得更加容易。 依赖注入的概念促进了Java对象之间的松散耦合。 在本教程中&#xff0c;我们将讨论Spring框架中…

excel 时间戳_我没有Excel基础,可以学Power BI吗

当然可以&#xff01;没有Excel基础一样可以熟练掌握Power BI !Excel基础可以略微影响Power BI的上手速度&#xff0c;却不能决定最终对Power BI的运用能力。如果有Excel基础学习Power BI在最初阶段会更快&#xff0c;但经过实践证明&#xff0c;Excel基础对于PBI的学习进度影响…

Windows下查看wifi密码的命令

netsh wlan show profiles namewifi名称 keyclear

lemon geci_创建一个Java :: Geci生成器

lemon geci几天前&#xff0c;我写了有关Java :: Geci架构&#xff0c;代码生成原理以及生成Java源代码的可能不同方式的文章。 在本文中&#xff0c;我将讨论在Java :: Geci中创建生成器有多么简单。 您好&#xff0c;Wold生成器 HelloWorld1 最简单的生成器是Hello, World…

安卓 图像清晰度识别_OCR文字识别的功能及注意事项

首先OCR文字识别是指电子设备检查纸上打印的字符&#xff0c;然后用OCR文字识别技术翻译成计算机文字的过程&#xff1b;就是对文本资料进行扫描&#xff0c;然后对图像文件进行分析处理&#xff0c;获取文字及版面信息的过程。那么在日常生活中有很多公司有文字识别这方面需求…

薪资/薪水/金额的数据类型

MySQL 数据库中&#xff0c;金额字段使用的数据类型和长度建议为&#xff1a;decimal(11,2) 而对应的实体类的成员变量的数据类型建议为&#xff1a;BigDecimal

java中list去除空值_Java –从列表中删除所有空值

java中list去除空值介绍&#xff1a; 在本文中&#xff0c;我们将学习如何使用普通的Java&#xff0c;Java 8 lambda表达式和某些第三方库从Java 列表中删除空值 。 所以&#xff0c;让我们开始吧&#xff01; 从Java中的 让我们探索从Java 列表中删除null的不同方法&#xf…