2

.NET Core v1.0에서 AWS Lambda를 사용할 때 종속성 삽입 또는 조롱 환경 변수 사용에 대한 모범 사례 또는 설명서가 있습니까?AWS Lambda 환경 변수 및 종속성 삽입

예를 들어, 다음은 KinesisEvent를 허용하고 일종의 처리를 수행하는 람다 함수 ProcessKinesisMessageById의 예입니다. 이 처리 중에는 환경 변수에 액세스해야하는 일종의 외부 서비스 (예 : AWS S3 또는 데이터베이스)에 액세스하는 작업이 포함됩니다.

public class AWSLambdaFileProcessingService 
{ 
    private IFileUploadService _fileUploadService; 

    // No constructor in the Lambda Function 

    [LambdaSerializer(typeof(JsonSerializer))] 
    public void ProcessKinesisMessageById(KinesisEvent kinesisEvent, ILambdaContext context) 
    { 
     Console.WriteLine("Processing Kinesis Request"); 

     _fileUploadService = new AWSFileUploadService(); // Can this be injected? (Constructor shown below) 

     // some sort of processing 
     _fileUploadService.DoSomethingWithKinesisEvent(kinesisEvent); 
    } 
} 

// Example of of a class that needs access to environment variables 
// Can this class be injected into the AWS Lambda function? 
// Or the Environment Variables mocked? 
public class AWSFileUploadService : IFileUploadService 
{ 
    private readonly IAmazonS3 _amazonS3Client; 
    private readonly TransferUtility _fileTransferUtility; 


    public AWSFileUploadService() 
    { 
     _amazonS3Client = new AmazonS3Client(
      System.Environment.GetEnvironmentVariable("AWS_S3_KEY"), 
      System.Environment.GetEnvironmentVariable("AWS_S3_SECRET_KEY") 
      ); 

     _fileTransferUtility = new TransferUtility(_amazonS3Client); 
    } 

