Saltar para o conteúdo principal

Como testar migrações de estados guardados no Unity com estados históricos dos jogadores

Gráfico original de migração Persistium mostrando histórico salvar pontos de controle, etapas de transformação e resultados inspecionados.

Um processo prático, local-primeiro para ensaiar Unity salvar migrações contra formas reais de estado histórico antes de uma atualização atinge os jogadores.

Teste de conta limpa falha o defeito importante

Uma nova compilação Unity pode parecer perfeita quando começa com um perfil vazio. Uma conta nova não tem campos aposentados, missões semi- concluídas, referências de catálogos em cache, pilhas de inventário antigas, compras interrompidas ou valores que eram válidos sob um conjunto de regras anterior. Prova, portanto, apenas que o novo formato pode criar um novo estado. Diz muito pouco sobre se o jogo pode transportar com segurança um jogador do mundo de ontem para o de hoje.

O trabalho de migração deve começar a partir de uma pergunta mais forte: pode este estado histórico exato entrar na compilação atual, preservar o significado que ainda importa, e tornar-se um estado atual válido ou falhar de uma forma que seja útil para diagnosticar? Essa é uma questão de produto tanto quanto uma pergunta de serializador. Um campo pode se deserializar com sucesso enquanto ainda viola uma regra de progressão, apontando para o conteúdo em falta, ou alterando um direito de compra. O carregamento de bytes não é suficiente.

Este artigo descreve um processo de ensaio local. Não é deliberadamente uma promessa de que uma ferramenta possa compreender cada configuração de serialização do Unity, infra- estrutura ou integração de plataformas. Os modelos de dados do Unity variam e um jogo pode adicionar a sua própria criptografia, sincronização na nuvem, formato binário ou adaptadores personalizados. A parte durável é a disciplina de teste: preservar formas históricas, copiá- las antes da mutação, tornar o caminho de migração explícito e verificar invariantes do jogo após cada limite.

Construir um corpus de estado histórico

Tratar as gravações antigas como um corpus versionado, não como arquivos soltos passados em bate- papo. Cada dispositivo precisa de um identificador estável, a versão que o produziu, um rótulo de cenário curto, uma declaração de formato e uma classificação de privacidade. O rótulo de cenário importa porque um pacote de migração deve incluir estados significativos, não apenas JSON aleatório. Exemplos incluem um novo jogador, um jogador num limite de capítulo, um inventário no seu limite de pilha, um tutorial interrompido, uma recompensa de evento antiga, um usuário com conteúdo cosmético opcional, e um perfil que contém intencionalmente uma combinação de legado inválido.

Use cópias de trabalho imutáveis. O corredor de teste nunca deverá sobrescrever a configuração do código- fonte enquanto tenta uma migração. Copie a configuração para uma pasta de trabalho temporária, grave o seu código de verificação ou hash de conteúdo, execute o adaptador e mantenha a saída resultante separadamente. Isto torna um resultado surpreendente em evidência que alguém poderá repetir. Também impede que um teste melhore silenciosamente a sua própria entrada até que o problema original desapareça.

Mantenha os dados do jogador de produção fora de um diretório de desenvolvimento geral. Um corpus histórico pode ser sintético, consentido, cuidadosamente redigido ou gerado a partir de cenários determinísticos. A chave é que ele mantém os casos de borda estrutural e semântica que importam. A privacidade não é um pensamento posterior: remover identificadores diretos, identificadores do dispositivo, informações de contato, fichas de pagamento, recibos brutos e segredos antes que um dispositivo fique amplamente acessível.

Desenhar o gráfico de migração antes de escrever um conversor

Os números de versões não são um plano de migração. Um jogo pode mover- se de 1 para 2, 2 para 3, e 3 para 4, e depois encontrar um jogador que tenha saltado directamente do 1 para 4. Ele pode enviar um hotfix que muda apenas um registo de inventário aninhado, ou retirar um campo que o conteúdo antigo ainda espera. Um gráfico torna estas escolhas visíveis: cada nó é um esquema de salvamento ou versão semântica; cada borda é uma transformação explícita ou uma rejeição intencional; cada borda tem pré- condições e condições pós.

Para cada versão inicial suportada, decida se as cadeias de jogo adjacentes transformam- se, usa uma transformação directa ou necessita de uma actualização do lado do serviço antes das cargas do cliente. Não deixe versões ignoradas para o que quer que aconteça quando os campos estiverem ausentes. Uma cadeia pode ser mais segura quando cada passo é pequeno e testado. Uma transformação directa pode ser mais segura quando os formatos intermédios nunca foram destinados a ser observáveis. Ambas as abordagens precisam de uma propriedade clara e de dispositivos repetitivos.

