Trilha de aprendizado · Nível 8 · Tutorial 3

Testar mudanças de estado em objetos

Escrever testes de classes que verificam inicialização, transições de estado, rejeição de operações inválidas e independência entre instâncias.

  • Nível: Intermediário
  • Duração: 16 min
  • 7 passos
Testar mudanças de estado em objetos

O que você vai percorrer

  1. Do contrato ao teste do estado inicial Crie a classe Playlist localmente e escreva um primeiro teste que observe seu estado inicial apenas pela interface pública. 2 min
  2. Verificar retorno e efeito de uma operação Aprenda a testar separadamente o valor devolvido por um método e a mudança observável no estado do objeto. 2 min
  3. Acompanhar sequências e verificar invariantes Teste uma sequência completa de transições da Playlist, conferindo estados intermediários, invariantes e resultados concretos. 2 min
  4. Confirmar que uma rejeição preserva o estado Verifique tanto a exceção esperada quanto a preservação do estado público após uma operação inválida. 3 min
  5. Manter cada teste independente Organize cada função de teste para criar e preparar o próprio objeto, permitindo executá-la sozinha ou em qualquer ordem. 2 min
  6. Detectar vazamento entre instâncias Construa um teste com duas playlists para verificar, pela interface pública, que alterações em uma instância não vazam para a outra. 2 min
  7. Aplicação final: reunir e executar a suíte Consolide a implementação e os testes da Playlist, execute a suíte localmente e revise como cada cenário verifica o contrato público. 4 min

O que você vai aprender

  • Verificar o estado público de um objeto após sua criação e após chamadas de métodos.
  • Testar sequências de operações que devem preservar as regras de validade do objeto.
  • Confirmar que operações rejeitadas não deixam alterações parciais quando esse for o contrato.
  • Construir testes independentes e detectar compartilhamento indevido de estado entre instâncias.

Antes de começar

  • Selecionar casos de teste e verificar exceções
  • Criar objetos com classes e métodos de instância
  • Distinguir atributos de instância e de classe
  • Preservar regras de validade em objetos

Passo 1 de 7

Do contrato ao teste do estado inicial

Crie a classe Playlist localmente e escreva um primeiro teste que observe seu estado inicial apenas pela interface pública.

O contrato observável da Playlist

O que o primeiro teste deve comprovar

Usaremos uma Playlist em memória: ela possui capacidade positiva, não aceita títulos repetidos e preserva a ordem de inclusão. Não há arquivos de áudio nem serviços externos.

Neste primeiro cenário, o foco é somente a criação válida. Ao construir Playlist(3), o teste deve observar que:

  • capacidade vale 3;
  • listar_faixas() devolve uma lista vazia.

A lista interna _faixas é detalhe de implementação. O teste deve consultar o estado pela interface pública.

Interface pública e estado interno

O teste se conecta apenas aos pontos públicos do objeto. O armazenamento interno permanece encapsulado.

Diagrama de um teste observando a capacidade e a lista de faixas pela parte pública de uma Playlist, enquanto a coleção interna aparece protegida dentro do objeto.

Verifique o comportamento visível; não acople o teste ao atributo interno _faixas.

Crie a implementação local

Organização dos arquivos

No seu computador, crie uma pasta de projeto com esta estrutura:

playlist_project/

  • playlist.py
  • tests/test_playlist.py

Salve o código abaixo em playlist.py. A implementação já contém as operações usadas ao longo do tutorial, mas agora examinaremos apenas o estado inicial.

playlist.py

Implementação completa da classe usada no tutorial.

python
class Playlist:
    def __init__(self, capacidade):
        if capacidade <= 0:
            raise ValueError("A capacidade deve ser positiva")

        self.capacidade = capacidade
        self._faixas = []

    def listar_faixas(self):
        return list(self._faixas)

    def adicionar(self, titulo):
        if titulo in self._faixas:
            raise ValueError("A faixa já está na playlist")
        if len(self._faixas) >= self.capacidade:
            raise ValueError("A playlist está cheia")

        self._faixas.append(titulo)
        return len(self._faixas)

    def remover(self, titulo):
        if titulo not in self._faixas:
            raise ValueError("A faixa não está na playlist")

        self._faixas.remove(titulo)

