[PYTHON] [AWS] Let's run a unit test of Lambda function in the local environment

Premise

This time, I would like to verify on the following assumptions.

In addition, if you use SAM, you can build DynamoDB etc. in a pseudo local environment using Docker environment, but this time, I would like to verify the test with an eye on unit test (future automatic test). I think.

Project creation

So, first of all, create a project. If you are not sure about creating a project with SAM, please do so in advance.

-[AWS] Serverless Application Model (SAM) Basic Summary -[AWS] Create API Gateway + Lambda + DynamoDB sample with Serverless Application Model (SAM)

We recommend that you read through.

$ sam init --runtime=python3.8
Which template source would you like to use?
	1 - AWS Quick Start Templates
	2 - Custom Template Location
Choice: 1

Project name [sam-app]:

Cloning app templates from https://github.com/awslabs/aws-sam-cli-app-templates.git

AWS quick start application templates:
	1 - Hello World Example
	2 - EventBridge Hello World
	3 - EventBridge App from scratch (100+ Event Schemas)
	4 - Step Functions Sample App (Stock Trader)
	5 - Elastic File System Sample App
Template selection: 1

-----------------------
Generating application:
-----------------------
Name: sam-app
Runtime: python3.8
Dependency Manager: pip
Application Template: hello-world
Output Directory: .

Next steps can be found in the README file at ./sam-app/README.md

Installation of required packages

Install the Python packages needed to run unit tests.

$ pipenv install pytest pytest-mock mocker moto --dev

Unit test

You can call the Lamnda function with the following command to run a unit test for each Lambda function. First, let's run a test of the Hello World Lambda function that is created by default (this test code is also created by default).

$ python -m pytest tests
============================= test session starts ==============================
platform darwin -- Python 3.8.5, pytest-6.0.1, py-1.9.0, pluggy-0.13.1
rootdir: /Users/******/aws/github/sam-app
plugins: mock-3.3.0
collected 1 item

tests/unit/test_handler.py .                                             [100%]

============================== 1 passed in 0.02s ===============================

You can see that one test was successful.

Add DynamoDB access code

This time, since the purpose is only to unit test, we will make only the changes necessary for the test without changing the SAM settings. Now, let's add a process to access (add a record) DynamoDB.

hello_world/app.py


import json
import boto3
import os
from datetime import datetime

def lambda_handler(event, context):
    try:
        event_body = json.loads(event["body"])
        dynamodb = boto3.resource("dynamodb")

        table = dynamodb.Table("Demo")
        table.put_item(
            Item={
                "Key": event_body["test"],
                "CreateDate": datetime.utcnow().isoformat()
            }
        )

        return {
            "statusCode": 200,
            "body": json.dumps({
                "message": "hello world",
            }),
        }
    except Exception as e:
        return {
            "statusCode": 500,
            "body": json.dumps({
                "message": e.args
            }),
    }

Added DynamoDB mockup to test code

tests/unit/test_handler.py


import boto3
import json
import pytest
from hello_world import app
from moto import mock_dynamodb2