Gráfico de migração original com cinco nós de estado conectados por caminhos de conversão suportados explícitos.
Um gráfico de migração faz com que a versão salte, os caminhos suportados e os pontos de inspeção visíveis antes que o código os esconda.

O gráfico deverá incluir mais do que o tipo serializado. Gravar as dependências do catálogo de conteúdo, as versões de configuração, as opções de funcionalidades que afectam a interpretação e a versão actual da aplicação. Uma gravação poderá ser sintática actual mas inválida porque um item referenciado já não existe ou uma regra alterada de significado. Estes são os limites de compatibilidade, não os casos de borda irritantes.

Torna observáveis viagens redondas de poupança-carga

Uma vez que um dispositivo entra no caminho de migração, execute uma viagem completa: carregue a entrada histórica, migrá- la, construa o modelo no jogo, salve- a usando o gravador atual, então carregue de novo essa saída atual. A primeira carga verifica se um registro histórico pode entrar no sistema. A segunda carga verifica se o gravador atual criou um resultado estável e autoconsistente. Compare o estado após a primeira e a segunda cargas de acordo com as regras semânticas, não apenas a igualdade de byte bruta.

A igualdade bruta é frequentemente a asserção errada. Podem ser adicionados novos padrões, a ordem de campo poderá mudar, as datas poderão ser atualizadas, e as coleções poderão receber uma ordenação estável. Defina um normalizador para valores que não sejam intencionalmente semânticos, e depois compare o resto. O próprio normalizador merece revisão; não deverá tornar- se um cobertor ignorar a lista que esconde a perda real. Uma boa comparação relata quais campos foram adicionados, removidos, transformados ou ignorados intencionalmente.

O resultado do teste deve nomear o primeiro estado inválido. Se uma cadeia de migração tiver três transições, não relate apenas que a carga final falhou. Capture o estado após cada ponto de verificação, valide-o e pare no limite mais precoce que viola um invariante. Essa localização converte um relatório vago “antigo salvo quebrado” em uma borda de migração específica, fixação e diferença de estado.

Invariantes específicas do jogo expresso

Um arnês de migração geral não pode saber o que significa progressão num determinado jogo. O jogo deve fornecer as suas próprias invariantes. Escreva- as numa linguagem que se conecte aos resultados dos jogadores: um nível está dentro do intervalo actual; uma quantidade de inventário não é negativa e limitada; cada item possuído tem uma definição conhecida ou um recuo legendário explícito; um tutorial completo permanece concluído; a moeda é finita; uma referência de busca pertence a um catálogo actual; um direito é único; uma semente mundial e um estado mundial gerado concordam.

Separar invariantes em camadas. Os invariantes estruturais respondem se existem campos e tipos necessários. Os invariantes referenciais respondem se os IDs apontam para definições conhecidas. Os invariantes de domínio respondem se os valores obedecem às regras do jogo. Os invariantes de segurança respondem se os dados são finitos, limitados e seguros para serem exibidos. Quando um dispositivo falha, a camada diz à equipe se precisa de uma mudança de serializador, adaptador de catálogos, decisão de regras ou política de recuperação.

Use mutações determinísticas para dificultar o trabalho do corpus. Comece a partir de um dispositivo conhecido e remova deliberadamente um campo, duplique um ID de inventário, substitua uma referência de catálogo, pule uma versão, reordene uma lista de ações ou altere uma quantidade limite. Cada mutação deve ter um resultado esperado: migrar com um padrão documentado, rejeitar com uma categoria de diagnóstico, quarentena para recuperação ou exigir um reparo de dados aprovado pelo proprietário. “Não falha” não é um resultado esperado suficiente.

Reduzir uma falha de gravação sem perder o erro

Os estados grandes do jogador são difíceis de entender. Se uma instalação contém centenas de campos e uma migração falha, um desenvolvedor pode passar horas lendo dados não relacionados. A minimização de contraexemplo reduz a instalação enquanto preserva o invariante violado. Remova um grupo de campo, tente novamente a mesma migração determinística e mantenha a remoção apenas quando a falha ainda ocorrer. Em seguida, as coleções de partição, encolhem valores para limites e reduzam o histórico de ação se o comportamento de repetição importar.

Visual de contraexemplo-redução original mostrando um estado grande estreitado através de partições repetiveis em uma luminária de regressão mínima.
O processo de redução preserva a falha invariante enquanto remove o estado não relacionado.