Escreva e execute o primeiro teste

Uma instância dentro da função de teste

Salve o teste abaixo em tests/test_playlist.py. Cada função test_* pode criar diretamente a instância necessária; não é preciso organizar os testes em classes.

A expectativa [] vem do contrato de uma Playlist recém-criada, não de uma inspeção de _faixas.

tests/test_playlist.py

Teste do estado inicial pela interface pública.

python
from playlist import Playlist


def test_playlist_inicia_com_capacidade_e_sem_faixas():
    playlist = Playlist(3)

    assert playlist.capacidade == 3
    assert playlist.listar_faixas() == []

Execute a partir de playlist_project

No terminal, com o ambiente que contém o pytest ativado, execute:

shell
python -m pytest

Escolha um teste resistente a mudanças internas

Qual teste verifica corretamente o estado inicial?

Considere o contrato público apresentado. Qual alternativa produz o teste menos acoplado ao armazenamento interno?

Passo 2 de 7

Verificar retorno e efeito de uma operação

Aprenda a testar separadamente o valor devolvido por um método e a mudança observável no estado do objeto.

Dois resultados para observar

Retorno não é o mesmo que efeito

Uma chamada de método pode produzir dois resultados distintos:

  • Retorno: o valor entregue pela expressão da chamada.
  • Efeito: a mudança observável no objeto depois da chamada.

O contrato determina o que deve ser verificado. Em adicionar(), testaremos o novo total retornado e a lista atualizada. Em remover(), o retorno esperado é None, mas a lista ainda deve mudar.

Diagrama de uma operação que produz um valor de retorno e também atualiza uma playlist ordenada.

A mesma chamada pode devolver um valor e alterar o estado público do objeto. Cada resultado precisa de sua própria verificação.

Testar adicionar()

Capture o retorno antes de consultar o estado

Pelo contrato, adicionar() inclui uma faixa válida e devolve o total de faixas após a inclusão. A chamada fica na ação do teste; depois, verificamos separadamente o retorno e o estado público.

Retorno e efeito de adicionar()

Acrescente este teste ao arquivo de testes usado no step anterior.

python
from playlist import Playlist


def test_adicionar_retorna_total_e_inclui_faixa():
    playlist = Playlist(capacidade=3)

    total = playlist.adicionar("Azul")

    assert total == 1
    assert playlist.listar_faixas() == ["Azul"]
    assert playlist.capacidade == 3

Dica

Expectativas vindas do contrato

Não use o próprio objeto para calcular o resultado esperado. Escreva explicitamente 1 e ["Azul"]: assim, uma implementação defeituosa não define a expectativa do teste.

Testar remover()

Mudança de estado com retorno None

Para testar remover(), primeiro prepare uma playlist que contenha a faixa. Faça isso pela operação pública adicionar(), sem alterar _faixas diretamente.

Pelo contrato, remover() exclui a faixa e retorna None. Portanto, um teste que verificasse apenas o retorno não confirmaria a remoção.

Retorno e efeito de remover()

python
def test_remover_retorna_none_e_retira_faixa():
    playlist = Playlist(capacidade=3)
    playlist.adicionar("Azul")

    retorno = playlist.remover("Azul")

    assert retorno is None
    assert playlist.listar_faixas() == []
    assert playlist.capacidade == 3

Exemplo

Por que usar is None?

assert retorno is None expressa diretamente que o contrato exige o objeto único None. Já assert playlist.listar_faixas() == [] verifica o efeito observável da remoção.

Complete as verificações

Retorno de adicionar()

Uma playlist vazia recebe retorno = playlist.adicionar("Sol"). Complete: assert retorno == ___

Retorno de remover()

Após preparar uma playlist com "Sol", executamos retorno = playlist.remover("Sol"). Complete: assert retorno is ___

Efeito de remover()

Depois de remover a única faixa, complete a verificação pública: assert playlist.listar_faixas() == ___

Passo 3 de 7

Acompanhar sequências e verificar invariantes

Teste uma sequência completa de transições da Playlist, conferindo estados intermediários, invariantes e resultados concretos.