    public bool DoSomethingWithKinesisEvent(KinesisEvent kinesisEvent) 
    { 
     // .... 
    } 

```

기능은 환경 변수를 게시 한 후 괜찮 작동하고 AWS에 게시 한 후 (비주얼 스튜디오 2017에서) 람다 기능보기 테스트 콘솔을 사용하여 테스트 할 수 있습니다. 그러나 로컬 테스트에서 사용하기 위해 환경 변수를 모의하거나 설정할 수 없으면 유닛 또는 통합 테스트를 작성하는 데 문제가 있습니다.

람다 기능을 로컬에서 테스트하기위한 제안이나 사례가 있습니까?

+0

추상적 환경 변수는 별도의 서비스에 int IFileUploadService' '에 해당 주사 액세스 코드에 대한 테스트의 예는 차례로'AWSLambdaFileProcessingService'에 삽입해야합니다. – Nkosi

답변

3

이것이 AWS Lambda Function이라는 사실은 구현에 대한 우려이며 현재 상태의 코드가 고립되어 테스트하기가 어렵다는 사실에 많은 도움이되지 않습니다. 이것은 디자인 문제의 문제입니다.

코드를 좀 더 유연하게 유지 보수 할 수있는 리팩토링을 고려하십시오.

환경 변수에 관해서는, 느슨한 결합과 더 나은 조롱을 위해 추상화 뒤에 정적 클래스를 캡슐화하는 것을 고려하십시오. 제공된 예에 기초 할 때

public interface ISystemEnvironment { 
    string GetEnvironmentVariable(string variable); 
} 

public class SystemEnvironmentService : ISystemEnvironment { 
    public string GetEnvironmentVariable(string variable) { 
     return System.Environment.GetEnvironmentVariable(variable); 
    } 
} 

AWSFileUploadService

단단히 구현 문제 자체가 결합되어, 추상화는 그 이점을 취할 수있는. 이 테스트 할 때 조롱 할 수있는 추상화를 노출로 테스트 할 때 필요에 따라 AWSLambdaFileProcessingService 지금

public class AWSLambdaFileProcessingService { 
    private IFileUploadService _fileUploadService; 

    [LambdaSerializer(typeof(JsonSerializer))] 
    public void ProcessKinesisMessageById(KinesisEvent kinesisEvent, ILambdaContext context) { 
     Console.WriteLine("Processing Kinesis Request"); 
     _fileUploadService = FileUploadService.Value; 
     // some sort of processing 
     _fileUploadService.DoSomethingWithKinesisEvent(kinesisEvent); 
    } 

    public static Lazy<IFileUploadService> FileUploadService = new Lazy<IFileUploadService>(() => { 
     var env = new SystemEnvironmentService(); 
     var s3 = new AmazonS3Client(
      env.GetEnvironmentVariable("AWS_S3_KEY"), 
      env.GetEnvironmentVariable("AWS_S3_SECRET_KEY") 
     ); 
     var service = new AWSFileUploadService(s3); 
     return service; 
    }); 
} 

게으른 공장 리팩토링 할 수있다 위의 두 가지 제안

public class AWSFileUploadService : IFileUploadService { 
    private readonly IAmazonS3 _amazonS3Client; 
    private readonly TransferUtility _fileTransferUtility; 

    public AWSFileUploadService(IAmazonS3 s3) { 
     _amazonS3Client = s3; 
     //Not sure about this next class but should consider abstracting it as well. 
     _fileTransferUtility = new TransferUtility(_amazonS3Client); 
    } 

    public bool DoSomethingWithKinesisEvent(KinesisEvent kinesisEvent) { 
     //code removed for brevity 
     return true; 
    } 
} 

를 교체 할 수 있습니다.

다음 예제

는 너무 어디에 어떻게 사용되는 기반으로 구현 문제로 간주 될 수있는 시스템 환경 추상화가 완전히 제거 될 수이 디자인과, 사실 MOQ

[TestMethod] 
public void TestKinesisMessage() { 
    //Arrange 
    var testMessage = "59d6572f028c52057caf13ff"; 
    var testStream = "testStream"; 
    var kinesisEvent = BuildKinesisTestRequest(testMessage, testStream); 
    var lambdaServiceMock = new Mock<ILambdaContext>(); 
    var fileUploadServiceMock = new Mock<IFileUploadService>();    
    //Replace the lazy initialization of the service 
    AWSLambdaFileProcessingService.FileUploadService = 
     new Lazy<IFileUploadService>(() => fileUploadServiceMock.Object); 
    var subject = new AWSLambdaFileProcessingService(); 

    //Act 
    subject.ProcessKinesisMessageById(kinesisEvent, lambdaServiceMock.Object); 

    //Assert 
    fileUploadServiceMock.Verify(_ => _.DoSomethingWithKinesisEvent(kinesisEvent), Times.AtLeastOnce()); 
} 

사용합니다.

+0

당신의 도움에 감사드립니다. 나는 시험에서 게으른 공장을 대체 할 적절한 방법에 대해 아직도 확신하지 못하고있다. 위의 권장 사항과 일부 미결 항목을 포함하는 코드 스 니펫을 추가하여 아래에 답변을 추가했습니다. 게으른 공장의 추상화를 테스트하는 방법에 대한 예가 있습니까? 도와 줘서 고마워. – apleroy

+2

@apleroy에는 서비스가 예상대로 호출되었는지 확인하는 Moq를 사용하여 Function에 대한 분리 된 단위 테스트의 간단한 예제가 포함되었습니다. 보다 복잡한 테스트를 작성할 수 있습니다. 이것은 단지 서비스의 Lazy 초기화를 대체하는 방법을 보여주기위한 것입니다. – Nkosi

+0

이것은 매우 도움이됩니다 - 감사합니다! – apleroy

2

이 답변은 @ Nkosi의 대답에서 권장 사항을 구현하려는 시도입니다.

저는 게으른 팩토리를 오버라이드하는 방법에 익숙하지 않고 다른 방법을 시도해 보았습니다. 아래는 이것을 달성하기위한 구현 방법에 대한 나의 시도입니다. 환경 변수에 대한 새로운 추상화는 게으른 팩토리에 의해 생성 된 의존성을 수용하기위한 ILambdaContext 인터페이스의 새로운 구현과 함께 아래에 포함됩니다. 원래의 질문을 보완하기 위해이 답변을 게시하고 @ Nkosi의 매우 유용한 답변에 대한 짧은 설명을 넘어 확장합니다.

// 코드 이것은 AWS 람다 함수

시작 -에만 리팩토링이 요청을 수락하고 새로 만든 서비스로 통과 (처리 로직의 삶)

public class AWSLambdaFileProcessingService 
{ 
    [LambdaSerializer(typeof(JsonSerializer))] 
    public void ProcessKinesisMessageById(KinesisEvent kinesisEvent, ILambdaContext context) 
    { 
     Console.WriteLine("Processing Kinesis Request"); 

     IKinesisEventProcessingService kinesisEventProcessingService = new KinesisEventProcessingService(context); 
     kinesisEventProcessingService.ProcessKinesisEvent(kinesisEvent); 
    } 
} 

이 새로운 서비스입니다 이 같은 상황이 테스트에 사용될 수 ILambdaContext의 구현을 허용 F이다

public class KinesisEventProcessingService : IKinesisEventProcessingService 
{ 
    private IFileUploadService _fileUploadService; 

    // constructor to attach Lazy loaded IFileUploadService 
    public KinesisEventProcessingService(ILambdaContext context) 
    { 
     AWSLambdaFileProcessingServiceContext AWSLambdaFileProcessingServiceContext = 
      LambdaContextFactory.BuildLambdaContext(context); 

     _fileUploadService = AWSLambdaFileProcessingServiceContext.FileUploadService; 
    } 

    public void ProcessKinesisEvent(KinesisEvent kinesisEvent) 
    { 

     _fileUploadService.DoSomethingWithKinesisEvent(kinesisEvent); 
     // .... 

    } 
} 

입력에 작용하는 모든 서비스를 캡슐화 테스트에 연결된 서비스 또는 재정의

public class AWSLambdaFileProcessingServiceContext : ILambdaContext 
{ 
    public AWSLambdaFileProcessingServiceContext() 
    { 
     FileUploadService = default(IFileUploadService); 
    } 

    public string AwsRequestId { get; } 
    // ... ILambdaContext properties 
    public TimeSpan RemainingTime { get; } 

    // Dependencies 
    public IFileUploadService FileUploadService { get; set; } 

} 

// static class for attaching dependencies to the context 
public static class LambdaContextFactory 
{ 
    public static AWSLambdaFileProcessingServiceContext BuildLambdaContext(ILambdaContext context) 
    { 
     // cast to implementation that has dependencies as properties of context 
     AWSLambdaFileProcessingServiceContext serviceContext = default(AWSLambdaFileProcessingServiceContext); 

     if (context.GetType().Equals(typeof(AWSLambdaFileProcessingServiceContext))) 
     { 
      serviceContext = (AWSLambdaFileProcessingServiceContext)context; 
     } 
     else 
     { 
      serviceContext = new AWSLambdaFileProcessingServiceContext(); 
     } 

     // lazily inject dependencies 
     if (serviceContext.FileUploadService == null) 
     { 
      serviceContext.FileUploadService = FileUploadService.Value; 
     } 

     return serviceContext; 
    } 

    public static Lazy<IFileUploadService> FileUploadService = new Lazy<IFileUploadService>(() => 
    { 
     ISystemEnvironmentService env = new SystemEnvironmentService(); 
     IAmazonS3 s3 = new AmazonS3Client(
      env.GetEnvironmentVariable("AWS_S3_KEY"), 
      env.GetEnvironmentVariable("AWS_S3_SECRET_KEY") 
     ); 
     IFileUploadService service = new AWSFileUploadService(s3); 
     return service; 
    }); 

이는 람다 함수

/// <summary> 
    /// This tests asserts that the Lambda function handles the input and calls the mocked service 
    /// </summary> 
    [Fact()] 
    public void TestKinesisMessage() 
    { 
     // arrange 
     string testMessage = "59d6572f028c52057caf13ff"; 
     string testStream = "testStream"; 

     IFileUploadService FileUploadService = new AWSFileUploadService(new Mock<IAmazonS3>().Object); 
     // create the custom context and attach above mocked FileUploadService from Lazy factory 
     var context = new AWSLambdaFileProcessingServiceContext(); 
     context.FileUploadService = FileUploadService; 

     var lambdaFunction = new AWSLambdaFileProcessingService(); 

     KinesisEvent kinesisEvent = BuildKinesisTestRequest(testMessage, testStream); 

     // act & assert 
     try 
     { 
      lambdaFunction.ProcessKinesisMessageById(kinesisEvent, context); 
     } 
     catch (Exception e) 
     { 
      // https://stackoverflow.com/questions/14631923/xunit-net-cannot-find-assert-fail-and-assert-pass-or-equivalent 
      Assert.True(false, "Error processing Kinesis Message :" + e.StackTrace); 
     } 
    }