[PYTHON] [AWS] Lançons un test unitaire de la fonction Lambda dans l'environnement local

supposition

Cette fois, je voudrais vérifier sur les hypothèses suivantes.

Si vous utilisez SAM, vous pouvez construire DynamoDB etc. dans un pseudo environnement local en utilisant l'environnement Docker, mais cette fois, je voudrais vérifier le test avec un œil sur le test unitaire (futur test automatique). Je pense.

Création de projet

Alors, tout d'abord, créez un projet. Si vous n'êtes pas sûr de créer un projet avec SAM, veuillez le faire à l'avance.

Nous vous recommandons de lire attentivement.

$ 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 des packages requis

Installez les packages Python nécessaires pour exécuter le test unitaire.

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

Test de l'unité

Vous pouvez appeler les fonctions Lamnda avec les commandes suivantes pour exécuter un test unitaire pour chaque fonction Lambda. Tout d'abord, exécutons un test de la fonction Lambda de HelloWorld qui est créée par défaut (ce code de test est également créé par défaut).

$ 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 ===============================

Vous pouvez voir qu'un test a réussi.

Ajouter un code d'accès DynamoDB

Cette fois, étant donné que le but est uniquement pour les tests unitaires, nous n'effectuerons que les modifications nécessaires pour le test sans modifier les paramètres SAM. Maintenant, ajoutons un processus pour accéder (ajouter un enregistrement) 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
            }),
    }

Ajout de la maquette DynamoDB pour tester le 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()

Les principales corrections sont

--Ajout de définitions requises pour l'importation --Ajout de @ mock_dynamodb2 avant test_lambda_handler --Avec test_lambda_handler, créez une pseudo table DynamoDB avant d'appeler la fonction Lambda.

est.

Exécution de test unitaire

Maintenant, exécutons le test unitaire immédiatement.

$ 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

Cela a réussi, mais je reçois un avertissement. Cela signifie que dans la version actuelle de Python, certaines fonctions utilisées par moto etc. ont été arrêtées. Cet avertissement n'affecte pas le test unitaire, alors créez un fichier avec le nom pytest.ini dans la maison du projet, écrivez ce qui suit et enregistrez-le.

pytest.ini


[pytest]
filterwarnings =
    ignore::DeprecationWarning

Essayons encore.

$ 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 ===============================

Cette fois, cela s'est terminé sans avertissement.

Résumé

Il est souhaitable que le test unitaire soit exécuté automatiquement lorsque le code source est modifié. Cette fois, comme le test unitaire est exécuté localement, seule la partie du code source est modifiée, mais s'il s'agit d'une extension du pipeline CI / CD, elle doit être dans requirements.txt ou dans chaque fichier de définition. Doit également être corrigé. La prochaine fois, j'aimerais faire une vérification à un moment donné, y compris la connexion avec le pipeline dans ce domaine.

Exemple de référentiel de code

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

Recommended Posts

[AWS] Lançons un test unitaire de la fonction Lambda dans l'environnement local
Récupérer l'appelant d'une fonction en Python
Comment tester unitaire une fonction contenant l'heure actuelle à l'aide de Freezegun en Python
Ecrire une fonction AWS Lambda en Python
[Python] Faire de la fonction une fonction lambda
Une fonction qui mesure le temps de traitement d'une méthode en python
Créez une fonction pour obtenir le contenu de la base de données dans Go
Prouvons le théorème d'addition d'une fonction triangulaire en remplaçant la fonction par une fonction dans SymPy (≠ substitution)
Remarques sur l'utilisation de KUnit, le mécanisme de test unitaire du noyau Linux
Ecrire le test dans la docstring python
Créons une fonction de chat avec Vue.js + AWS Lambda + dynamo DB [Paramètres AWS]
Exécuter l'interpréteur Python dans le script
Préparer l'environnement de Chainer sur l'instance spot EC2 avec AWS Lambda
Lançons le script Bash en Java
Vérifiez le nombre de fois où la requête (sql) a été lancée dans django avec un test
[AWS Lambda] Créer un package de déploiement à l'aide de l'image Amazon Linux Docker
Instructions pour connecter Google Colab. À l'environnement d'exécution local dans un environnement Windows
Créez un environnement de test Vim + Python en 1 minute
Exécutez régulièrement des programmes Python sur AWS Lambda
Dessiner un graphique d'une fonction quadratique en Python
Utilisez le dernier pip dans un environnement virtualenv
Copiez la liste en Python
Trouvez le nombre de jours dans un mois
Définir une adresse IP fixe dans l'environnement Linux
Correction des arguments de la fonction utilisée dans map
Sortie sous la forme d'un tableau python
[Django] Essayons de clarifier la partie de Django qui était en quelque sorte à travers le test
Lorsqu'une variable locale portant le même nom que la variable globale est définie dans la fonction
Ceci est un exemple d'application de fonction dans dataframe.
Utilisé depuis l'introduction de Node.js dans l'environnement WSL
L'histoire de la création de l'environnement Linux le plus rapide au monde
Je veux écrire en Python! (2) Écrivons un test
#Une fonction qui renvoie le code de caractère d'une chaîne de caractères
J'ai essayé d'exécuter TensorFlow dans l'environnement AWS Lambda: Préparation
Utilisons les données ouvertes de "Mamebus" en Python
Testons l'hypothèse d'effondrement médical du nouveau virus corona
Un mémorandum sur la mise en œuvre des recommandations en Python
Que signifie le dernier () dans une fonction en Python?
La valeur de meta lors de la spécification d'une fonction sans valeur de retour avec Dask dataframe s'applique
L'histoire du débogage dans l'environnement local car la compilation n'a pas fonctionné avec Read the Docs
L'image est affichée dans l'environnement de développement local, mais l'image n'est pas affichée sur le serveur distant de VPS.
Vérifions et formons statiquement le code du test automatique E2E écrit en Python [VS Code]
Créons une fonction pour le test paramétré à l'aide d'un objet frame
Remarque sur le comportement par défaut de collate_fn dans PyTorch
Découvrez la largeur apparente d'une chaîne en python
J'ai essayé l'algorithme de super résolution "PULSE" dans un environnement Windows
Créer une portée locale en Python sans polluer l'espace de noms
Vérifiez le fonctionnement de Python pour .NET dans chaque environnement
Maintenance de l'environnement de développement Django + MongoDB (en cours d'écriture)
Avoir le graphique d'équation de la fonction linéaire dessiné en Python
Obtenez le nombre d'éléments spécifiques dans la liste python
J'ai fait une fonction pour vérifier le modèle de DCGAN
Comment développer dans un environnement virtuel Python [Memo]
[Note] Importation de fichiers dans le répertoire parent en Python
Comment exécuter automatiquement la fonction d'exportation de GCP Datastore
Modifier la période de conservation des journaux CloudWatch Logs dans Lambda