Um cenário, várias transições

Não espere apenas pelo estado final

Considere uma Playlist com capacidade 2. O cenário inclui duas faixas, atinge o limite, remove uma delas e ocupa novamente a vaga.

Todas essas operações pertencem ao mesmo teste porque descrevem um único fluxo de comportamento. Depois de cada transição relevante, verifique tanto o conteúdo esperado quanto as invariantes:

  • a quantidade permanece entre zero e a capacidade;
  • não existem títulos repetidos;
  • a capacidade configurada continua igual a 2.

Fluxo de estados da Playlist

Cada quadro representa o estado observável logo depois de uma operação pública.

Diagrama com quatro estados de uma playlist de dois espaços: uma faixa, duas faixas, uma faixa após remoção e duas faixas após nova inclusão.

A ordem esperada é: [Intro] → [Intro, Tema] → [Tema] → [Tema, Final].

Teste o percurso completo

Combine o concreto com o geral

A lista esperada verifica quais faixas estão presentes e em qual ordem. As demais asserções verificam regras gerais da Playlist. Uma categoria não substitui a outra: uma lista pode ter quantidade válida e, ainda assim, conter títulos errados ou fora de ordem.

Teste com estados intermediários

Adicione este teste ao arquivo de testes da Playlist. Ele usa somente a interface pública.

python
from playlist import Playlist


def test_sequencia_ocupa_libera_e_reocupa_capacidade():
    playlist = Playlist(2)

    assert playlist.adicionar("Intro") == 1
    faixas = playlist.listar_faixas()
    assert faixas == ["Intro"]
    assert 0 <= len(faixas) <= playlist.capacidade
    assert len(faixas) == len(set(faixas))
    assert playlist.capacidade == 2

    assert playlist.adicionar("Tema") == 2
    faixas = playlist.listar_faixas()
    assert faixas == ["Intro", "Tema"]
    assert 0 <= len(faixas) <= playlist.capacidade
    assert len(faixas) == len(set(faixas))
    assert playlist.capacidade == 2

    assert playlist.remover("Intro") is None
    faixas = playlist.listar_faixas()
    assert faixas == ["Tema"]
    assert 0 <= len(faixas) <= playlist.capacidade
    assert len(faixas) == len(set(faixas))
    assert playlist.capacidade == 2

    assert playlist.adicionar("Final") == 2
    faixas = playlist.listar_faixas()
    assert faixas == ["Tema", "Final"]
    assert 0 <= len(faixas) <= playlist.capacidade
    assert len(faixas) == len(set(faixas))
    assert playlist.capacidade == 2

Por que observar o caminho?

Dica

O estado final não conta toda a história

Se o teste verificasse somente ["Tema", "Final"] no fim, uma transição intermediária incorreta poderia passar despercebida ou ser mascarada por uma operação posterior. As verificações intermediárias localizam a primeira transição que rompeu o contrato.

Exemplo

Quantidade válida não basta

Depois da segunda inclusão, len(faixas) == 2 seria verdadeiro tanto para ["Intro", "Tema"] quanto para ["Tema", "Intro"]. A invariante da quantidade é respeitada nos dois casos, mas somente a comparação com a lista esperada confirma conteúdo e ordem.

Associe cada verificação ao que ela confirma

Operações e garantias

Relacione cada momento ou expressão à garantia oferecida pelo teste.

Toque em um item e depois no par correspondente.

Passo 4 de 7

Confirmar que uma rejeição preserva o estado

Verifique tanto a exceção esperada quanto a preservação do estado público após uma operação inválida.

Uma rejeição tem duas expectativas

Exceção e estado preservado

Na Playlist, adicionar além da capacidade, adicionar um título repetido ou remover uma faixa ausente deve gerar ValueError sem alterar o estado.

Por isso, o teste precisa verificar duas partes distintas do contrato:

  1. a chamada inválida lança a exceção prevista;
  2. capacidade, conteúdo e ordem continuam iguais aos valores anteriores.

Use um estado inicial não vazio. Assim, uma perda, inclusão parcial ou reordenação fica visível.

Comparação antes e depois

