如果代碼中需要與 AWS 服務(wù)(比如 S3)交互, 如何寫單元測(cè)試?
0x00 mock API
既然是調(diào)用 AWS 的 API, 那么可以從 AWS SDK 入手, 通過(guò)開發(fā)語(yǔ)言級(jí)別的 Mock framework, 攔截 API 調(diào)用. 以 Java 訪問(wèn) S3 為例, 使用 Unitils 框架, Mock AmazonS3Client
類.
// 通過(guò) mock AmazonS3Client
private Mock<AmazonS3Client> mockS3Client = null;
// 約定 API 返回內(nèi)容
this.mockS3Client.returns(objectListingResult).listObjects(request);
這種做法有幾個(gè)缺點(diǎn):
- 需要開發(fā)語(yǔ)言支持, 如果選擇了 Go, 我也不知道怎么動(dòng)態(tài) Mock
- 需要自己寫的代碼容易傳入 Mock 過(guò)的 client 代碼, 比如一個(gè)靜態(tài)變量的 AmazonS3Client 怎辦, 咳咳
- 沒(méi)有真正通過(guò) API 請(qǐng)求服務(wù), 如果 mock 邏輯錯(cuò)誤, 沒(méi)有達(dá)到測(cè)試的目的
0x01 LocalStack:
LocalStack 是開發(fā) JIRA 的公司 Atlassian 開發(fā)的, 用 Python "山寨"了 AWS 的 API, 通過(guò) REST API 提供跟 AWS 一模一樣的服務(wù). 使用起來(lái)也非常簡(jiǎn)單, 直接 docker pull atlassianlabs/localstack
就完成了安裝. 啟動(dòng)也足夠簡(jiǎn)單,
# 8080 端口是 web 使用
# SERVICES 環(huán)境變量用于指定啟動(dòng)的服務(wù)
# 4560-4582 是各個(gè)服務(wù)使用的端口
docker run -p 8080:8080 -p 4560-4582:4560-4582 --name localstack -e SERVICES='s3,web' atlassianlabs/localstack
各個(gè)服務(wù)使用的端口如下:
- API Gateway at http://localhost:4567
- Kinesis at http://localhost:4568
- DynamoDB at http://localhost:4569
- DynamoDB Streams at http://localhost:4570
- Elasticsearch at http://localhost:4571
- S3 at http://localhost:4572
- Firehose at http://localhost:4573
- Lambda at http://localhost:4574
- SNS at http://localhost:4575
- SQS at http://localhost:4576
- Redshift at http://localhost:4577
- ES (Elasticsearch Service) at http://localhost:4578
- SES at http://localhost:4579
- Route53 at http://localhost:4580
- CloudFormation at http://localhost:4581
- CloudWatch at http://localhost:4582
AWS 的 cli/SDK 都提供一個(gè) endpoint-url
(也就是 AWS API server 的 url) 的 hook, 方便的讓我們使用 Localstack. 例如, aws cli 中使用本地的 S3:
# 創(chuàng)建 bucket
aws s3api --endpoint http://localhost:4572 create-bucket --bucket test-bucket
# 執(zhí)行 ls 操作
aws s3 --endpoint http://localhost:4572 ls s3://test-bucket/
為了與 Java/JUnit 集成, LocalStack 還提供了 LocalstackTestRunner
, 參見(jiàn)官方示例:
@RunWith(LocalstackTestRunner.class)
public class MyCloudAppTest {
@Test
public void testLocalS3API() {
AmazonS3 s3 = new AmazonS3Client(...);
s3.setEndpoint(LocalstackTestRunner.getEndpointS3());
List<Bucket> buckets = s3.listBuckets();
...
}
}
不過(guò)值得提醒的是, 這個(gè) LocalstackTestRunner 從 github 上下載最新的 localstack 并且在本機(jī)安裝, 作為天朝碼農(nóng)你懂的, 因此還是建議使用 docker 方式運(yùn)行 localstack, 非常便捷.
LocalStack 與 gitlab CI 的集成也非常簡(jiǎn)單, 僅需要在前置的 stage 的 services
中定義 localstack 的 image 即可.
LocalStack 的好處也非常明顯:
- 真正的 REST API 調(diào)用, 不用代碼級(jí)別 Mock SDK, 因此也做到了所有語(yǔ)言通吃
- 錯(cuò)誤注入, 比如通過(guò)
KINESIS_ERROR_PROBABILITY
環(huán)境變量的值指定多大概率扔出ProvisionedThroughputExceededException
異常. 不過(guò)這種設(shè)計(jì)在 gitlab CI 中就不是很容易集成進(jìn)來(lái), 畢竟這個(gè)環(huán)境變量是 Localstack 進(jìn)程全局的, 如果想同時(shí)測(cè)試正常情況和異常情況, 還是要?jiǎng)觿?dòng)腦筋
總結(jié)
再也沒(méi)有借口不寫 AWS 服務(wù)相關(guān)的測(cè)試代碼了, 不是么.....
-- EOF --