Para quem está interessado no tópico de automação no iOS, tenho duas novidades - boas e más. Bom: no aplicativo iOS para serviços pagos, apenas um ponto de integração é usado - compras no aplicativo (compras
integradas no aplicativo ). Ruim: a Apple não fornece nenhuma ferramenta para automatizar compras de teste.
Neste artigo, sugiro que você e eu procuremos um método de automação universal além do bem e do mal da Apple. O artigo será útil para quem integra serviços de terceiros que são uma caixa preta em seus aplicativos: publicidade, streaming, gerenciamento de localização etc. Geralmente, essas integrações são muito difíceis de testar, pois não há como configurar de forma flexível um serviço de terceiros para testar o aplicativo.
Meu nome é Victor Koronevich, sou engenheiro sênior de automação de testes no Badoo. Envolvido em automação móvel por mais de dez anos. Juntamente com meu colega Vladimir Solodov, fizemos este relatório na conferência Heisenbug. Ele também me ajudou a preparar este texto.No
artigo anterior
, descrevemos quais métodos o Badoo usa para testar a integração com provedores de pagamento, dos quais temos mais de 70. Neste artigo, falaremos mais sobre como conseguimos obter automação estável e barata de testar serviços pagos em um aplicativo iOS.
Vamos começar com uma descrição geral de nossa pesquisa:
- Definição do problema
- Declaração do problema
- Solução No. 1. Apple Sandbox
- Decisão número 2. Método de simulação de função e uso de um objeto falso
- Avaliação da decisão: principais riscos
- Resultado
- Conclusão
Definição do problema
A automação precisa ser feita quando surgir uma necessidade natural. Quando esse momento veio conosco?
Existem muitos recursos gratuitos no aplicativo Badoo, mas os pagos oferecem ao usuário mais opções. Eles os obtêm de duas maneiras: para empréstimos - a moeda interna do Badoo - ou adquirindo uma assinatura premium. Para um determinado número de créditos, você pode elevar seu perfil nos resultados da pesquisa para o primeiro lugar, fazer um presente para outro usuário e muito mais. A assinatura premium é válida por um certo período de tempo e oferece várias opções ao mesmo tempo: ativar o modo invisibilidade, ver pessoas que demonstraram simpatia por você, cancelar o resultado do seu voto e outras.
Esses recursos apareceram no Badoo gradualmente. Há alguns anos, testamos serviços pagos em aplicativos iOS apenas manualmente. Porém, à medida que os recursos e as novas telas aparecem, o teste manual leva cada vez mais tempo. Os requisitos para alterações no aplicativo vieram de lados diferentes: dos desenvolvedores do lado do cliente, dos desenvolvedores do lado do servidor e até do próprio fornecedor da Apple. Para um testador, uma iteração de teste começou a levar cerca de oito horas. Tornou-se impossível obter um feedback rápido para um desenvolvedor em sua filial em 30 minutos, o que poderia afetar negativamente a competitividade do produto.
Queríamos obter os resultados do teste o mais rápido possível. E eles encontraram um problema: como organizar o teste de regressão de serviços pagos em nossos aplicativos iOS de forma barata, a fim de obter resultados rápidos e estáveis?
Declaração do problema
Portanto, levando em consideração as especificidades do nosso processo de entrega do produto final e o tamanho da equipe, queremos:
- Teste todas as compras no aplicativo cliente (pagamentos únicos e assinaturas);
- repita as iterações do teste de 10 a 20 vezes por dia;
- Obtenha resultados de teste ~ 150 scripts de teste em menos de meia hora;
- se livrar do barulho;
- poder executar testes em uma ramificação específica do código do desenvolvedor, independentemente dos resultados de outras execuções.
Agora que formulamos a tarefa, é hora de começar a jornada para o maravilhoso mundo dos engenheiros e suas soluções.
Solução No. 1. Apple Sandbox
Primeiro, começamos a procurar informações sobre a organização de testes automáticos de serviços pagos na documentação da Apple. E eles não encontraram nada. O suporte à automação parece muito escasso. Se algo aparecer, será difícil configurar a automação com as ferramentas propostas (lembre-se de pelo menos
UIAutomation , bem como o momento em que o primeiro utilitário
xcrun simctl do iOS Simulator apareceu) e você precisará procurar soluções de engenharia, inclusive no segmento de código aberto.
Na documentação da Apple para testar serviços pagos, você pode encontrar apenas o
Apple Sandbox . Não ficou claro como vincular essa sandbox à automação, mas decidimos pesquisar seriamente essa solução. O fato de a sandbox do Android ser estável nos deu confiança e, nessa época, já tínhamos escrito testes com sucesso no Android. Talvez a caixa de areia da Apple seja tão boa?
Mas quando implementamos autotestes usando essa sandbox, bebemos por completo. Vamos analisar rapidamente os principais problemas.
1. O pool de usuários de teste
A principal limitação para automação foram os recursos do conteúdo no pool de usuários de teste, o que deve garantir a independência do lançamento dos autotestes.
Para executar apenas uma compra automática de uma assinatura, precisamos:
- leve um novo usuário para autorização na sandbox;
- alterar no simulador o atual ID Apple vinculado;
- Faça login no Badoo com o Badoo
- acesse a tela de compra da assinatura e selecione um produto;
- Confirme a compra e faça login via Apple ID;
- verifique se a compra foi bem-sucedida;
- envie usuário do Badoo para limpeza;
- limpe o usuário da sandbox das assinaturas.
Se você tentar usar imediatamente o mesmo usuário no próximo teste, será impossível comprar uma segunda assinatura. Você precisa esperar até a primeira assinatura "ficar ruim" ou cancelar a inscrição nas configurações. Como dissemos no primeiro
artigo , o sandbox tem um período de validade de assinatura específico. Se você comprar uma assinatura "por um mês", precisará aguardar cinco minutos para fechá-la automaticamente. O processo de cancelamento de inscrição também não é rápido.
Assim, para uma nova execução do mesmo teste, precisaremos esperar até que a assinatura termine ou levar outro usuário "limpo". Se queremos executar dois testes simultaneamente, independentemente um do outro, precisamos ter pelo menos dois usuários de sandbox no pool. Portanto, para executar 100 testes automáticos em paralelo em 100 threads, precisamos de 100 usuários diferentes.
E agora vamos imaginar que estamos executando um autoteste em dois agentes, cada um dos quais pode executá-los em 100 threads. Nesse caso, precisamos de pelo menos 200 usuários!
2. Notificações "ruins"
Bem, o que diabos não está brincando! Organizamos um pool de usuários e começamos a observar como os testes são executados. Eles caíram ao longo da estrada, mas a maioria - por novas razões desconhecidas para nós. Começamos a entender e percebemos que, ao autorizar, confirmar uma compra e trabalhar como usuário na sandbox, a App Store envia alertas: por exemplo, solicita um novo nome de usuário e senha, confirma a autorização clicando no botão "OK", fornece informações sobre um erro interno com o botão "OK" . Às vezes eles aparecem, às vezes não. E se eles aparecerem, sempre em uma ordem diferente.