@pytest.fixture()
def apigw_event():
    """ Generates API GW Event"""

    return {
        "body": '{ "test": "body"}',
        "resource": "/{proxy+}",
        "requestContext": {
            "resourceId": "123456",
            "apiId": "1234567890",
            "resourcePath": "/{proxy+}",
            "httpMethod": "POST",
            "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
            "accountId": "123456789012",
            "identity": {
                "apiKey": "",
                "userArn": "",
                "cognitoAuthenticationType": "",
                "caller": "",
                "userAgent": "Custom User Agent String",
                "user": "",
                "cognitoIdentityPoolId": "",
                "cognitoIdentityId": "",
                "cognitoAuthenticationProvider": "",
                "sourceIp": "127.0.0.1",
                "accountId": "",
            },
            "stage": "prod",
        },
        "queryStringParameters": {"foo": "bar"},
        "headers": {
            "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
            "Accept-Language": "en-US,en;q=0.8",
            "CloudFront-Is-Desktop-Viewer": "true",
            "CloudFront-Is-SmartTV-Viewer": "false",
            "CloudFront-Is-Mobile-Viewer": "false",
            "X-Forwarded-For": "127.0.0.1, 127.0.0.2",
            "CloudFront-Viewer-Country": "US",
            "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
            "Upgrade-Insecure-Requests": "1",
            "X-Forwarded-Port": "443",
            "Host": "1234567890.execute-api.us-east-1.amazonaws.com",
            "X-Forwarded-Proto": "https",
            "X-Amz-Cf-Id": "aaaaaaaaaae3VYQb9jd-nvCd-de396Uhbp027Y2JvkCPNLmGJHqlaA==",
            "CloudFront-Is-Tablet-Viewer": "false",
            "Cache-Control": "max-age=0",
            "User-Agent": "Custom User Agent String",
            "CloudFront-Forwarded-Proto": "https",
            "Accept-Encoding": "gzip, deflate, sdch",
        },
        "pathParameters": {"proxy": "/examplepath"},
        "httpMethod": "POST",
        "stageVariables": {"baz": "qux"},
        "path": "/examplepath",
    }

@mock_dynamodb2
def test_lambda_handler(apigw_event, mocker):
    dynamodb = boto3.resource('dynamodb')
    dynamodb.create_table(
        TableName='Demo',
        KeySchema=[
            {
                'AttributeName': 'Key',
                'KeyType': 'HASH'
            },
            {
                'AttributeName': 'CreateDate',
                'KeyType': 'RANGE'
            }
        ],
        AttributeDefinitions=[
            {
                'AttributeName': 'Key',
                'AttributeType': 'S'
            },
            {
                'AttributeName': 'CreateDate',
                'AttributeType': 'S'
            }
        ],
        ProvisionedThroughput={
            'ReadCapacityUnits': 10,
            'WriteCapacityUnits': 10
        }
    )

    ret = app.lambda_handler(apigw_event, "")
    data = json.loads(ret["body"])

    assert ret["statusCode"] == 200
    assert "message" in ret["body"]
    assert data["message"] == "hello world"
    # assert "location" in data.dict_keys()

The main corrections are

--Addition of definitions required for import --Added @ mock_dynamodb2 before test_lambda_handler --Create a DynamoDB table in a pseudo manner before calling the Lambda function with test_lambda_handler

is.

Unit test execution

Now, let's run the unit test immediately.

$ python -m pytest tests
============================= test session starts ==============================
platform darwin -- Python 3.8.5, pytest-6.0.1, py-1.9.0, pluggy-0.13.1
rootdir: /Users/******/aws/github/sam-app
plugins: mock-3.3.0
collected 1 item

tests/unit/test_handler.py .                                             [100%]

=============================== warnings summary ===============================
[pytest]
/Users/******/.local/share/virtualenvs/sam-app-3Tr4jFKA/lib/python3.8/site-packages/boto/plugin.py:40
  /Users/******/.local/share/virtualenvs/sam-app-3Tr4jFKA/lib/python3.8/site-packages/boto/plugin.py:40: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
    import imp

/Users/******/.local/share/virtualenvs/sam-app-3Tr4jFKA/lib/python3.8/site-packages/moto/cloudformation/parsing.py:407
  /Users/******/.local/share/virtualenvs/sam-app-3Tr4jFKA/lib/python3.8/site-packages/moto/cloudformation/parsing.py:407: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3, and in 3.9 it will stop working
    class ResourceMap(collections.Mapping):

-- Docs: https://docs.pytest.org/en/stable/warnings.html

It's been successful, but I'm getting a warning. This means that in the current version of Python, some functions used by moto etc. have been stopped. This warning does not affect the unit test, so create a file with the name pytest.ini in the project home, write the following and save it.

pytest.ini


[pytest]
filterwarnings =
    ignore::DeprecationWarning

