Esse dias tive que fazer uma api que realizava transações de cartões de crédito e fazia diversas validações, como por exemplo, se a quantidade solicitada pelo vendedor é maior do que o limite existente no cartão ou se o cartão está bloqueado. Levando esses pontos em consideração, temos que efetuar pelo menos 2 testes:
Verificar o valor da conta ultrapassa o limite
Verificar se o cartão está ativo
Verificar se a transação foi aprovada se as verificações acima retornarem falso
O JSON que será recebido pela nossa API será conforme abaixo:
{ "status": true, "number":123456, "limit":1000, "transaction":{ "amount":500 } }
Agora vamos ao código: Primeira coisa é instalar as dependências:
python3 -m pip install pytest flask
Agora vamos escrever os testes, para isso vou utilizar uma ferramenta chamada Pytest. Arquivo: test_app.py
#!/usr/bin/python3 import os import tempfile import pytest from app import app @pytest.fixture def client(): app.config['TESTING'] = True client = app.test_client() yield client def test_valid_transaction(client): card = { "status": True, "number":123456, "limit":1000, "transaction":{ "amount":500 } } rv = client.post("/api/transaction",json=card) assert True == rv.get_json().get("aprovado") assert 500 == rv.get_json().get("novoLimite") def test_above_limit(client): card = { "status": True, "number":123456, "limit":1000, "transaction":{ "amount":1500 } } rv = client.post("/api/transaction",json=card) assert False == rv.get_json().get("aprovado") assert "Compra acima do limite" in rv.get_json().get("motivo") def test_blocked_card(client): card = { "status": False, "number":123456, "limit":1000, "transaction":{ "amount":500 } } rv = client.post("/api/transaction",json=card) assert False == rv.get_json().get("aprovado") assert "Cartao bloqueado" in rv.get_json().get("motivo")
Agora vamos criar um arquivo chamado app.py que será a nossa API, esse arquivo ainda não está completo, é só para ver se os testes estão funcionando.
#!/usr/bin/python3 from flask import Flask, request, jsonify app = Flask(__name__) @app.route("/api/transaction",methods=["POST"]) def transacao(): response = {"aprovado":True,"novoLimite":10} return jsonify(response) if __name__ == '__main__': app.run(debug=True)
Para executar os testes execute o comando:
pytest
Obviamente todos os testes vão falhar e o nosso objetivo é fazer eles darem certo. Com os testes falhando a saída será parecida com essa:
============================================== test session starts ============================================== platform linux -- Python 3.6.5, pytest-3.3.2, py-1.5.2, pluggy-0.6.0 rootdir: /home/alisson, inifile: collected 3 items blog/test_app.py FFF [100%] =================================================== FAILURES ==================================================== ____________________________________________ test_valid_transaction _____________________________________________ client = > def test_valid_transaction(client): card = { "status": True, "number":123456, "limit":1000, "transaction":{ "amount":500 } } rv = client.post("/api/transaction",json=card) assert True == rv.get_json().get("aprovado") > assert 500 == rv.get_json().get("novoLimite") E AssertionError: assert 500 == 10 E + where 10 = ('novoLimite') E + where = {'aprovado': True, 'novoLimite': 10}.get E + where {'aprovado': True, 'novoLimite': 10} = >() E + where > = .get_json blog/test_app.py:28: AssertionError _______________________________________________ test_above_limit ________________________________________________ client = > def test_above_limit(client): card = { "status": True, "number":123456, "limit":1000, "transaction":{ "amount":1500 } } rv = client.post("/api/transaction",json=card) > assert False == rv.get_json().get("approved") E AssertionError: assert False == None E + where None = ('approved') E + where = {'aprovado': True, 'novoLimite': 10}.get E + where {'aprovado': True, 'novoLimite': 10} = >() E + where > = .get_json blog/test_app.py:40: AssertionError _______________________________________________ test_blocked_card _______________________________________________ client = > def test_blocked_card(client): card = { "status": False, "number":123456, "limit":1000, "transaction":{ "amount":500 } } rv = client.post("/api/transaction",json=card) > assert False == rv.get_json().get("approved") E AssertionError: assert False == None E + where None = ('approved') E + where = {'aprovado': True, 'novoLimite': 10}.get E + where {'aprovado': True, 'novoLimite': 10} = >() E + where > = .get_json blog/test_app.py:53: AssertionError =========================================== 3 failed in 3.06 seconds ============================================
Note que falharam no total 3 testes:
test_valid_transaction
> assert 500 == rv.get_json().get("novoLimite") E AssertionError: assert 500 == 10
Nesse teste era esperado que o novo limite do cartão fosse 500 e foi retornado 10.
test_above_limit
> assert False == rv.get_json().get("aprovado") E AssertionError: assert False == None
Nesse teste esperado que o valor de aprovado fosse igual a False
test_blocked_card
> assert False == rv.get_json().get("aprovado") E AssertionError: assert False == None
Nesse também era esperado que o valor de aprovado fosse igual a False, pois as transações não podem ser permitidas. Agora vamos a aplicação principal. Para fazer a validação das transações vou criar um decorator chamado checar_cartao, ele ficará da seguinte forma:
def checar_cartao(f): wraps(f) def validacoes(*args, **kwargs): dados = request.get_json() if not dados.get("status"): response = {"aprovado":False, "novoLimite":dados.get("limit"), "motivo":"Cartao bloqueado"} return jsonify(response) if dados.get("limit") < dados.get("transaction").get("amount"): response = {"aprovado":False, "novoLimite":dados.get("limit"), "motivo":"Compra acima do limite"} return jsonify(response) return f(*args, **kwargs) return(validacoes)
e chamá-lo antes da requisição ser respondida:
@app.route("/api/transaction",methods=["POST"]) @checar_cartao def transacao(): // codigo da funcao
Agora explicando o código acima.
O Decorator em Python é uma função que retorna uma função, então qual a lógica desse decorator? Ao invés de fazer uma série de IFs dentro do código principal da função da minha API, antes da requisição chegar ela é passada pelo decorator que tem a função validacoes, onde nela são verificados o limite do cartão de crédito e o seu status, caso verdadeira as condições é retornada a função jsonify que devolve a transação negada. Caso todas as condições sejam falsas, no final temos o return f(*args, **kwargs), que devolve a função original, no caso a função transacao e o fluxo do programa segue normalmente. Assim o código do app.py ficou da seguinte maneira:
#!/usr/bin/python3 from flask import Flask, request, jsonify from functools import wraps app = Flask(__name__) def checar_cartao(f): wraps(f) def validacoes(*args, **kwargs): dados = request.get_json() if not dados.get("status"): response = {"aprovado":False, "novoLimite":dados.get("limit"), "motivo":"Cartao bloqueado"} return jsonify(response) if dados.get("limit") < dados.get("transaction").get("amount"): response = {"aprovado":False, "novoLimite":dados.get("limit"), "motivo":"Compra acima do limite"} return jsonify(response) return f(*args, **kwargs) return(validacoes) @app.route("/api/transaction",methods=["POST"]) @checar_cartao def transacao(): card = request.get_json() novo_limite = card.get("limit") - card.get("transaction").get("amount") response = {"aprovado":True,"novoLimite":novo_limite} return jsonify(response) if __name__ == '__main__': app.run(debug=True)
Faça as alterações no seu código e rode os testes novamente, a saída agora deve ser parecida com a saída abaixo:
============================================== test session starts ============================================== platform linux -- Python 3.6.5, pytest-3.3.2, py-1.5.2, pluggy-0.6.0 rootdir: /home/alisson/blog, inifile: collected 3 items test_app.py ... [100%] =========================================== 3 passed in 0.14 seconds ============================================
Isso significa que todos os testes passaram. É nóis valeu!