Motivos pelos quais seu código Python é difícil de testar com o Princípio da Responsabilidade Única e como corrigir isso!

O Princípio da Responsabilidade Única (SRP, na sigla em inglês) é a base dos princípios SOLID. Ele afirma que uma classe deve ter um, e apenas um, motivo para ser alterada.
Quando ignoramos esse princípio, criamos componentes “Classes Deus” que sabem demais e fazem demais. Essas classes são frágeis: uma alteração no banco de dados↳Banco de dados134 conteúdosSQL ou NoSQL: eis a questão!!Data · mar 2020Banco de dados: como organizar e dar segurança para milhões de dados de loteriasData · mai 20215 serviços gratuitos na cloud para bancos de dados PostgresData · fev 2025Ver tudo em Data → pode quebrar a lógica de validação, ou uma mudança nas regras de negócio pode quebrar o sistema de notificação por e-mail.
Neste guia, analisaremos um antipadrão comum em um recurso de registro de usuários e o refatoraremos em uma arquitetura limpa e desacoplada.
❌ O “Antes”: A Violação do SRP
Aqui está um exemplo típico de código legado. Temos uma classe chamada UserService, mas ela não está se comportando como um serviço; está se comportando como um monolito.
Por que isso é ruim:
- Alto acoplamento: Está fortemente acoplado a regras de validação específicas e instruções de impressão específicas do banco de dados.
- Difícil de testar: Não é possível testar a lógica de registro sem acionar a lógica do banco de dados.
- Vários motivos para alteração: Este arquivo será aberto se as regras de senha forem alteradas. Este arquivo será aberto se o banco de dados for alterado.
class UserService : </code>
<code> def register ( self, name, email, password ): </code>
<code> # Responsabilidade 1: Lógica de Validação </code>
<code> if len (name) < 3 : </code>
<code> raise ValueError( "Nome muito curto" ) </code>
<code> if "@" not in email: </code>
<code> raise ValueError( "Email inválido" ) </code>
<code> if len (password) < 6 : </code>
<code> raise ValueError( "Senha muito fraca" ) </code>
<code> # Responsabilidade 2: Lógica do Banco de Dados (Verificar existência) </code>
<code> print ( f"[DB] Verificando se {email} existe..." ) </code>
<code> if email == "taken@example.com" : </code>
<code> raise ValueError( "Email já em uso" ) </code>
<code> # Responsabilidade 3: Lógica de Persistência (Salvar) </code>
<code> print ( f"[DB] INSERT INTO users (name, email) VALUES (' {name} ', ' {email} ')" ) </code>
<code> # Responsabilidade 4: Lógica de Notificação </code>
<code> print ( f"[MAIL] Enviando e-mail de boas-vindas para {email} ..." )</code>
</pre>
<p><strong>Uso:</strong></p>
<pre class="rh ri rj rk rl rm qx rn hv ro bv cc"><code>#
Serviço de uso = UserService()
service.register( "Alice" , "alice@example.com" , "123456" )✅ A Refatoração: Separação de Preocupações
Para corrigir isso, precisamos identificar as responsabilidades distintas e separá-las em classes próprias.
1. A Entidade (Estrutura de Dados)
Primeiro, criamos uma representação dos dados. Essa classe não possui lógica; ela apenas armazena o estado.
- Motivo da alteração: Somente se
phone_numberforem adicionados atributos de usuário (como ).
from dataclasses import dataclass
@dataclass
class User :
name: str
email: str
password: str2. O Validador (Regras de Negócio)
Extraímos as regras de validação. Esta classe garante a integridade dos dados.
- Motivo da alteração: Somente se as regras para dados válidos forem alteradas.
class UserValidator :
def validate ( self, user: User ):
if len (user.name) < 3 :
raise ValueError( "Nome muito curto" )
if "@" not in user.email:
raise ValueError( "Email inválido" )
if len (user.password) < 6 :
raise ValueError( "Senha muito fraca" )3. O Repositório (Persistência)
Abstraímos as operações do banco de dados. Esta classe lida com o “como” armazenar os dados.
- Motivo para alteração: Somente se a tecnologia do banco de dados (SQL, NoSQL, Arquivo) for alterada.
class UserRepository :
def exists ( self, email: str ) -> bool :
# Simula verificação no banco de dados
return email == "taken@example.com"
def save ( self, user: User ):
# Simula salvamento no banco de dados
print ( f"[DB] INSERT INTO users VALUES (' {user.name} ', ' {user.email} ')" )4. O Serviço (O Orquestrador)
Finalmente, reescrevemos o código UserService. Observe como ele ficou limpo. Ele não sabe como validar ou salvar ; simplesmente delega essas tarefas aos especialistas (as dependências injetadas).
- Motivo da alteração: Somente se o fluxo de trabalho do cadastro for alterado (por exemplo, adicionando uma etapa para verificar o número de telefone via SMS).
class UserService :
def __init__ ( self, validator: UserValidator, repository: UserRepository ):
# Injeção de Dependência: Solicitamos as ferramentas necessárias
self.validator = validator
self.repository = repository
def register ( self, name, email, password ):
user = User(name, email, password)
# Etapa 1: Validar
self.validator.validate (user)
# Etapa 2: Verificar as Regras de Negócio
if self.repository.exists (email):
raise ValueError( "Email já está em uso" )
# Etapa 3: Persistir
self.repository.save (user)
print ( ">">> Usuário registrado com sucesso." )Exemplo de uso
Ao interligar os componentes, criamos um sistema flexível.
# 1. Configurar as dependências
repo = UserRepository()
val = UserValidator()
# 2. Injetar as dependências no serviço
service = UserService(validator=val, repository=repo)
# 3. Executar a lógica
try :
service.register( "Bob" , "bob@example.com" , "securePass123" )
except ValueError as e:
print ( f"Erro: {e} " )Conclusão
Ao aplicar o Princípio da Responsabilidade Única, transformamos um script rígido em uma arquitetura de software profissional.
Os benefícios dessa abordagem são claros:
- Testabilidade: Agora você pode realizar testes unitários
UserValidatorisoladamente, sem a necessidade de um banco de dados falso. - Facilidade de manutenção: Se o esquema do banco de dados for alterado, você só precisa editar o arquivo
UserRepository. OUserServicerestante permanece intacto. - Legibilidade: O
UserServicetexto agora se apresenta como uma narrativa concisa dos acontecimentos , em vez de uma lista confusa de como eles acontecem.
Essa separação reduz a carga cognitiva do desenvolvedor e garante que seu aplicativo seja robusto o suficiente para lidar com mudanças futuras com risco mínimo.
Mais de Cláudio Raposo
- Você está confundindo Injeção de Dependência com Inversão de Dependência? Vou te ajudar a entender!
- Abstração de Infraestrutura e Microsserviços Portáteis: Implementando Dapr no Azure Container Apps
- Segregação de Responsabilidades de Comando e Consulta (CQRS): Desacoplamento Extremo com Azure Cosmos DB e Azure AI Search