Let's try again.

$ python -m pytest tests
============================= test session starts ==============================
platform darwin -- Python 3.8.5, pytest-6.0.1, py-1.9.0, pluggy-0.13.1
rootdir: /Users/******/aws/github/sam-app, configfile: pytest.ini
plugins: mock-3.3.0
collected 1 item

tests/unit/test_handler.py .                                             [100%]

============================== 1 passed in 2.10s ===============================

This time it ended without warning.

Summary

It is desirable that unit tests are automatically executed when the source code is changed. This time, since the unit test is executed locally, only the source code part is modified, but if it is an extension of the CI / CD pipeline, it should be in requirements.txt or each definition file. Also needs to be fixed. Next time, I would like to do verification at some point, including the connection with the pipeline in that area.

Sample code repository

https://github.com/hito-psv/test-demo-001

Recommended Posts

[AWS] Let's run a unit test of Lambda function in the local environment
Get the caller of a function in Python
How to unit test a function containing the current time using freezegun in python
Write AWS Lambda function in Python
[Python] Make the function a lambda function
A function that measures the processing time of a method in python
Create a function to get the contents of the database in Go
Let's prove the addition theorem of trigonometric functions by replacing the function with a function in SymPy (≠ substitution)
A memo for utilizing the unit test mechanism KUnit of the Linux kernel
Write the test in a python docstring
Let's create a chat function with Vue.js + AWS Lambda + dynamo DB [AWS settings]
Run the Python interpreter in a script
Test & Debug Tips: Create a file of the specified size in Python
Let's claim the possibility of pyenv-virtualenv in 2021
Prepare the environment of Chainer on EC2 spot instance with AWS Lambda
Let's run a Bash script in Java
Test the number of times you have thrown a query (sql) in django
[AWS Lambda] Create a deployment package using the Docker image of Amazon Linux
Instructions for connecting Google Colab. To the local runtime in a Windows environment
Create a Vim + Python test environment in 1 minute
Periodically run a python program on AWS Lambda
Draw a graph of a quadratic function in Python
Use the latest pip in a virtualenv environment
Make a copy of the list in Python
Find the number of days in a month
Set a fixed IP in the Linux environment
Fix the argument of the function used in map
Output in the form of a python array
[Django] Let's try to clarify the part of Django that was somehow through in the test
When a local variable with the same name as a global variable is defined in the function
This is a sample of function application in dataframe.
Used from the introduction of Node.js in WSL environment
The story of building the fastest Linux environment in the world
I want to write in Python! (2) Let's write a test
# Function that returns the character code of a string
I tried running TensorFlow in AWS Lambda environment: Preparation
Let's use the open data of "Mamebus" in Python
Let's test the medical collapse hypothesis of the new coronavirus
A reminder about the implementation of recommendations in Python
What does the last () in a function mean in Python?
The value of meta when specifying a function with no return value in Dask dataframe apply
The story of debugging in the local environment because the compilation did not work with Read the Docs
The image is displayed in the local development environment, but the image is not displayed on the remote server of VPS
Let's statically check and format the code of E2E automatic test written in Python [VS Code]
Let's create a function for parametrized test using frame object
A note on the default behavior of collate_fn in PyTorch
Find out the apparent width of a string in python
I tried the super-resolution algorithm "PULSE" in a Windows environment
Create a local scope in Python without polluting the namespace
Check the operation of Python for .NET in each environment
Django + MongoDB development environment maintenance (in the middle of writing)
Have the equation graph of the linear function drawn in Python
Get the number of specific elements in a python list
Check the scope of local variables with the Python locals function.
I made a function to check the model of DCGAN
How to develop in a virtual environment of Python [Memo]
[Note] Import of a file in the parent directory in Python
How to run the Export function of GCP Datastore automatically
Change the log retention period of CloudWatch Logs in Lambda
To write a test in Go, first design the interface
Computing the BigQuery cost of a single Airflow DAG run