gpt4 book ai didi

java - 为什么以及如何模拟模型助手类?

转载 作者:行者123 更新时间:2023-11-30 10:25:46 25 4
gpt4 key购买 nike

我阅读了很多有关单元测试、模拟以及所有这些内容的文章。我目前还在阅读 Steve Freeman 和 Nat Pryce 合着的“Growing Object-Oriented Software Guided by Tests”一书。

我开始理解很多东西,但遗漏了一个关键点,我试图在网上的任何地方找到答案,但我还不满意。

在下面的示例中,我有一个在线商店,它从第三方库接收消息,翻译这些消息,解释它们并最终在需要时将它们保存到数据库中。在一个具体案例中,我收到一条关于用户信用卡地址更改的消息,我想将该信息存储到数据库中。

结构如下:

src/
domain/
MessageTranslator.java
ShopEventListener.java
ShopHandler.java
model/
CreditCard.java
CreditCardBase.java
CreditCardBuilder.java
User.java
UserBase.java
UserBuilder.java
test/
MessageTranslatorTest.java
ShopHandlerTest.java

MessageTranslatorTest

public class MessageTranslatorTest {


@Test
public void notifiesCCAddressChangedWhenChangeCCAddressMessageReceived() throws Exception {
ShopEventListener listenerMock = mock(ShopEventListener.class);

MessageTranslator messageTranslator = new MessageTranslator(listenerMock);
messageTranslator.processMessage("action=changeCCAddress; firstname=John; lastname=Doe; address=foobar3");

verify(listenerMock).ccAddressChanged("John", "Doe", "foobar3");
}
}

MessageTranslator(现在很简单)

public class MessageTranslator {

private final ShopEventListener listener;

public MessageTranslator(ShopEventListener userEventListener) {
listener = userEventListener;
}

public void processMessage(String message) throws Exception {
String[] attributes = message.split(";");
listener.ccAddressChanged(attributes[1].split("=")[1].trim(), attributes[2].split("=")[1].trim(), attributes[3].split("=")[1].trim());
}

}

商店处理程序

public class ShopHandler implements ShopEventListener {

@Override
public void ccAddressChanged(String firstname, String lastname, String newAddress) throws Exception {

// find a user (especially userid) in the Database for given firstname and lastname
UserBase userBase = new UserBase();
User user = userBase.find(aUser().withFirstname(firstname).withLastname(lastname).build());

if (user == null) {
throw new Exception();
}

// find the matching CreditCard for the userid in the database
Integer userid = user.getUserid();
CreditCardBase ccBase = new CreditCardBase();
CreditCard cc = ccBase.find(aCreditCard().withUserid(userid).build());

if (cc == null) {
throw new Exception();
}

// change address locally and then write it back to the database
cc.setAddress(newAddress);
cc.persist();
}

}

ShopHandlerTest

public class ShopHandlerTest {

@Test
public void changesCCAddressWhenChangeCCAddressEventReceived() throws Exception {
ShopHandler shop = new ShopHandler();
shop.ccAddressChanged("John", "Doe", "foobar3");

// TODO: How to test the changes in inner object?
}

}

这是我经常犯错的地方。

  1. 我是否要模拟辅助类 UserBase 和 CreditCardBase 不执行任何数据库查询而只是返回一个准备好的假对象?
  2. 我是否想模拟持久化方法而不将任何真实数据写入数据库,而可能只是测试要持久化的对象的参数并让其他(集成)测试测试数据库操作?
  3. 如果 1. 和 2. 的回答是"is",那么我实际上在测试什么?那么值得对这个单元进行单元测试吗?
  4. 这样的结构有意义吗?
  5. 如果 1. 和 2. 的回答是"is",那么我该如何模拟内部对象?我觉得依赖注入(inject)在这里是错误的方法,因为首先它没有真正的依赖,但有一些辅助类,其次(也是更重要的 imo)ShopHandler 类可能充满依赖性,因为它可能需要很多不同的辅助类和模型类来执行所有不同的操作。如果我只想根据外部消息更新用户的生日怎么办,我是否仍然需要设置所有依赖项(如 CreditCardBase 等)的路径?

抱歉发了这么长的帖子,但如果你能把我推向正确的方向,那就太棒了。如果您需要更多代码来理解上述内容,请告诉我。