O retrato do estado anterior serve como referência para avaliar o objeto depois da chamada rejeitada.

Diagrama mostrando uma playlist com duas faixas antes de uma operação inválida e a mesma playlist, com conteúdo e ordem idênticos, depois da rejeição.

A tentativa falha, mas o estado observável antes e depois permanece igual.

Atenção

Não presuma preservação automática

Uma exceção não garante, por si só, que nada foi modificado. A ausência de mutação precisa fazer parte do contrato da operação. Quando fizer parte, o teste deve verificá-la explicitamente.

Guarde um retrato independente

Observe apenas a interface pública

Antes da tentativa inválida, registre separadamente os valores observáveis. listar_faixas() devolve uma cópia, então a lista guardada funciona como um retrato do conteúdo e da ordem naquele momento. Não é necessário acessar _faixas nem guardar outra referência para a própria Playlist.

Duplicação rejeitada sem alteração

Acrescente este teste ao arquivo de testes da Playlist:

python
import pytest

from playlist import Playlist


def test_adicionar_faixa_duplicada_preserva_estado():
    playlist = Playlist(capacidade=3)
    playlist.adicionar("Horizonte")
    playlist.adicionar("Chuva")

    capacidade_antes = playlist.capacidade
    faixas_antes = playlist.listar_faixas()

    with pytest.raises(ValueError):
        playlist.adicionar("Horizonte")

    assert playlist.capacidade == capacidade_antes
    assert playlist.listar_faixas() == faixas_antes

Dica

Por que não guardar somente o objeto?

Fazer antes = playlist não cria um retrato: os dois nomes apontariam para o mesmo objeto mutável. Guardar a capacidade e a cópia retornada por listar_faixas() permite comparar valores anteriores independentes.

Cubra as outras rejeições do contrato

Repita o padrão nos demais cenários

Para lotação e remoção ausente, a estrutura continua sendo: preparar um estado observável, registrar o retrato, executar somente a chamada rejeitada dentro de pytest.raises e comparar o estado posterior.

Capacidade excedida e faixa ausente

Os dois testes também preservam conteúdo, ordem e capacidade:

python
def test_adicionar_alem_da_capacidade_preserva_estado():
    playlist = Playlist(capacidade=2)
    playlist.adicionar("Horizonte")
    playlist.adicionar("Chuva")

    capacidade_antes = playlist.capacidade
    faixas_antes = playlist.listar_faixas()

    with pytest.raises(ValueError):
        playlist.adicionar("Vento")

    assert playlist.capacidade == capacidade_antes
    assert playlist.listar_faixas() == faixas_antes


def test_remover_faixa_ausente_preserva_estado():
    playlist = Playlist(capacidade=3)
    playlist.adicionar("Horizonte")
    playlist.adicionar("Chuva")

    capacidade_antes = playlist.capacidade
    faixas_antes = playlist.listar_faixas()

    with pytest.raises(ValueError):
        playlist.remover("Vento")

    assert playlist.capacidade == capacidade_antes
    assert playlist.listar_faixas() == faixas_antes

Explique o que o teste precisa detectar

Além do ValueError

Uma playlist contém Horizonte e Chuva. Uma tentativa de adicionar Horizonte novamente gera ValueError. Quais valores você registraria antes da tentativa, o que verificaria depois e por que testar somente a exceção seria insuficiente?

Escreva pelo menos 80 caracteres (0/80).

Passo 5 de 7

Manter cada teste independente

Organize cada função de teste para criar e preparar o próprio objeto, permitindo executá-la sozinha ou em qualquer ordem.

Cada teste prepara o próprio cenário

Independência entre funções

Cada função test_* deve criar uma nova Playlist e construir, por métodos públicos, o estado de que precisa. Um objeto mutável global pode carregar alterações de um teste para outro, fazendo o resultado depender da ordem de execução.

A ordem das operações dentro de um teste pode ser necessária ao cenário. O problema aparece quando uma função depende de outra ter sido executada antes.

Cenários isolados

Compare uma suíte encadeada com testes que possuem preparações próprias.

Comparação entre testes ligados ao mesmo objeto mutável e testes separados, cada um com sua própria playlist.

