01 maio 2022

Armadilhas das variáveis de ambiente em testes unitários Python

Atualmente com o grande uso de computação em nuvem, também, é comum nós desenvolvedores criamos ferramentas que precisam de um certo dinamismo em alguns pontos do software, como por exemplo, chaves de acesso, informações do “host” do banco de dados e exposição de porta. Para ter essa fácil troca de valores o caminho mais fácil é usar variáveis de ambiente. Porém em um cenário de testes com Python e UnitTest (pacote encontrado na biblioteca padrão), alguns problemas podem aparecer se não utilizada os recursos corretos para se compor os testes.

Tomemos como exemplo o seguinte código para teste:

import os
from urllib.parse import urlparse
from unittest import TestCase


def resolver_service():
    url = os.getenv('SERVICE_URL', 'https://duck.com/')
    service = urlparse(url)
    return service.scheme, service.netloc


class Test(TestCase):
    def test_service_default(self):
        protocol, name = resolver_service()

        self.assertEqual(protocol, 'https')
        self.assertEqual(name, 'duck.com')

Ao ser executado, ele retorna os valores (padrões) esperado. Porém precisamos alterar esses mesmo valores para validar seu real funcionamento. Uma das formas de inserir valores de variávies de ambiente no Python é injetando diretamento no os.environ com o nome da variável. Extendendo o exemplo com mais 2 unidades de teste.

class Test(TestCase):
    def test_service_default(self):
        protocol, name = resolver_service()

        self.assertEqual(protocol, 'https')
        self.assertEqual(name, 'duck.com')

    def test_service_ftp(self):
        os.environ['SERVICE_URL'] = 'ftp://example.tld'
        protocol, name = resolver_service()

        self.assertEqual(protocol, 'ftp')
        self.assertEqual(name, 'example.tld')

    def test_service_https(self):
        protocol, name = resolver_service()

        self.assertEqual(protocol, 'https')
        self.assertEqual(name, 'duck.com')

Ao executar temos um erro no último teste:

..F
======================================================================
FAIL: test_service_https (__main__.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/example_erro_testing.py", line 29, in test_service_https
    self.assertEqual(protocol, 'https')
AssertionError: 'ftp' != 'https'
- ftp
+ https

----------------------------------------------------------------------
Ran 3 tests in 0.001s

FAILED (failures=1)

O último teste está falhando, pois está carregando o valor do teste anterior. O esperado era pegar o valor padrão que seria https. Ou seja, o Python não limpa os valores por padrão para a próxima execução. Mas ele tem ferramentas adequadas para esse tipo de trabalho, que seriam os mocks. No caso iremos usar o patch para ser mais exato, patch.dict que após a execução dos testes, restaura os valores padrões do dicionário que é a estrutura que estamos manipulando no Python para ter os valores da variável de ambiente.

class Test(TestCase):
    def test_service_default(self):
        protocol, name = resolver_service()

        self.assertEqual(protocol, 'https')
        self.assertEqual(name, 'duck.com')

    @patch.dict('os.environ', {'SERVICE_URL': 'ftp://example.tld'})
    def test_service_ftp(self):
        protocol, name = resolver_service()

        self.assertEqual(protocol, 'ftp')
        self.assertEqual(name, 'example.tld')

    def test_service_https(self):
        protocol, name = resolver_service()

        self.assertEqual(protocol, 'https')
        self.assertEqual(name, 'duck.com')

Com isso já teremos nossos testes funcionando e escrito da forma correta quando se quer manipular valores de variáveis de ambiente.

09 abril 2022

Reconhecimento facial no laptop para o bem ou para o mal

Com o passar do tempo, tenho me adequado ao modo de vida “preguiçoso” - ou moderno. Acredito que por passar muitas vezes executando as mesmas tarefas que muitas vezes, são tediosas e suscetíveis a erros, chega um momento que a cabeça pede ajuda. Uma dessas tarefas é a questão das senhas. Digitar senhas é um fardo quando se precisa digitar dezenas e dezenas de vezes. Com mais e mais vazamento de dados, a responsabilidade de se manter se seguro aumenta, o que aumenta também o trabalho de manter sua vida digital minimamente segura (se é que é possível), porém se você não está disposto a gastar alguns “milhares” de dólares com licenças de sistemas operacionais fechados e confiar sua segurança (ou seus dados) a outro, o trabalho precisa ser feito na mão. Porém, graças a grande e boa comunidade do Software Livre, o trabalho é feito uma única vez para todo o sempre e com atualização e colabaração de muitos desenvolvedores em volta do mundo.

Uma das tecnologias já em uso pela indústria é o reconhecimento facial, tecnologia essa que tem muitas ressalvas no âmbito comercial[1] e muitas vezes até no pessoal[2]. Porém não deixa de ser uma tecnologia interessante para o grande público, quando pensamos em melhorar nosso dia a dia, como por exemplo, nunca mais digitar senhas ao ligar o computador ou executar uma tarefa com nível de permissão mais alto que exige senhas.

Pois bem, se você usa smartphone (o que é provável), deve saber da existência desse recurso de autenticação fácil em aparelhos Android ou iOS. Assim como nesses sistemas no Linux também existe uma maneira de “habilitar” essa funcionalidade de autenticação. Por meio do Howdy[3], um módulo PAM escrito atualmente em Python[4] é possível ter essa funcionalidade em questão de minutos. O único requisito é ter um computador com webcam.

Com a autenticação facial funcionando, não é mais preciso digitar senhas tanto nas tarefas de linha de comando quanto nas interfaces gráficas. Como é um módulo PAM, é possível destravar quase tudo com o reconhecimento facial, com exceção do molho de chaves “Keyring”. E também é possível especificar aplicações ou comandos disponíveis para requisitar o reconhecimento facial.

As coisas parecem bem mais fluidas, não é necessário digitar dezenas de vezes a senha pois esbarrou numa tecla errada, ou ficar “dançando” os dedos na impressão digital. Claro que não se resume apenas a simplicidade, pois toda facilidade nem sempre vem sem algum peso. Existem algumas ressalvas de segurança em relação a esse tipo de sistema de autenticação, por exemplo, ter apenas esse método de autenticação, ou até mesmo sobre burlar o sistema de autenticação com uma foto de alta qualidade. O próprio Howdy nos permiti 3 níveis de autenticação segura, uma com reconhecimento rápido, outra intermediário e uma terceira que leva muita mais tempo para reconhecer, porém é mais seguro pois evita fraudes com fotos de nossos rostos, por exemplo.

Notas e Referências 

24 março 2021

Se "achando" em um projeto (git) desconhecido

Uma das atividades comuns de um programador é dar suporte e manutenção para softwares já em funcionamento, muitas vezes em ambientes e tecnologias das quais não se tem domínio. Mas graças ao controle de versão que é quase que onipresente em projetos profissionais, podemos usar ele para nos encontrar no projeto.

Boa parte do trabalho é feita com o git log¹.

Procurando mensagens referentes a uma determinada palavra:

git log -g --grep "lambda function"

Uma busca na historia do repositorio com determinado termo dentro do codigo:

git log -p -S "lambda_function"

Para abranger todos os *branch* nessa busca, além do HEAD (atual), adicione o parâmetro --all:

git log -p --all -S "lambda_function"

Além desses meio se ambientar, também existe o git grep², que funciona de forma similar ao grep³.

Referências