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!