À esquerda, testes compartilham um estado que muda ao longo da suíte. À direita, cada teste começa com um objeto novo e controla todo o seu cenário.

Transforme dependência em preparação local

Evite um objeto global compartilhado

Neste exemplo inadequado, o segundo teste só funciona se o primeiro deixar a faixa na playlist.

python
from playlist import Playlist


playlist = Playlist(2)


def test_adicionar_faixa():
    playlist.adicionar("Horizonte")
    assert playlist.listar_faixas() == ["Horizonte"]


def test_remover_faixa():
    # Depende da alteração feita por test_adicionar_faixa.
    playlist.remover("Horizonte")
    assert playlist.listar_faixas() == []

Dê a cada teste um cenário completo

Agora cada função cria sua instância. O teste de remoção prepara a faixa necessária antes de executar a ação principal.

python
from playlist import Playlist


def test_adicionar_faixa():
    playlist = Playlist(2)

    total = playlist.adicionar("Horizonte")

    assert total == 1
    assert playlist.listar_faixas() == ["Horizonte"]


def test_remover_faixa():
    playlist = Playlist(2)
    playlist.adicionar("Horizonte")

    retorno = playlist.remover("Horizonte")

    assert retorno is None
    assert playlist.listar_faixas() == []

Dica

Experimente seleções diferentes

No terminal, a partir da pasta do projeto, execute cada função sozinha e depois inverta a ordem dos identificadores:

python -m pytest tests/test_playlist.py::test_adicionar_faixa

python -m pytest tests/test_playlist.py::test_remover_faixa

python -m pytest tests/test_playlist.py::test_remover_faixa tests/test_playlist.py::test_adicionar_faixa

Essas execuções podem revelar dependências, mas passar em algumas ordens não prova independência. A garantia vem de cada teste preparar integralmente o próprio cenário.

Independência ou sequência legítima?

Sequência dentro de um teste

Em uma única função de teste, adicionar uma faixa e depois removê-la é uma dependência indevida, mesmo quando a playlist foi criada dentro da própria função.

O que a ordem demonstra

Se a suíte passa quando os testes são executados em uma ordem escolhida, isso prova que eles são independentes.

Passo 6 de 7

Detectar vazamento entre instâncias

Construa um teste com duas playlists para verificar, pela interface pública, que alterações em uma instância não vazam para a outra.

Duas instâncias, dois estados

Independência dentro do mesmo teste

Criar um objeto novo em cada função de teste evita dependências entre testes. Porém, isso não garante que duas instâncias da mesma classe mantenham estados separados.

Para verificar essa outra propriedade, crie as duas playlists antes de qualquer alteração. Depois, modifique uma por vez e observe ambas por listar_faixas(). Se uma faixa adicionada a uma playlist aparecer na outra, existe vazamento de estado mutável.

O comportamento esperado

Cada operação deve afetar somente a instância que a recebeu. As duas playlists podem existir ao mesmo tempo sem compartilhar suas coleções de faixas.

Diagrama em três momentos mostrando duas playlists separadas: inicialmente vazias, depois uma faixa somente na primeira e, por fim, faixas diferentes em cada uma.

A independência é observada pelo conteúdo público de cada playlist após cada alteração.

Teste o vazamento nos dois sentidos

Altere e confira as duas

Adicione este teste ao arquivo de testes já usado no projeto. Ele verifica primeiro se a alteração da primeira não alcança a segunda; depois faz a operação inversa. As expectativas usam somente a interface pública.

Cenário com duas playlists

Execute o teste com python -m pytest no seu projeto local.

python
from playlist import Playlist


def test_playlists_mantem_faixas_independentes():
    primeira = Playlist(capacidade=2)
    segunda = Playlist(capacidade=2)

    primeira.adicionar("Horizonte")

    assert primeira.listar_faixas() == ["Horizonte"]
    assert segunda.listar_faixas() == []

    segunda.adicionar("Travessia")

    assert primeira.listar_faixas() == ["Horizonte"]
    assert segunda.listar_faixas() == ["Travessia"]

Dica

Como interpretar uma falha