A redução requer reprodutibilidade. Bloqueie o hash de fixação, a versão de migração, a versão do adaptador, a semente determinística, o instantâneo de configuração relevante e o invariante esperado. Se o ambiente mudar durante cada repetição, um redutor poderá produzir um estado menor para um problema diferente. Mantenha um replay manifesto com essas entradas e o resultado observado. O resultado não é apenas um arquivo menor; é um caso de regressão que alguém pode executar.

As fixações de regressão geradas deverão indicar o mesmo caminho de revisão que as escritas à mão. Dê a cada fixação um nome estável, uma breve explicação da falha, a invariante que preserva e uma classificação esperada. Uma fixação de regressão é valiosa porque faz uma rotina de contornos anteriormente surpreendente. Não deve tornar- se uma fonte oculta de dados do jogador de produção ou um catch-all para falhas inexplicáveis.

Põe o ensaio em CI sem fazer barulho

Execute um subconjunto representativo rápido em todas as mudanças relevantes: bordas de migração atuais, estados históricos de alto risco e um pequeno conjunto de mutações determinísticas. Execute o corpus completo em um trabalho agendado, release candidate, ou alteração que toque em serialização, progressão, interpretação do catálogo, sincronização na nuvem ou código de formatação de salvamento.

O resultado do CI deve ser conciso, mas inspecionável. Informe o ID do dispositivo, as versões inicial e alvo, a margem de migração, a camada invariante, o primeiro ponto de verificação inválido e um caminho para o artefato ou diff editados. Evite imprimir o conteúdo completo do perfil em logs. Quando uma falha for nova, mantenha a informação mínima necessária para reproduzi- la localmente; quando for esperado, registre essa classificação para que uma versão não normalize silenciosamente um risco conhecido.

A decisão de liberação deve indicar o que as evidências significam. Passar testes de migração suportam uma reivindicação limitada: o corpus testado e os caminhos declarados comportaram-se como esperado nesse ambiente. Eles não provam que cada jogador salve, dispositivo ou serviço externo irá se comportar de forma idêntica. As equipes ainda precisam de planos de retrocesso, manuseio de suporte ao cliente e monitoramento que respeitam a privacidade.

Lista de verificação prática

  • Mantenha um corpus histórico versionado com rótulos de cenário e classificações de privacidade.
  • Copiar entradas antes da migração e preservar hashes para artefatos de origem e saída.
  • Desenhar bordas de migração suportadas, incluindo políticas de versão ignorada e rejeições explícitas.
  • Validar invariantes estruturais, referenciais, de domínio e de segurança após cada checkpoint.
  • Saída migrada de ida e volta através do escritor e leitor atual.
  • Use mutações determinísticas para testar campos em falta, registros duplicados, conteúdo aposentado e valores limite.
  • Reduza falhas reprodutíveis em acessórios de regressão revisados com manifestos de repetição.
  • Execute subconjuntos focados em risco continuamente e o corpus completo antes de alterações sensíveis à liberação.
  • Manter logs e artefatos editados; não transformar o CI em um arquivo de dados do jogador.

Perguntas mais frequentes

Precisamos de um jogador real para testar a migração?

Não. As formas históricas reais são úteis, mas o corpus pode ser sintético ou cuidadosamente redigido. O que importa é preservar as combinações de estado difíceis: conteúdo aposentado, longa progressão, transições interrompidas, dados opcionais e valores em limites significativos. Se os dados de produção são usados para descobrir uma caixa de borda, derivar um dispositivo seguro que mantém apenas a propriedade sob teste.

Deve cada versão antiga permanecer suportada para sempre?

Não necessariamente. O suporte é uma decisão de produto e operacional. O importante é tornar o limite explícito: quais versões migram, que requerem uma atualização anterior, que são rejeitadas, e como o jogador é informado. Um caminho acidental não documentado é a opção menos segura porque não é suportado de forma confiável nem claramente recusado.

Pode um teste serializador substituir testes de jogabilidade?

No. Testes Serializer estabelecem que os dados podem ser lidos e escritos de acordo com formas declaradas. Testes de Gameplay e domínio estabelecem que o estado resultante faz sentido no conjunto de regras atuais. Um ensaio de migração robusta precisa tanto, quanto uma revisão de conteúdo e referências de configuração que influenciam o significado de valores salvos.

Qual é o papel do Persistium aqui?

Persistium é uma família de produtos de confiabilidade local-primeiro desenvolvimento. A orientação nesta nota é conselho de engenharia prática, não uma alegação de que um produto público Persistium está disponível ou que ele diagnosticou dados reais do cliente. Sua direção de continuidade explora o tipo de migração explícita, repetição, comparação e redução de fluxo de trabalho descrito acima.

Sistema relacionadoExplore Persistium