Como é possível que um erro suspeito seja simplesmente ignorado em um autoteste? E se ocorrer um erro real, o que devo fazer? Essa área se tornou automaticamente uma “zona cega” para nós, e tivemos que escrever manipuladores especiais para todos os alertas possíveis que pudessem chegar da App Store.
Tudo isso tornou os testes mais lentos:
- alertas podem chegar a diferentes etapas do cenário de teste, destruindo a idéia principal do teste - cenário de teste previsível; tivemos que adicionar um manipulador de erros que esperava que uma possível série de alertas ignorados conhecidos aparecesse;
- às vezes, novas variações de alertas chegavam ou outros erros ocorriam; portanto, tínhamos que reiniciar os testes interrompidos; isso aumentou o tempo de execução de todos os testes.
3. Houve um teste?
Portanto, os usuários no pool são bloqueados e limpos por n minutos. Executamos testes em 120 threads e já existem muitos usuários no pool, mas isso não é suficiente. Criamos nosso sistema de gerenciamento de usuários, manipulamos alertas e, depois, a TI aconteceu. A sandbox ficou indisponível por alguns dias para qualquer usuário de teste.
Ninguém esperava isso. E essa foi a última gota no cálice de nossa paciência, que finalmente matou o amor pela caixa de areia da Apple e nos fez embarcar no caminho para o outro lado do bem e do mal. Percebemos que não precisávamos dessa automação e que não queríamos mais sofrer com essa decisão perigosa.
Decisão número 2. Método de simulação de função e uso de um objeto falso
Então, tivemos problemas com a automação na caixa de areia da Apple. Mas não pense que no mundo móvel tudo está completamente ruim. No Android, a sandbox é muito mais estável - você pode executar autotestes lá.
Vamos tentar encontrar outra solução para iOS. Mas como olhar? Onde procurar? Vejamos a história dos testes e desenvolvimento de software: o que aconteceu com o mundo louco da Apple? O que dizem as pessoas que escreveram vários livros e conquistaram autoridade no mundo da automação e desenvolvimento de software?
Lembrei-me imediatamente do trabalho “xUnit Test Patterns: Refactoring Test Code”, escrito por Gerard Mesaroche (
revisão de Martin Fowler), - na minha opinião, um dos melhores livros para qualquer testador que conheça pelo menos uma linguagem de programação de alto nível e queira fazer automação . Alguns capítulos deste livro dedicados a testar o SUT isoladamente de outros componentes do aplicativo, que são nossa “caixa preta”, podem nos ajudar.
1. Introdução ao mocha e fake
Deve-se notar que, no mundo dos testes automáticos, não existe um limite geralmente aceito entre os conceitos de Duplas de teste, Stub de teste, Spy de teste, Objeto mock, Objeto falso e Objeto fictício. Você deve sempre considerar a terminologia do autor. Precisamos apenas de dois conceitos do grande mundo do Test Doubles: uma função simulada e um objeto falso. O que é isso E por que precisamos disso? Nós damos uma breve definição desses conceitos para que não tenhamos discordâncias.
Suponha que tenhamos um aplicativo e um componente integrados, o que é para nós uma "caixa preta". Dentro do aplicativo, podemos chamar funções acessando este componente e obtendo os resultados dessas funções. Dependendo do resultado, nosso aplicativo reage de uma maneira específica. Às vezes, o resultado da execução da função pode ser uma entidade inteira com vários campos que refletem os dados reais do usuário.
Substituição de uma função por qualquer outra que retorne o resultado desejado, vamos chamar de simulação da função, ou simplesmente simulação. Essas funções podem ter a mesma assinatura, mas são duas funções diferentes.
E a substituição da entidade obtida como resultado da função por uma entidade falsa (contendo os dados necessários nos campos e, às vezes, até dados corrompidos) será chamada de implementação de um objeto falso. Você pode ler mais sobre isso no livro que mencionei acima ou em qualquer outro compêndio de testes e desenvolvimento de software.
Para finalizar, vamos enfatizar alguns recursos do uso de funções simuladas e objetos falsos:
- Para molhar as funções, você precisa acessar o código-fonte e saber como o aplicativo funciona com o componente de dentro para o desenvolvedor.
- Para implementar um objeto falso, você precisa conhecer a estrutura do objeto real.
- O uso da função de simulação permite uma configuração flexível do aplicativo com o componente.
- O uso de um objeto falso permite dotar uma entidade de quaisquer propriedades.
O método moki e objeto falso é ideal para isolar a operação de um componente dentro de um aplicativo. Vamos ver como podemos aplicar esse método para resolver nosso problema, onde a App Store será o componente. Devido às peculiaridades do uso desse método, precisamos primeiro estudar a natureza do trabalho de nossa aplicação com o componente e, depois, a implementação técnica para criar mokeys específicos e objetos falsos.
2. Como acontece uma compra real
Antes de começarmos a descrever a interação de todas as partes do sistema, vamos destacar os principais atores:
- usuário do aplicativo - qualquer ator que executa ações com o aplicativo, pode ser uma pessoa ou um script que executa as instruções necessárias;
- aplicativo (no nosso caso, usamos o aplicativo iOS do Badoo instalado no simulador do iOS);
- servidor - um ator que processa solicitações do aplicativo e envia respostas ou notificações assíncronas sem uma solicitação do cliente (nesse caso, queremos dizer um servidor Badoo abstrato para simplificar a estrutura);
- A App Store é um ator que é uma "caixa preta" para nós: não sabemos como ela é organizada, mas conhecemos sua interface pública para processar compras dentro do aplicativo ( estrutura StoreKit ) e também sabemos como verificar dados em um servidor Apple.
Vamos ver como a compra ocorre. Todo o processo pode ser visto no diagrama:
Figura 1. Esquema de pagamento na App StoreDescreveremos passo a passo as principais ações dos atores.
1. O ponto de partida é o estado de todos os atores antes de abrir a tela com uma lista de produtos.
O que é essa tela e como chegamos nela?
Suponha que um usuário encontre uma pessoa interessante, abra seu perfil, escreva uma mensagem e deseje enviar um presente. Enviar um presente é um serviço pago. O usuário pode rolar o perfil até a seção para enviar presentes ou selecionar imediatamente um presente no chat.
Se o usuário selecionar um presente e não tiver dinheiro na conta, ele verá uma lista de diferentes pacotes de empréstimos (Assistente de pagamentos) para compra. O ponto de partida no nosso exemplo é uma lista de presentes. No diagrama, podemos considerar esse ponto em qualquer tela antes de mostrar a lista de produtos para a compra de empréstimos ou assinaturas.
2. Abrindo uma lista de produtos.
Estamos no ponto de partida, por exemplo, na lista de presentes. O usuário seleciona um dos presentes no aplicativo. O aplicativo faz uma solicitação ao nosso servidor para obter uma lista dos possíveis pacotes de empréstimos para identificação do produto (100, 550, 2000, 5000). O servidor retorna essa lista para o aplicativo.
Em seguida, o aplicativo envia a lista de IDs do produto recebida para verificação ao ator da App Store (estrutura iOS do sistema StoreKit que vai para o servidor Apple). Ele retorna uma lista de produtos comprovados - e, como resultado, o aplicativo mostra ao usuário a lista final de pacotes de empréstimos com ícones e preços.
3. Seleção de produtos e geração de recibos.
O usuário seleciona um produto pago. A App Store exige comprovante de compra e autorização via Apple ID. Após a autorização bem-sucedida do usuário, o controle é transferido para o aplicativo. O aplicativo está aguardando a geração de um recibo dentro de seu próprio pacote. O usuário neste momento vê o sol, que bloqueia a tela. Esse recibo foi gerado pode ser entendido usando o método appStoreReceiptURL da classe
Bundle . Após a verificação ser gerada pela App Store, o aplicativo seleciona a verificação de seu pacote e envia uma solicitação com a verificação e os dados do usuário ao servidor Badoo.
4. Verificando a verificação no servidor Badoo.
Assim que o servidor Badoo recebe a verificação e os dados do usuário, ele os envia de volta ao lado do servidor Apple para realizar o primeiro ciclo de verificação. Esta é uma das recomendações da Apple. Então, neste primeiro ciclo de verificação, o servidor recebe informações sobre o status atual da assinatura.
5. Enviar notificações por push (notificação por push) do servidor.
O servidor Badoo processa novamente as informações recebidas após a verificação pela Apple e envia ao aplicativo uma resposta junto com uma notificação por push.
6. Notificação por push no aplicativo.
Se foi uma compra de empréstimos, imediatamente o saldo do usuário no aplicativo será alterado e ele verá o presente enviado no chat. Se foi uma compra de assinatura, o usuário deve aguardar a notificação por push final de que a assinatura está ativada.
3. Determinação de dependências e loop de teste
Para uma discussão mais aprofundada, apresentamos mais dois conceitos - a dependência externa e o circuito de teste.
Dependência externa
Por dependências externas, entendemos qualquer interação com um componente, que é para nós uma "caixa preta". Nesse caso, a App Store atua como um componente na forma de uma estrutura de sistema iOS (StoreKit), com a qual nosso aplicativo iOS funciona, e um servidor Apple, para onde as solicitações de verificação vão.
Gerenciar essas dependências em condições reais é impossível, o aplicativo é forçado a responder aos sinais de saída da caixa preta (veja a Fig. 2).
Temos três dependências externas:
- Verificando os produtos StoreKit.
- Recebendo e Substituindo um Recibo de Compra.
- Verificando uma verificação em um servidor Badoo.
Figura 2. Dependências externasCircuito de teste
Circuito de teste - são seções do caminho que percorreremos e verificaremos durante o processo de teste.
Figura 3. Loop de testeO objetivo do nosso trabalho na eliminação de dependências é construir um circuito de teste o mais próximo possível do caminho real e permitir excluir todas as dependências externas e transferir o controle para o seu lado.
Consideramos cada dependência em sequência.
4. Isolamento de dependências: implementação técnica
Em nossa empresa, para a implementação de pagamentos, foi adotado um conceito de PPP, baseado na interface do Provedor de Pagamentos. Essa é a interface principal para interagir com o ator da App Store (StoreKit) dentro de nosso aplicativo, que possui dois métodos principais:
- preparar é o método responsável pela verificação dos produtos;
- makePayment é um método que processa uma compra no aplicativo.
Todos os pagamentos no iOS foram refatorados de acordo com esse conceito, o que nos permitiu obter um Provedor de Pagamento Simulado de classe simples e conveniente. Essa é a interface principal para interagir com uma cópia conveniente do comportamento do StoreKit dentro de nosso aplicativo. O que significa "cópia conveniente"? Esse provedor possui zombarias dos métodos de preparação e makePayment que fazem o que queremos. Vejamos um exemplo de partes de código, como conseguimos integrar o moki.
Dependência nº 1. Verificando produtos StoreKit
Para verificar a lista de produtos, use a função de preparação, que retorna uma lista de produtos verificados. Podemos usar a simulação na qual desativamos a verificação e retornamos a lista de produtos recebidos como totalmente verificada. Assim, a dependência será eliminada.
Figura 4. O primeiro esquema de eliminação de dependênciaNo topo da arquitetura em nosso aplicativo está o Provedor de Pagamentos. Ele reflete a interface de um possível provedor no aplicativo. O código para implementar o mok pode ser encontrado na classe Mock Payment Provider.
public class MockPaymentProvider: PaymentProvider { public static var receipt: String? public static var storeKitTransactionID: String? public func prepare(products: [BMProduct]) -> [BMProduct] { return products } ... }
Listagem 1. Mock client checkNo provedor de pagamento simulado, podemos ver a implementação do método de preparação. A mágica do moka acaba sendo muito simples: o método ignorou a verificação de produtos no lado StoreKit e simplesmente retorna uma lista de produtos recebida. A implementação real de preparação é semelhante a esta:
public func prepare(products: [BMProduct]) -> [BMProduct] { let validatedProducts = self.productsSource.validate(products: products) return validatedProducts }
Listagem 2. Fornecedor de pagamento em loja realDependência No. 2. Recebendo e Substituindo um Recibo de Compra
A segunda dependência é um pouco mais complicada: precisamos remover a autorização primeiro para não manter o pool de contas de usuário e, de alguma forma, obter a verificação propriamente dita. Podemos simplesmente excluir o formulário de autorização:
Figura 5. Excluindo um formulário de autorização ao efetuar um pagamentoNão é tão simples com um cheque. Há muitas perguntas:
- Como obter antecipadamente um recibo do produto certo?
- Se recebemos o cheque, quando e como anexá-lo ao aplicativo?
Aqui, o ator "Usuário" tem um novo papel - controle de qualidade. Quando executamos o teste, não podemos apenas clicar nos botões da interface, mas também chamar os métodos de API da estrutura de teste (métodos que simulam ações do usuário) e os serviços de API REST (métodos que podem fazer mágica com o serviço interno do Badoo). No Badoo, usamos uma ferramenta API de controle de qualidade muito poderosa (você pode encontrar todos os seus recursos no link:
https://vimeo.com/116931200 ). É ele quem nos ajuda nos testes e verifica o produto certo no lado do servidor do Badoo. O servidor Badoo é o melhor local para gerar verificações: há criptografia e descriptografia da verificação, para que o servidor saiba tudo sobre essa estrutura de dados.
Depois de recebermos um cheque falso, podemos colocá-lo através de uma
porta traseira no lado do aplicativo. Em seguida, o aplicativo enviará um cheque falso junto com os dados do usuário para o nosso servidor.
Figura 6. Esquema para recebimentoComo isso se tornou tecnicamente possível?
1. Para configurar um cheque falso no aplicativo, pudemos usar um backdoor que salvou o cheque falso no campo MockPaymentProvider do recibo:
#if BUILD_FOR_AUTOMATION @objc extension BadooAppDelegate { @objc func setMockPurchaseReceipt(_ receipt: String?) { PaymentProvidersFactory.useMockPaymentProviderForITunesPayments = true MockPaymentProvider.receipt = receipt } ... } #endif
Listagem 3. Backdoor de verificação falsa2. O aplicativo pôde fazer nosso cheque graças ao MockPaymentProvider, no qual usamos a simulação makePayment e o cheque salvo no MockPaymentProvider.receipt:
public class MockPaymentProvider: PaymentProvider { ... public func makePayment(_ transaction: BPDPaymentTransactionContext) { ... if let receiptData = MockPaymentProvider.receipt?.data(using: .utf8) { let request = BPDPurchaseReceiptRequest(...) self.networkService.send(request, completion: { [weak self] (_) in guard let sSelf = self else { return } if let receipt = request.responsePayload() { sSelf.delegate?.paymentProvider(sSelf, didReceiveReceipt: receipt) } }) } else { self.delegate?.paymentProvider(self, didFailTransaction: transaction) } } }
Listagem 4. Chamando um moka de processamento de compras com um cheque falso3. Conseguir um cheque falso
Para obter uma verificação falsa, usamos o método no servidor (consulte a Listagem 5). É necessária uma matriz padrão com dados para gerar dados de verificação e inclui os dados necessários para um produto específico.
$new_receipt_model = array_replace_recursive(
Listagem 5. Parte do servidor da geração de verificaçãoPara repetir a estrutura de uma verificação real, a verificação personalizada enviada pelo aplicativo deve ser criptografada usando um certificado. Usamos nosso certificado de trabalho em vez do certificado da Apple.
function signReceipt($receipt, $response) {
Listagem 6. Método para assinar uma verificação com um certificado4. Como resultado, no teste, obtemos:
(/ "((\d+) | (\d+) ?/) do |service_type|
Lista 7. Etapa de teste do Gherkin para a estrutura CucumberDependência nº 3. Verificando uma verificação em um servidor Badoo
Para remover a terceira dependência, você precisa se livrar da verificação do cheque no servidor. É importante lembrar que a verificação é feita em duas etapas. No primeiro estágio, a verificação é autenticada com base em assinaturas e certificados. No segundo - o cheque é enviado para a App Store. Em caso de validação bem-sucedida nesta fase, receberemos uma verificação descriptografada que pode ser processada.
Figura 7. Removendo a verificação do servidorPrimeiro, o servidor executa a verificação inicial da verificação no método confirmReceiptByCert da classe pai. Isso verifica a assinatura com o certificado da App Store. No caso de uma verificação falsa, essa verificação falhará porque foi assinada pelo nosso certificado e chamaremos o método de verificação com o certificado local confirmReceiptByLocalCert. Nesse método, tentaremos descriptografar a verificação usando um certificado local e, se for bem-sucedido, colocaremos o resultado da descriptografia no campo interno local_receipt da classe filho (método addLocallyVerifiedReceipt).
class EngineTest extends Engine function verifyReceiptByCert($receipt) { $result = parent::verifyReceiptByCert($receipt); if ($result === -1 || empty($result)) { $result = $this->verifyReceiptByLocalCert($receipt); } return $result; } function verifyReceiptByLocalCert($receipt) { $receipt_file = tempnam(sys_get_temp_dir(), 'rcp'); file_put_contents($receipt_file, base64_decode($receipt)); $result = openssl_pkcs7_verify($receipt_file, PKCS7_BINARY, '/dev/null', [$DIR]); if ($result) { $this->addLocallyVerifiedReceipt($receipt, base64_decode($response)); } unlink($receipt_file); return $result; } class Engine function verifyReceiptByCert($receipt) { $receipt_file = tempnam(sys_get_temp_dir(), 'rcp'); file_put_contents($receipt_file, base64_decode($receipt)); $result = openssl_pkcs7_verify($receipt_file, PKCS7_BINARY, '/dev/null', [$DIR]); unlink($receipt_file); return $result; }
Listagem 8. Verificação inicialDurante a verificação secundária (confirmReceipt), obtemos o valor do campo local_receipt da classe filho getLocallyVerifiedReceipt. Se não estiver vazio, usaremos seu valor como resultado da verificação.
Se o campo estiver vazio, chamaremos a verificação secundária da classe
pai (
parent :: confirmReceipt). Lá, solicitamos à App Store a verificação do seu lado. O resultado da verificação em ambos os casos é uma verificação descriptografada.
class EngineTest extends Engine function verifyReceipt($receipt_encoded, $shared_secret, $env) { $response = $this->getLocallyVerifiedReceipt($receipt_encoded); if (!empty($response)) { return json_decode($response, true); } return parent::verifyReceipt($receipt_encoded, $shared_secret, $env); } class Engine function verifyReceipt($receipt_encoded, $shared_secret, $env) { $response = $this->_sendRequest($receipt_encoded, $shared_secret, $env); return $response; }
Listagem 9. Verificação secundária5. Teste de vídeo: compra de empréstimos e assinaturas
Teste número 1. Compra de Assinaturas
Teste de execução de vídeo:

Teste número 2. Comprando empréstimos e enviando um presente
Teste de execução de vídeo:

Avaliação da decisão: principais riscos
A remoção de dependências externas acarreta certos riscos.
1. Configuração incorreta.
Como a verificação não está do nosso lado, podemos configurar nossos produtos incorretamente no lado da Apple. Para nos proteger do erro, escrevemos um teste de unidade separado do lado do servidor, que verifica se todos os produtos que iniciamos no lado da Apple correspondem aos produtos que temos em nossa configuração.
2. Casos fronteiriços.
Por exemplo, quando o pagamento é totalmente concluído, o usuário recebe uma notificação de que ele foi concluído, mas nosso aplicativo não consegue encontrar o cheque que deve ser falsificado como resultado do pagamento. O risco está no fato de nós mesmos anexarmos a verificação com a ajuda de uma porta dos fundos e, naturalmente, não podemos rastrear esse caso. Para compensar de alguma forma esse risco, realizamos verificações completas usando a sandbox ou um pagamento real após o lançamento.
3. Falsificação injusta ou fraude.
Depois de ler este artigo, você pode pensar que, como o Badoo usa cheques falsos, você pode anexar algo falso a nós e usar o serviço gratuitamente. Para que esse risco não se concretize, assinamos tudo com nosso próprio certificado e limitamos o uso de moks e verificações falsas a testes funcionais executados apenas em nosso ambiente de desenvolvimento.
4. Altere o formato da verificação.
Esse é o risco mais sério. É possível alterar o formato de um cheque quando a Apple altera algo sem nos avisar. Tivemos um caso assim: ao mudar para o iOS 11, o formato da verificação mudou completamente. Geramos uma verificação falsa em nosso servidor e a usamos no teste. Tudo estava perfeito conosco: todos os campos estão no lugar, tudo é maravilhoso, tudo está sendo processado. Mas quando mudamos para o sistema real, nada funcionou. Os campos significativos na verificação simplesmente deixaram de existir.
Como compensar esse risco? Em primeiro lugar, não excluímos a possibilidade de teste de ponta a ponta da sandbox antes do lançamento e pagamento real após o lançamento. Agora, estamos na fase ativa de um projeto para verificar as notificações, quando tentamos classificar todas as verificações que recebemos da produção com base no fato de entendermos o que é ou não. Se a resposta for não, então começamos a processar tudo manualmente, ver o que mudou, o que está errado, o que precisa ser alterado em nosso sistema.
Resultado
Considere as principais vantagens que conseguimos obter como resultado da aplicação do método moki e do objeto falso.
Automação barata, rápida e estável de serviços pagos no iOS
Juntamente com a equipe de testes manuais do iOS (agradecimentos especiais a Colin Chan), conseguimos escrever mais de 150 testes automáticos para pagamentos. Essa é uma quantidade bastante grande de cobertura para uma área do aplicativo.
Graças à paralelização, podemos obter o resultado em apenas 15 a 20 minutos em qualquer filial do desenvolvedor do cliente iOS ou do servidor de cobrança. Antes da automação, o teste manual dessa área por uma pessoa levava oito horas.
Também podemos testar a grande maioria dos casos de teste, configurando o Mock Payment Provider através do moki da maneira que precisamos. Com a ajuda de mooks, aprendemos como desativar a verificação do produto e simular casos quando a verificação é parcialmente realizada. Assim, abrimos casos que antes não podíamos testar em princípio.
Regressão funcional no desenvolvimento de novos recursos
A automação funcionou muito bem nesses casos em que o desenvolvedor no processo de trabalhar em um novo recurso afetou a funcionalidade antiga. Tivemos um exemplo quando um desenvolvedor fez um recurso complexo com cache e executou nossos testes automáticos. Alguns deles caíram no erro. Ele viu e consertou. Então ele reiniciou os autotestes novamente - e novamente, algo caiu. Como resultado, ele fez uma série de iterações até o momento em que tudo começou a funcionar normalmente no lado do aplicativo.
Regressão funcional na refatoração de pagamentos
Talvez a automação mais bem-sucedida e eficiente possível ocorra no campo da refatoração de código. Nesse caso, apenas a implementação interna é alterada - não há necessidade de alterar o código de autoteste. A interface do usuário não muda de forma alguma e os autotestes podem ser conduzidos com eficiência.
Testando recursos experimentais da Apple: período de carência
Um sistema semelhante é completamente intercambiável quando você testa novas integrações que ainda não foram implementadas na sandbox. Assim foi com o período de carência. Essa funcionalidade não está na sandbox. O período de carência na Apple ainda não está disponível para todos. Este é um projeto piloto que o Badoo está implementando com a Apple. Para verificar com um período de cortesia, precisamos adicionar aqui uma parte do código JSON:
pending_renewal_info:[ { expiration_intent: 2 grace_period_expires_date: 2019-04-25 15:50:57 Etc/GMT auto_renew_product_id: badoo.productId original_transaction_id: 560000361869085 is_in_billing_retry_period: 1 grace_period_expires_date_pst: 2019-04-25 08:50:57 America/Los_Angeles product_id: badoo.productId grace_period_expires_date_ms: 1556207457000 auto_renew_status: 1 }]
Listagem 10. Período de carência para uma assinaturaFizemos isso com muita facilidade em apenas alguns segundos. Em nosso sistema, fomos capazes de testar nossa reação a um novo recurso. Agora estamos executando essa funcionalidade no prod.
Teste de qualidade do produto em métodos de composição
Como resultado de nossa pesquisa, fomos capazes de descrever um método que elimina o ruído de dependências externas. Isso ajudou os desenvolvedores de clientes no processo de desenvolvimento de recursos a encontrar erros nos estágios iniciais.
Mas não pense que fomos capazes de testar tudo com esse método. Para testar tudo, é melhor usar uma composição de métodos: teste com um cartão real no produto, teste na caixa de areia, método de mokes e objetos falsos, teste de unidade e integração. Lembre-se do equilíbrio da pirâmide de teste e não tente resolver todos os problemas com um método. Isso pode levar a uma automação triste na caixa de areia, a testes manuais tristes com um cartão real de todos os casos e a muitos outros erros graves no local exato em que sua aparência é mais dolorosa.
Conclusão
Como resultado de nossa pesquisa, obtivemos um método barato, rápido e estável de testar não apenas serviços pagos no iOS, mas também quaisquer componentes incorporados ao aplicativo como uma "caixa preta". Agora, no Badoo, estamos implementando esse método para testes em provedores pagos do Android (Global Charge, Boku, Centili) que possuem sandboxes instáveis ou outras restrições. Também usamos o método moki para testar publicidade, streaming e geolocalização.
Vale dizer que o processo de introdução de um novo método não foi rápido. Eu tive que negociar com quatro equipes: controle de qualidade do iOS, desenvolvedor de iOS, controle de qualidade de faturamento, desenvolvedor de faturamento. Nem todo mundo queria mudar para um novo método, temendo riscos. Às vezes, era um seguimento dogmático: durante muitos anos testamos na caixa de areia, e a principal força que poderia destruir o dogma era o desejo dos testadores de cobrança e da plataforma iOS de mudar a situação e se livrar do tormento. Posteriormente, os desenvolvedores perceberam essas vantagens desse método, como diagnóstico preciso (não conseguimos encontrar bugs no sandbox, mas bugs de nosso cliente ou servidor), flexibilidade na configuração do componente (conseguimos testar facilmente casos negativos no nível de integração) e, é claro, a resposta foi 30 minutos em uma ramificação com código desenvolvido.
Muito obrigado a todos que leram até o fim. Muito obrigado a todos que ajudaram e participaram deste projeto. Agradecimentos especiais a essas pessoas:
- Peter Kolpashchikov é um desenvolvedor iOS que ajudou a criar moki no lado do cliente e desenvolveu um conceito de PPP;
- Vladimir Solodov - Controle de qualidade de cobrança, que ajudou na API de controle de qualidade para gerar cheques e caixas falsos no servidor de cobrança;
- Maxim Filatov e Vasily Stepanov - equipe de desenvolvimento de cobrança, que ajudou no código do servidor de cobrança;
- Equipe de desenvolvimento do iOS - desenvolvedores que foram capazes de refatorar nossos pagamentos em um novo conceito, possibilitando o uso de mokas;
- A equipe de controle de qualidade do iOS é uma incrível equipe de testes que escreveu vários autotestes;
- Equipe de QA de cobrança - testadores que ajudaram a pesquisar problemas.