Se a segunda playlist já contiver "Horizonte" após a primeira inclusão, ou se a primeira passar a conter "Travessia" após a segunda, uma operação afetou a instância errada. O teste não precisa comparar atributos internos nem a identidade de listas: o comportamento público já revela o compartilhamento indevido.

Explique o diagnóstico

Quais verificações revelam o vazamento?

Considere duas playlists inicialmente vazias. Escreva as verificações que você faria após adicionar "Norte" somente à primeira e, depois, "Sul" somente à segunda. Explique qual divergência indicaria que as instâncias compartilham estado indevidamente.

Escreva pelo menos 80 caracteres (0/80).

Passo 7 de 7

Aplicação final: reunir e executar a suíte

Consolide a implementação e os testes da Playlist, execute a suíte localmente e revise como cada cenário verifica o contrato público.

Consolide a implementação local

Estrutura final do projeto

No seu computador, reúna a implementação e os testes nesta estrutura:

playlist_projeto/
├── playlist.py
└── tests/
    └── test_playlist.py

Os testes observarão somente capacidade, os retornos dos métodos e listar_faixas(). O atributo interno _faixas não será consultado diretamente.

playlist.py

Crie ou substitua o arquivo playlist.py por esta implementação completa:

python
class Playlist:
    def __init__(self, capacidade):
        if capacidade <= 0:
            raise ValueError("A capacidade deve ser positiva")

        self.capacidade = capacidade
        self._faixas = []

    def adicionar(self, titulo):
        if titulo in self._faixas:
            raise ValueError("A faixa já está na playlist")
        if len(self._faixas) >= self.capacidade:
            raise ValueError("A playlist está cheia")

        self._faixas.append(titulo)
        return len(self._faixas)

    def remover(self, titulo):
        if titulo not in self._faixas:
            raise ValueError("A faixa não está na playlist")

        self._faixas.remove(titulo)

    def listar_faixas(self):
        return self._faixas.copy()

Reúna a suíte completa

Um cenário independente por função

Salve o código abaixo em tests/test_playlist.py. Cada função cria as próprias instâncias e prepara seu cenário por operações públicas. A ordem interna de um cenário importa, mas nenhuma função depende de outra função de teste.

tests/test_playlist.py

A suíte cobre inicialização, retorno e efeito, sequência com invariantes, rejeições sem mutação e independência entre instâncias.

python
import pytest

from playlist import Playlist


def test_inicializa_vazia_com_capacidade_configurada():
    playlist = Playlist(3)

    assert playlist.capacidade == 3
    assert playlist.listar_faixas() == []


def test_adicionar_devolve_total_e_preserva_ordem():
    playlist = Playlist(3)

    total = playlist.adicionar("Aurora")

    assert total == 1
    assert playlist.listar_faixas() == ["Aurora"]


def test_remover_devolve_none_e_altera_conteudo():
    playlist = Playlist(3)
    playlist.adicionar("Aurora")
    playlist.adicionar("Horizonte")

    retorno = playlist.remover("Aurora")

    assert retorno is None
    assert playlist.listar_faixas() == ["Horizonte"]


def test_sequencia_preserva_ordem_capacidade_e_unicidade():
    playlist = Playlist(2)

    assert playlist.adicionar("Aurora") == 1
    assert playlist.listar_faixas() == ["Aurora"]

    assert playlist.adicionar("Horizonte") == 2
    assert playlist.listar_faixas() == ["Aurora", "Horizonte"]
    assert len(playlist.listar_faixas()) <= playlist.capacidade
    assert len(playlist.listar_faixas()) == len(set(playlist.listar_faixas()))

    assert playlist.remover("Aurora") is None
    assert playlist.listar_faixas() == ["Horizonte"]

    assert playlist.adicionar("Maré") == 2
    assert playlist.listar_faixas() == ["Horizonte", "Maré"]
    assert playlist.capacidade == 2
    assert len(playlist.listar_faixas()) <= playlist.capacidade
    assert len(playlist.listar_faixas()) == len(set(playlist.listar_faixas()))