最佳答案

Do I want to mock the helper classes UserBase and CreditCardBase to not perform any database queries but just return a prepared fake object?

看起来您的“辅助类”实际上是存储库/DAO。您通常希望独立于 DAO 测试您的业务逻辑,而不需要真正的数据库访问。所以是的,您可能应该模拟这些 DAO 并准备对它们的调用,因为它们在现实中工作。在大多数情况下,准备好的假对象是可以的。您可能还想验证您的模拟 DAO 是否实际被调用。

Do I want to mock the persist-method to not write any real data to the database but maybe just test the parameters of the object to be persisted and have other (integration) tests test the database operations?

我觉得有点奇怪,您的业务实体中似乎有 persist 方法。通常 DAO 实现这种类型的方法。

是的,如果您测试业务逻辑,您也应该模拟对 DAO 的 persist 调用。如果您不这样做,您将对业务逻辑进行比应有的更繁重的测试。

是的,您也应该测试您的 DAO,但与业务逻辑分开。

If 1. and 2. will be answered with yes, then what am I actually testing here? Is it worth unittesting this unit then?

您正在测试您的业务逻辑。正是在您的 ccAddressChanged 方法中实现的。大致:

  • 如果找不到用户,则抛出异常。
  • 如果找到用户但找不到用户的信用卡,则抛出异常。
  • 如果两者都能找到,则信用卡会保留更新后的地址。

Does the structure make sense this way?

这和我习惯的不太一样。你似乎在实体中有数据访问逻辑,那么你也有这个“基础”帮助类......

If 1. and 2. will be answered with yes, then how do I mock the inner objects?

对于“内部对象”,您可能指的是这些辅助类。它们实际上更像是“帮助类”,它们是提供对数据库的访问的 DAO。您可以从外部传递或注入(inject)它们。基本上这是依赖注入(inject),你的业务逻辑依赖于这些 DAO 组件。如果您能够从外部传递它们,那么在您的测试中您可以模拟 DAO 并将模拟传递给您的业务服务。借助像 Spring 这样的 DI 框架,您将获得对此的框架支持。

这是使用 Spring 和 Mockito 对 ShopHandler 类进行测试的粗略草图:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {ShopHandler.class})
public class ShopHandlerTest {

@Autowired
private ShopHandler sut;

@MockBean
private UserRepository userRepository;

@MockBean
private CreditCardRepository creditCardRepository;

@Test(expected = UserNotFoundException.class)
public void throwsUserNotFoundExceptionIfUserIsUnknown() {
when(userRepository.findUserByFirstNameAndLastName("Scott", "Tiger").thenReturn(null);

sut.ccAddressChanged("Scott", "Tiger", "Some Address");
}

@Test
public void successFullyUpdatesCreditCardAddress() {
when(userRepository.findUserByFirstNameAndLastName("Scott", "Tiger").thenReturn(new User("userId", ...));
when(creditCardRepository.findByUserId("userId")).thenReturn(new CreditCard(...));

ArgumentCaptor<CreditCard> creditCardCaptor = ArgumentCaptor.forClass(CreditCard.class);

verify(creditCardRepository).save(creditCardCaptor.capture());

sut.ccAddressChanged("Scott", "Tiger", "Some Address");

asserthThat(creditCardCaptor.getValue().getAddress()).isEqualTo("Some Address");
}
}

I feel like dependency injection is the wront approach here,

依赖注入(inject)在这里是一种非常明智的方法。

because first its no real dependency,

好吧,当然这些是真正的依赖关系。

but some helper classes,

您认为它从哪里结束成为“辅助类”并开始成为“真正的依赖项”?您所说的“帮助程序类”非常类似于 DAO,它们绝对是“真正的依赖项”。

second (and more important imo) the ShopHandler class could be flooded with dependencies, as it might need alot of different helper classes and model classes to perform all the different actions.

如果您需要执行所有这些操作并且需要所有这些依赖项来执行此操作,那么这就是现实。然而,问题是——您真的必须在一个业务服务中实现所有这些操作吗?不能把这个分成很多业务服务吗?然后你会得到更小的更集中的类,它们只需要几个依赖项。

关于java - 为什么以及如何模拟模型助手类?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45960620/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com