如何模拟 - 从 s3 读取文件



我是编写单元测试的新手。我正在尝试读取存储在S3中的 JSON 文件,并且收到"传递给 when() 的参数不是模拟!"和"配置文件不能为空"错误。

这是我迄今为止尝试使用JAVA检索对象的方法:

private void amazonS3Read() {
String clientRegion = "us-east-1";
String bucketName = "version";
String key = "version.txt";
S3Object fullObject = null;
try {
AmazonS3 s3Client = AmazonS3ClientBuilder.standard()
.withRegion(clientRegion)
.withCredentials(new ProfileCredentialsProvider())
.build();
fullObject = s3Client.getObject(new GetObjectRequest(bucketName, key));
S3ObjectInputStream s3is = fullObject.getObjectContent();
json = returnStringFromInputStream(s3is);
fullObject.close();
s3is.close();
} catch (AmazonServiceException e) {
// The call was transmitted successfully, but Amazon S3 couldn't process
// it, so it returned an error response.
e.printStackTrace();
} catch (SdkClientException e) {
// Amazon S3 couldn't be contacted for a response, or the client
// couldn't parse the response from Amazon S3.
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//Do some operations with the data
}

测试文件

@Test
public void amazonS3ReadTest() throws Exception {
String bucket = "version";
String keyName = "version.json";
InputStream inputStream = null;
S3Object s3Object = Mockito.mock(S3Object.class);
GetObjectRequest getObjectRequest = Mockito.mock(GetObjectRequest.class);
getObjectRequest = new GetObjectRequest(bucket, keyName);
AmazonS3 client = Mockito.mock(AmazonS3.class);
Mockito.doNothing().when(AmazonS3ClientBuilder.standard());
client = AmazonS3ClientBuilder.standard()
.withRegion(clientRegion)
.withCredentials(new ProfileCredentialsProvider())
.build();
Mockito.doReturn(s3Object).when(client).getObject(getObjectRequest);
s3Object = client.getObject(getObjectRequest);
Mockito.doReturn(inputStream).when(s3Object).getObjectContent();
inputStream = s3Object.getObjectContent();
//performing other operations
}

获取两个不同的异常:

Argument passed to when() is not a mock! Example of correct stubbing: doThrow(new RuntimeException()).when(mock).someMethod();
org.mockito.exceptions.misusing.NotAMockException: 
Argument passed to when() is not a mock!
Example of correct stubbing: 

profile file cannot be null
java.lang.IllegalArgumentException: profile file cannot be null
at com.amazonaws.util.ValidationUtils.assertNotNull(ValidationUtils.java:37)
at com.amazonaws.auth.profile.ProfilesConfigFile.<init>(ProfilesConfigFile.java:142)
at com.amazonaws.auth.profile.ProfilesConfigFile.<init>(ProfilesConfigFile.java:133)
at com.amazonaws.auth.profile.ProfilesConfigFile.<init>(ProfilesConfigFile.java:100)
at com.amazonaws.auth.profile.ProfileCredentialsProvider.getCredentials(ProfileCredentialsProvider.java:135)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.getCredentialsFromContext(AmazonHttpClient.java:1184)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.runBeforeRequestHandlers(AmazonHttpClient.java:774)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.doExecute(AmazonHttpClient.java:726)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeWithTimer(AmazonHttpClient.java:719)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.execute(AmazonHttpClient.java:701)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.access$500(AmazonHttpClient.java:669)
at com.amazonaws.http.AmazonHttpClient$RequestExecutionBuilderImpl.execute(AmazonHttpClient.java:651)
at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:515)
at com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:4443)
at com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:4390)
at com.amazonaws.services.s3.AmazonS3Client.getObject(AmazonS3Client.java:1427)

我做错了什么,我该如何解决这个问题?

你的方法看起来是错误的。

  • 你想模拟一个私有方法的依赖和调用:amazonS3Read(),你似乎想对该方法进行单元测试。
    我们不对类的私有方法进行单元测试,但我们从其 API(应用程序编程接口)测试该类,即public/protected方法。
  • 您的单元测试是一系列模拟记录:其中大部分是通过 Mockito 对您的私有方法的描述。 我什至很难识别没有嘲笑的部分...... 你在这里断言什么?你在某些模拟上调用 4 个方法? 不幸的是,它在结果/行为方面没有任何断言。 您可以在调用的方法之间添加不正确的调用,测试将保持绿色,因为您不会测试可以使用assertEquals(...)习惯用法断言的结果。
    这并不意味着模拟一种方法是不可接受的,但是当你的测试主要是模拟时,有些事情是错误的,我们可以相信它的结果。

我会建议你两件事:

  • 编写一个单元测试,专注于断言您执行的逻辑:计算/转换/传输值等等......不要专注于链接方法。

  • 使用一些轻量级和简单的 S3 兼容服务器编写一些集成测试,这些测试将在行为断言方面为您提供真实的反馈。 副作用可以通过这种方式进行测试。
    例如,你有Riak,MinIo或仍然是Localstack。


更具体地说,这里有一种重构方法来改进事情。
如果amazonS3Read()私有方法必须进行单一测试,则可能应该将其移动到特定的类中,例如MyAwsClient并使其成为公共方法。

然后这个想法是在责任方面尽可能明确amazonS3Read()
其逻辑可以概括为:

1) 获取一些标识符信息以传递给 S3 服务。
这意味着定义了一个带有参数的方法:

public Result amazonS3Read(String clientRegion, String bucketName, String key) {...}

2) 应用所有细粒度的 S3 函数来获取S3ObjectInputStream对象。
我们可以在一个类AmazonS3Facade的特定方法中收集所有这些:

S3ObjectInputStream s3is = amazonS3Facade.getObjectContent(clientRegion, bucketName, key);

3) 执行处理返回S3ObjectInputStream并返回结果的逻辑

json = returnStringFromInputStream(s3is); 
// ...   
return result;

现在如何测试?

很简单。
使用 JUnit 5 :

@ExtendWith(MockitoExtension.class)
public MyAwsClientTest{
MyAwsClient myAwsClient;
@Mock 
AmazonS3Facade amazonS3FacadeMock;        
@Before
void before(){
myAwsClient = new MyAwsClient(amazonS3FacadeMock);
}
@Test
void amazonS3Read(){
// given
String clientRegion = "us-east-1";
String bucketName = "version";
String key = "version.txt";
S3ObjectInputStream s3IsFromMock = ... // provide a stream with a real content. We rely on it to perform the assertion
Mockito.when(amazonS3FacadeMock.getObjectContent(clientRegion, bucketName, key))
.thenReturn(s3IsFromMock);
// when    
Result result = myAwsClient.amazonS3Read(clientRegion, bucketName, key);
// assert result content.
Assertions.assertEquals(...);
}
}

有哪些优势?

  • 类实现是可读的和维护的,因为它专注于函数处理。
  • 整个 S3 逻辑被移动到一个地方AmazonS3Facade(单一职责原则/模块化)。
  • 因此,测试实现现在是可读和可维护的
  • 测试实际上测试您执行的逻辑(而不是验证对多个模拟的一系列调用)。

请注意,单一测试AmazonS3Facade几乎没有价值,因为这只是对 S3 组件的一系列调用,不可能根据返回的结果断言,因此非常脆弱。
但是,使用早期引用的简单轻量级 S3 兼容服务器为此编写集成测试确实很有意义。

相关内容

  • 没有找到相关文章

最新更新