def test_duplicata_e_rejeitada_sem_alterar_estado():
    playlist = Playlist(3)
    playlist.adicionar("Aurora")
    antes_capacidade = playlist.capacidade
    antes_faixas = playlist.listar_faixas()

    with pytest.raises(ValueError):
        playlist.adicionar("Aurora")

    assert playlist.capacidade == antes_capacidade
    assert playlist.listar_faixas() == antes_faixas


def test_excesso_de_capacidade_e_rejeitado_sem_alterar_estado():
    playlist = Playlist(2)
    playlist.adicionar("Aurora")
    playlist.adicionar("Horizonte")
    antes_capacidade = playlist.capacidade
    antes_faixas = playlist.listar_faixas()

    with pytest.raises(ValueError):
        playlist.adicionar("Maré")

    assert playlist.capacidade == antes_capacidade
    assert playlist.listar_faixas() == antes_faixas


def test_remocao_ausente_e_rejeitada_sem_alterar_estado():
    playlist = Playlist(3)
    playlist.adicionar("Aurora")
    playlist.adicionar("Horizonte")
    antes_capacidade = playlist.capacidade
    antes_faixas = playlist.listar_faixas()

    with pytest.raises(ValueError):
        playlist.remover("Maré")

    assert playlist.capacidade == antes_capacidade
    assert playlist.listar_faixas() == antes_faixas


def test_instancias_mantem_estados_independentes():
    primeira = Playlist(3)
    segunda = Playlist(2)

    primeira.adicionar("Aurora")
    assert primeira.listar_faixas() == ["Aurora"]
    assert segunda.listar_faixas() == []

    segunda.adicionar("Maré")
    assert segunda.listar_faixas() == ["Maré"]
    assert primeira.listar_faixas() == ["Aurora"]

Execute e registre o resultado

Comandos de execução

Abra o terminal na pasta playlist_projeto. Execute primeiro a suíte completa e depois dois cenários isolados:

shell
python -m pytest
python -m pytest tests/test_playlist.py::test_sequencia_preserva_ordem_capacidade_e_unicidade
python -m pytest tests/test_playlist.py::test_instancias_mantem_estados_independentes

Dica

Compare resultado e expectativa

Na suíte completa, o resultado esperado é a aprovação dos oito testes. Nas seleções seguintes, somente o teste indicado deve ser executado e aprovado. Se houver falha, leia a comparação exibida pelo pytest e localize qual estado observado contrariou o contrato.

Relato da aplicação final

Escolha um dos testes da suíte e cole-o na resposta. Registre também o resultado observado ao executá-lo. Depois, explique como a suíte detectaria: (1) uma alteração parcial causada por uma operação rejeitada e (2) um vazamento de faixas entre instâncias.

Escreva pelo menos 120 caracteres (0/120).

Revisão final

Resumo

Critérios de uma suíte de estado confiável

Use esta lista para revisar sua solução antes de encerrar:

  • Cada teste prepara uma nova instância ou as instâncias necessárias ao próprio cenário.
  • As expectativas vêm do contrato e verificam capacidade, retornos, conteúdo e ordem pela interface pública.
  • As sequências conferem estados intermediários relevantes, e não somente o resultado final.
  • Após uma operação rejeitada, o teste compara um retrato anterior independente com o estado posterior.
  • Duas instâncias são alteradas separadamente para revelar eventual compartilhamento de estado mutável.
  • A suíte completa e testes selecionados podem ser executados sem depender da ordem das funções.

Tutorial concluído

Parabéns! Você concluiu: Testar mudanças de estado em objetos

Agora você sabe testar estados iniciais, efeitos e retornos, sequências com invariantes, rejeições sem mutação e independência entre objetos.

100 XP

Baixe o Aplicativo agora para ter acesso a + de 5000 cursos gratuitos, exercícios, certificado e muito conteúdo sem pagar nada!

  • Cursos online 100% gratuitos do início ao fim

    Milhares de cursos online em vídeo, ebooks e áudiobooks.

  • Mais de 60 mil exercícios gratuitos

    Para testar seus conhecimentos no decorrer dos cursos online

  • Certificado Digital gratuito válido em todo o Brasil

    Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail

Aplicativo Cursa na tela de ebook, na tela de curso em vídeo e na tela de exercícios do curso, mais o certificado de conclusão de curso