Tutorial de teste da unidade iOS e teste da IU

Nota de atualização: Michael Katz atualizou este tutorial para Xcode 10.1, Swift 4.2, e iOS 12. Audrey Tam escreveu o original.

Testes de escrita não é glamoroso, mas como os testes evitam que seu aplicativo cintilante se transforme em um pedaço de lixo cheio de bugs, é necessário. Se você está lendo este tutorial, você já sabe que deve escrever testes para o seu código e interface, mas você pode não saber como.

Você pode ter uma aplicação funcionando, mas quer testar as alterações que está fazendo para estender a aplicação. Talvez você já tenha escrito testes, mas não tem certeza se eles são os testes certos. Ou, você começou a trabalhar em um novo aplicativo e quer testar à medida que você vai.

Este tutorial irá mostrar-lhe:

  • Como usar o navegador de testes do Xcode para testar o modelo de um aplicativo e métodos assíncronos
  • Como falsificar interações com objetos de biblioteca ou sistema usando stubs e mocks
  • Como testar a IU e o desempenho
  • Como usar a ferramenta de cobertura de código

Altra o caminho, vais apanhar algum do vocabulário usado para testar ninjas.

Pigurar o que testar

Antes de escrever qualquer teste, é importante conhecer o básico. O que você precisa testar?

Se o seu objectivo é estender um aplicativo existente, deve primeiro escrever testes para qualquer componente que pretenda alterar.

De um modo geral, os testes devem abranger:

  • Funcionalidade principal: Classes de modelos e métodos e suas interações com o controlador
  • Os fluxos de trabalho mais comuns da IU
  • Condições-limite
  • Correções de erros

Best Practices for Testing

O acrônimo FIRST descreve um conjunto conciso de critérios para testes unitários eficazes. Esses critérios são:

  • Rápido: Os testes devem ser executados rapidamente.
  • Independente/Isolado: Os testes não devem compartilhar estado entre si.
  • Repetível: Você deve obter os mesmos resultados toda vez que executar um teste. Fornecedores de dados externos ou problemas de concordância podem causar falhas intermitentes.
  • Auto-validação: Os testes devem ser totalmente automatizados. A saída deve ser “pass” ou “fail”, em vez de confiar na interpretação de um programador de um arquivo de log.
  • Timely: Idealmente, os testes devem ser escritos antes de você escrever o código de produção que eles testam (Test-Driven Development).

Seguir os princípios do FIRST irá manter seus testes claros e úteis, em vez de se transformar em bloqueios para o seu aplicativo.

Começando

Comece baixando os materiais do projeto usando o botão Download Materials no topo ou no fundo deste tutorial. Há dois projetos iniciais separados: BullsEye e HalfTunes.

  • BullsEye é baseado em uma aplicação de amostra no iOS Apprentice. A lógica do jogo está na classe BullsEyeGame, que você irá testar durante este tutorial.
  • HalfTunes é uma versão atualizada da aplicação de exemplo do URLSession Tutorial. Os usuários podem consultar a API do iTunes para músicas, depois baixar e reproduzir trechos de músicas.

Unit Testing in Xcode

O navegador Test fornece a maneira mais fácil de trabalhar com testes; você vai usá-lo para criar alvos de teste e executar testes contra a sua aplicação.

Criar um alvo de teste unitário

Abra o projeto BullsEye e pressione Command-6 para abrir o navegador de testes.

Clique no botão + no canto inferior esquerdo, depois selecione New Unit Test Target… no menu:

Aceitar o nome padrão, BullsEyeTests. Quando o pacote de testes aparecer no navegador de testes, clique para abrir o pacote no editor. Se o pacote não aparecer automaticamente, resolva o problema clicando em um dos outros navegadores, depois volte para o navegador Test.

O modelo padrão importa o framework de testes, XCTest, e define uma subclasse de BullsEyeTests, com setUp(), tearDown(), e exemplos de métodos de teste.

Existem três formas de executar os testes:

  1. Produto ▸ Test or Command-U. Ambos executam todas as classes de teste.
  2. Clique no botão de seta no navegador de teste.
  3. Clique no botão diamante na sarjeta.

Tambem pode executar um método de teste individual clicando no seu diamante, seja no navegador de testes ou na sarjeta.

Tente as diferentes maneiras de executar testes para ter uma sensação de quanto tempo leva e como se parece. Os testes de amostra ainda não fazem nada, então eles correm muito rápido!

Quando todos os testes forem bem sucedidos, os diamantes ficarão verdes e mostrarão marcas de verificação. Você pode clicar no diamante cinza no final de testPerformanceExample() para abrir o resultado do desempenho:

Você não precisa de testPerformanceExample() ou testExample() para este tutorial, então delete-os.

Usando XCTAssert para testar modelos

Primeiro, você usará XCTAssert funções para testar uma função central do modelo do BullsEye: Um objecto BullsEyeGame calcula correctamente a pontuação para uma ronda?

Em BullsEyeTests.swift, adicione esta linha logo abaixo da declaração import>

@testable import BullsEye

Isto dá à unidade de testes acesso aos tipos e funções internas do BullsEye.

No topo da classe BullsEyeTests, adicione esta propriedade:

var sut: BullsEyeGame!

Cria um espaço reservado para um BullsEyeGame, que é o System Under Test (SUT), ou o objeto que esta classe de caso de teste se preocupa com testes.

Próximo, substitua o conteúdo de setup() por este:

super.setUp()sut = BullsEyeGame()sut.startNewGame()

Cria um objeto BullsEyeGame no nível da classe, para que todos os testes nesta classe de teste possam acessar as propriedades e métodos do objeto SUT.

Aqui, você também chama o objeto startNewGame() do jogo, que inicializa o targetValue. Muitos dos testes irão usar targetValue para testar que o jogo calcula a pontuação corretamente.

Antes de esquecer, libere seu objeto SUT em tearDown(). Substitua seu conteúdo por:

sut = nilsuper.tearDown()
Nota: É uma boa prática criar o SUT em setUp() e liberá-lo em tearDown() para garantir que cada teste comece com uma tabela limpa. Para mais discussão, confira o post de Jon Reid sobre o assunto.

Escrevendo seu primeiro teste

Agora, você está pronto para escrever seu primeiro teste!

Adicionar o seguinte código ao final de BullsEyeTests:

func testScoreIsComputed() { // 1. given let guess = sut.targetValue + 5 // 2. when sut.check(guess: guess) // 3. then XCTAssertEqual(sut.scoreRound, 95, "Score computed from guess is wrong")}

O nome de um método de teste começa sempre com teste, seguido por uma descrição do que testa.

É boa prática formatar o teste em dado, quando e depois secções:

  1. Dado: Aqui, você configura quaisquer valores necessários. Neste exemplo, você cria um valor guess para poder especificar o quanto ele difere de targetValue.
  2. Quando: Nesta seção, você executará o código que está sendo testado: Chame check(guess:).
  3. Depois: Esta é a seção onde você irá afirmar o resultado esperado com uma mensagem que será impressa se o teste falhar. Neste caso, sut.scoreRound deve ser igual a 95 (100 – 5).

Executar o teste clicando no ícone do diamante na sarjeta ou no navegador do teste. Isto irá construir e executar o aplicativo, e o ícone do diamante mudará para uma marca de verificação verde!

Nota: Para ver uma lista completa do XCTestAssertions, vá para Apple’s Assertions Listted by Category.

Debugging a Test

Existe um bug incorporado em BullsEyeGame de propósito, e você vai praticar encontrá-lo agora. Para ver o bug em ação, você vai criar um teste que subtrai 5 de targetValue na seção dada, e deixa tudo o resto igual.

Adicionar o seguinte teste:

func testScoreIsComputedWhenGuessLTTarget() { // 1. given let guess = sut.targetValue - 5 // 2. when sut.check(guess: guess) // 3. then XCTAssertEqual(sut.scoreRound, 95, "Score computed from guess is wrong")}

A diferença entre guess e targetValue ainda é 5, portanto a pontuação ainda deve ser 95.

No navegador do Breakpoint, adicione um Breakpoint de Falha do Teste. Isto irá parar a execução do teste quando um método de teste colocar uma afirmação de falha.

Executar o teste, e deve parar na linha XCTAssertEqual com uma falha no teste.

Inspecta sut e guess no console de depuração:

>

guess é targetValue - 5 mas scoreRound é 105, não 95!

Para investigar mais, use o processo normal de depuração: Defina um ponto de parada no comando when e também um em BullsEyeGame.swift, dentro de check(guess:), onde ele cria difference. Em seguida, execute o teste novamente, e passe sobre a instrução let difference para inspecionar o valor de difference no app:

O problema é que difference é negativo, então a pontuação é 100 – (-5). Para corrigir isso, você deve usar o valor absoluto de difference. Em check(guess:), descomente a linha correta e exclua a incorreta.

Remova os dois pontos de parada, e execute o teste novamente para confirmar que ele agora é bem sucedido.

Usando XCTestExpectation para testar operações assíncronas

Agora que você aprendeu como testar modelos e depurar falhas no teste, é hora de passar para testar código assíncrono.

Abra o projeto HalfTunes. Ele usa URLSession para consultar a API do iTunes e baixar amostras de músicas. Suponha que você queira modificá-la para usar o AlamoFire para operações de rede. Para ver se algo quebra, você deve escrever testes para as operações de rede e executá-los antes e depois de alterar o código.

URLSession os métodos são assíncronos: eles retornam imediatamente, mas não terminam de funcionar até mais tarde. Para testar métodos assíncronos, você usa XCTestExpectation para fazer seu teste esperar que a operação assíncrona seja concluída.

Testes assíncronos são normalmente lentos, então você deve mantê-los separados de seus testes unitários mais rápidos.

Criar um novo alvo de teste unitário chamado HalfTunesSlowTests. Abra a classe HalfTunesSlowTests, e importe o módulo de aplicação do HalfTunes logo abaixo do import existente:

@testable import HalfTunes

Todos os testes nesta classe usam o padrão URLSession para enviar pedidos para os servidores da Apple, então declare um objeto sut, crie-o em setUp() e solte-o em tearDown().

Substitua o conteúdo da classe HalfTunesSlowTests por:

var sut: URLSession!override func setUp() { super.setUp() sut = URLSession(configuration: .default)}override func tearDown() { sut = nil super.tearDown()}

Next, adicione este teste assíncrono:

// Asynchronous test: success fast, failure slowfunc testValidCallToiTunesGetsHTTPStatusCode200() { // given let url = URL(string: "https://itunes.apple.com/search?media=music&entity=song&term=abba") // 1 let promise = expectation(description: "Status code: 200") // when let dataTask = sut.dataTask(with: url!) { data, response, error in // then if let error = error { XCTFail("Error: \(error.localizedDescription)") return } else if let statusCode = (response as? HTTPURLResponse)?.statusCode { if statusCode == 200 { // 2 promise.fulfill() } else { XCTFail("Status code: \(statusCode)") } } } dataTask.resume() // 3 wait(for: , timeout: 5)}

Este teste verifica se o envio de uma consulta válida para o iTunes retorna um código de estado de 200. A maioria do código é o mesmo que você escreveria no aplicativo, com estas linhas adicionais:

  1. expectation(description:): Retorna um objeto XCTestExpectation, armazenado em promise. O parâmetro description descreve o que você espera que aconteça.
  2. promise.fulfil(): Chame isso no fechamento da condição de sucesso do manipulador de conclusão do método assíncrono para sinalizar que a expectativa foi atendida.
  3. wait(for:timeout:): Mantém o teste em execução até que todas as expectativas sejam cumpridas, ou o intervalo timeout termina, o que acontecer primeiro.

Executar o teste. Se você estiver conectado à internet, o teste deve levar cerca de um segundo para ser bem sucedido após a carga da aplicação no simulador.

Failing Fast

Failure magoa, mas não precisa levar uma eternidade.

Para experimentar o fracasso, basta apagar os ‘s’ do “itunes” na URL:

let url = URL(string: "https://itune.apple.com/search?media=music&entity=song&term=abba")

Executar o teste. Ele falha, mas leva o intervalo de tempo completo! Isto é porque você assumiu que o pedido seria sempre bem sucedido, e foi aí que você ligou para promise.fulfill(). Como a solicitação falhou, ela só terminou quando o intervalo expirou.

Você pode melhorar isso e fazer o teste falhar mais rápido alterando a suposição: Em vez de esperar pelo sucesso da requisição, espere apenas até que o manipulador de conclusão do método assíncrono seja invocado. Isto acontece assim que o aplicativo recebe uma resposta – OK ou erro – do servidor, o que preenche a expectativa. Seu teste pode então verificar se a solicitação foi bem sucedida.

Para ver como isso funciona, crie um novo teste.

Mas primeiro, corrija o teste anterior, desfazendo a alteração feita no teste url.
Então, adicione o seguinte teste à sua classe:

func testCallToiTunesCompletes() { // given let url = URL(string: "https://itune.apple.com/search?media=music&entity=song&term=abba") let promise = expectation(description: "Completion handler invoked") var statusCode: Int? var responseError: Error? // when let dataTask = sut.dataTask(with: url!) { data, response, error in statusCode = (response as? HTTPURLResponse)?.statusCode responseError = error promise.fulfill() } dataTask.resume() wait(for: , timeout: 5) // then XCTAssertNil(responseError) XCTAssertEqual(statusCode, 200)}

A diferença principal é que simplesmente entrar com o manipulador de conclusão satisfaz a expectativa, e isso leva apenas cerca de um segundo para acontecer. Se a solicitação falhar, as afirmações then falham.

Realize o teste. Agora deve demorar cerca de um segundo para falhar. Ela falha porque a requisição falhou, não porque a execução do teste excedeu timeout.

Fixar o url, e então executar o teste novamente para confirmar que ele agora tem sucesso.

Falsificação de Objetos e Interações

Testes assíncronos dão a você a confiança de que seu código gera a entrada correta para uma API assíncrona. Você também pode querer testar se o seu código funciona corretamente quando recebe entrada de um URLSession, ou se atualiza corretamente o banco de dados padrão do usuário ou um container iCloud.

A maioria dos aplicativos interagem com objetos do sistema ou da biblioteca – objetos que você não controla – e os testes que interagem com esses objetos podem ser lentos e irrepetíveis, violando dois dos princípios do PRIMEIRO. Em vez disso, você pode falsificar as interações obtendo a entrada de stubs ou atualizando objetos mock.

Falsificar quando seu código tem uma dependência de um objeto do sistema ou biblioteca. Você pode fazer isso criando um objeto falso para desempenhar esse papel e injetando essa falsificação em seu código. Dependency Injection by Jon Reid descreve várias maneiras de fazer isso.

Fake Input From Stub

Neste teste, você vai verificar se os dados da aplicação updateSearchResults(_:) estão corretos, verificando se searchResults.count está correto. O SUT é o controlador de visualização, e você falsificará a sessão com stubs e alguns dados pré-descarregados.

Vá até o navegador de testes e adicione um novo alvo de teste de unidade. Dê o nome de HalfTunesFakeTests. Abra o HalfTunesFakeTests.rápido e importe o módulo de aplicação do HalfTunes logo abaixo da instrução import:

@testable import HalfTunes

Agora, substitua o conteúdo da classe HalfTunesFakeTests por este:

var sut: SearchViewController!override func setUp() { super.setUp() sut = UIStoryboard(name: "Main", bundle: nil) .instantiateInitialViewController() as? SearchViewController}override func tearDown() { sut = nil super.tearDown()}

Esta declara o SUT, que é um SearchViewController, cria-o em setUp() e o libera em tearDown():

Nota: O SUT é o controlador de visualização, pois o HalfTunes tem um enorme problema no controlador de visualização – todo o trabalho é feito no SearchViewController.rápido. Mover o código de rede para um módulo separado reduziria esse problema e, também, tornaria o teste mais fácil.

Próximo, você precisará de alguns exemplos de dados JSON que sua sessão falsa fornecerá ao seu teste. Apenas alguns itens servirão, então para limitar os resultados de download no iTunes anexe &limit=3 à string URL:

https://itunes.apple.com/search?media=music&entity=song&term=abba&limit=3

Copie esta URL e cole-a em um navegador. Isto descarrega um ficheiro chamado 1.txt, 1.txt.js ou similar. Pré-visualize-o para confirmar que é um arquivo JSON, depois renomeie-o abbaData.json.

Agora, volte para Xcode e vá para o navegador do Projeto. Adicione o arquivo ao grupo HalfTunesFakeTests.

O projeto HalfTunes contém o arquivo de suporte DHURLSessionMock.swift. Isto define um protocolo simples chamado DHURLSession, com métodos (stubs) para criar uma tarefa de dados com um URL ou um URLRequest. Também define URLSessionMock, que está em conformidade com este protocolo com inicializadores que lhe permitem criar um objecto mock URLSession com a sua escolha de dados, resposta e erro.

Para configurar o fake, vá a HalfTunesFakeTests.swift e adicione o seguinte em setUp(), após a declaração que cria o SUT:

let testBundle = Bundle(for: type(of: self))let path = testBundle.path(forResource: "abbaData", ofType: "json")let data = try? Data(contentsOf: URL(fileURLWithPath: path!), options: .alwaysMapped)let url = URL(string: "https://itunes.apple.com/search?media=music&entity=song&term=abba")let urlResponse = HTTPURLResponse( url: url!, statusCode: 200, httpVersion: nil, headerFields: nil)let sessionMock = URLSessionMock(data: data, response: urlResponse, error: nil)sut.defaultSession = sessionMock

Esta configuração configura os dados e a resposta falsos e cria o objeto da sessão falsa. Finalmente, no final, ele injeta a sessão falsa no aplicativo como uma propriedade de sut.

Agora, você está pronto para escrever o teste que verifica se chamar updateSearchResults(_:) analisa os dados falsos. Adicione o seguinte teste:

func test_UpdateSearchResults_ParsesData() { // given let promise = expectation(description: "Status code: 200") // when XCTAssertEqual( sut.searchResults.count, 0, "searchResults should be empty before the data task runs") let url = URL(string: "https://itunes.apple.com/search?media=music&entity=song&term=abba") let dataTask = sut.defaultSession.dataTask(with: url!) { data, response, error in // if HTTP request is successful, call updateSearchResults(_:) // which parses the response data into Tracks if let error = error { print(error.localizedDescription) } else if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 { self.sut.updateSearchResults(data) } promise.fulfill() } dataTask.resume() wait(for: , timeout: 5) // then XCTAssertEqual(sut.searchResults.count, 3, "Didn't parse 3 items from fake response")}

Você ainda tem que escrever isto como um teste assíncrono porque o stub está fingindo ser um método assíncrono.

A afirmação é que searchResults está vazio antes da tarefa de dados ser executada. Isto deve ser verdade, porque você criou um SUT completamente novo em setUp().

Os dados falsos contêm o JSON para três objetos Track, então a asserção é que o controlador de visualização searchResults array contém três itens.

Executar o teste. Deve ser bem rápido porque não há nenhuma conexão de rede real!

Fake Update to Mock Object

O teste anterior usou um stub para fornecer a entrada de um objeto falso. A seguir, você usará um objeto falso para testar se o seu código é atualizado corretamente UserDefaults.

Reabrir o projeto BullsEye. O aplicativo tem dois estilos de jogo: O usuário ou move o controle deslizante para corresponder ao valor alvo ou adivinha o valor alvo da posição do controle deslizante. Um controle segmentado no canto inferior direito muda o estilo do jogo e o salva nos padrões do usuário.

Seu próximo teste irá verificar se o aplicativo salva corretamente a propriedade.gameStyle

No navegador Test, clique em New Unit Test Class e nomeie o aplicativo BullsEyeMockTests. Adicione o seguinte abaixo a import instrução:

@testable import BullsEyeclass MockUserDefaults: UserDefaults { var gameStyleChanged = 0 override func set(_ value: Int, forKey defaultName: String) { if defaultName == "gameStyle" { gameStyleChanged += 1 } }}

MockUserDefaults anula a set(_:forKey:) para incrementar a bandeira gameStyleChanged. Muitas vezes, você verá testes similares que definem uma variável Bool, mas incrementar um Int lhe dá mais flexibilidade – por exemplo, seu teste poderia verificar se o método é chamado apenas uma vez.

Declare the SUT and the mock object in BullsEyeMockTests:

var sut: ViewController!var mockUserDefaults: MockUserDefaults!

Next, substitua o padrão setUp() e tearDown() por este:

override func setUp() { super.setUp() sut = UIStoryboard(name: "Main", bundle: nil) .instantiateInitialViewController() as? ViewController mockUserDefaults = MockUserDefaults(suiteName: "testing") sut.defaults = mockUserDefaults}override func tearDown() { sut = nil mockUserDefaults = nil super.tearDown()}

Isto cria o SUT e o objeto mock e injeta o objeto mock como uma propriedade do SUT.

Agora, substitua os dois métodos de teste padrão no modelo por this:

func testGameStyleCanBeChanged() { // given let segmentedControl = UISegmentedControl() // when XCTAssertEqual( mockUserDefaults.gameStyleChanged, 0, "gameStyleChanged should be 0 before sendActions") segmentedControl.addTarget(sut, action: #selector(ViewController.chooseGameStyle(_:)), for: .valueChanged) segmentedControl.sendActions(for: .valueChanged) // then XCTAssertEqual( mockUserDefaults.gameStyleChanged, 1, "gameStyle user default wasn't changed")}

A quando se afirma que a bandeira gameStyleChanged é 0 antes do método de teste alterar o controle segmentado. Então, se a afirmação de então também for verdadeira, significa que set(_:forKey:) foi chamado exatamente uma vez.

Executar o teste; ele deve ser bem sucedido.

Teste de IU no Xcode

Teste de IU permite testar as interações com a interface do usuário. O teste de IU funciona encontrando objetos de IU de um aplicativo com consultas, sintetizando eventos, e depois enviando os eventos para esses objetos. A API permite que você examine as propriedades e estado de um objeto de IU a fim de compará-las com o estado esperado.

No navegador de teste do projeto BullsEye, adicione um novo alvo de teste de IU. Verifique se o alvo a ser testado é o BullsEye, e então aceite o nome padrão BullsEyeUITests.

Open BullsEyeUITests.swift e adicione esta propriedade no topo da propriedade BullsEyeUITests class:

var app: XCUIApplication!

In setUp(), substitua a declaração XCUIApplication().launch() pelo seguinte:

app = XCUIApplication()app.launch()

Mude o nome de testExample() para testGameStyleSwitch().

Abra uma nova linha em testGameStyleSwitch() e clica no botão vermelho Gravar na parte inferior da janela do editor:

Esta abre a aplicação no simulador num modo que grava as suas interacções como comandos de teste. Uma vez carregada a aplicação, toque no segmento Slide da chave de estilo do jogo e no rótulo superior. Então, clique no botão Gravar código Xcode para parar a gravação.

Você agora tem as três linhas a seguir em testGameStyleSwitch():

let app = XCUIApplication()app.buttons.tap()app.staticTexts.tap()

O Gravador criou código para testar as mesmas ações que você testou no aplicativo. Envie um toque para o controle deslizante e para a etiqueta. Você vai usá-los como base para criar seu próprio teste de IU.
Se você vir qualquer outra declaração, basta excluí-la.

A primeira linha duplica a propriedade que você criou em setUp(), então exclua essa linha. Você não precisa tocar em nada ainda, então delete também .tap() no final das linhas 2 e 3. Agora, abra o pequeno menu ao lado de e seleccione segmentedControls.buttons.

O que lhe resta deve ser o seguinte:

app.segmentedControls.buttonsapp.staticTexts

Tapa quaisquer outros objectos para que o gravador o ajude a encontrar o código que pode aceder nos seus testes. Agora, substitua essas linhas por esse código para criar uma determinada seção:

// givenlet slideButton = app.segmentedControls.buttonslet typeButton = app.segmentedControls.buttonslet slideLabel = app.staticTextslet typeLabel = app.staticTexts

Agora você tem nomes para os dois botões no controle segmentado, e as duas possíveis etiquetas superiores, adicione o seguinte código abaixo:

// thenif slideButton.isSelected { XCTAssertTrue(slideLabel.exists) XCTAssertFalse(typeLabel.exists) typeButton.tap() XCTAssertTrue(typeLabel.exists) XCTAssertFalse(slideLabel.exists)} else if typeButton.isSelected { XCTAssertTrue(typeLabel.exists) XCTAssertFalse(slideLabel.exists) slideButton.tap() XCTAssertTrue(slideLabel.exists) XCTAssertFalse(typeLabel.exists)}

Isso verifica se a etiqueta correta existe quando você tap() em cada botão no controle segmentado. Execute o teste – todas as asserções devem ser bem sucedidas.

Teste de desempenho

Da documentação da Apple: Um teste de performance pega um bloco de código que você quer avaliar e o executa dez vezes, coletando o tempo médio de execução e o desvio padrão para as execuções. A média destas medições individuais formam um valor para a execução do teste que pode então ser comparado com uma linha de base para avaliar o sucesso ou fracasso.

É muito simples escrever um teste de performance: Basta colocar o código que você quer medir no fechamento do arquivo measure().

Para ver isso em ação, reabra o projeto HalfTunes e, no HalfTunesFakeTests.swift, adicione o seguinte teste:

func test_StartDownload_Performance() { let track = Track( name: "Waterloo", artist: "ABBA", previewUrl: "http://a821.phobos.apple.com/us/r30/Music/d7/ba/ce/mzm.vsyjlsff.aac.p.m4a") measure { self.sut.startDownload(track) }}

Executar o teste, depois clique no ícone que aparece ao lado do início do measure() fechamento móvel para ver as estatísticas.

Click Set Baseline para definir um tempo de referência. Em seguida, execute o teste de desempenho novamente e veja o resultado – ele pode ser melhor ou pior que a linha de base. O botão Editar permite redefinir a linha de base para este novo resultado.

Baselines são armazenadas por configuração de dispositivo, assim você pode ter o mesmo teste sendo executado em vários dispositivos diferentes, e ter cada um mantendo uma linha de base diferente dependendo da velocidade do processador da configuração específica, memória, etc.

A qualquer momento que você fizer alterações em um aplicativo que possam impactar o desempenho do método sendo testado, execute o teste de desempenho novamente para ver como ele se compara com a linha de base.

Code Coverage

A ferramenta de cobertura de código diz-lhe qual o código do aplicativo que está realmente a ser executado pelos seus testes, para que você saiba que partes do código do aplicativo não estão (ainda) testadas.

Para ativar a cobertura de código, edite a ação Teste do esquema e marque a caixa Reunir cobertura para marcar na guia Opções:

>

Executar todos os testes (Command-U), depois abra o navegador Relatório (Command-9). Selecione Coverage sob o item superior dessa lista:

Clique no triângulo de revelação para ver a lista de funções e fechamentos no SearchViewController.swift:

>>

Rolar até updateSearchResults(_:) para ver que a cobertura é 87,9%.

Clique no botão de seta para esta função para abrir o arquivo fonte para a função. Ao passar o mouse sobre as anotações de cobertura na barra lateral direita, seções de código destacam verde ou vermelho:

>

As anotações de cobertura mostram quantas vezes um teste atinge cada seção de código; seções que não foram chamadas são destacadas em vermelho. Como você esperaria, o for-loop rodou 3 vezes, mas nada nos caminhos de erro foi executado.

Para aumentar a cobertura desta função, você poderia duplicar abbaData.json, e então editá-lo para que ele cause os diferentes erros. Por exemplo, altere "results" para "result" para um teste que atinge print("Results key not found in dictionary").

100% de cobertura?

Quão difícil você deve se esforçar para ter 100% de cobertura de código? Google “100% de cobertura de unidade de teste” e você encontrará uma série de argumentos a favor e contra isso, juntamente com um debate sobre a própria definição de “100% de cobertura”. Os argumentos contra dizem que os últimos 10-15% não vale a pena o esforço. Os argumentos a favor dizem que os últimos 10-15% são os mais importantes, porque é muito difícil de testar. Google “hard to unit test bad design” para encontrar argumentos persuasivos de que código não testável é um sinal de problemas de design mais profundos.

Where to Go From Here?

You now have some great tools to use in writing tests for your projects. Espero que este tutorial de Teste de Unidade iOS e Teste de IU tenha lhe dado a confiança para testar todas as coisas!

Você pode baixar a versão completa do projeto usando o botão Baixar Materiais na parte superior ou inferior deste tutorial. Continue desenvolvendo suas habilidades adicionando os seus próprios testes.

Aqui estão alguns recursos para estudo adicional:

  • Existem vários vídeos da WWDC sobre o tópico de testes. Dois bons vídeos da WWDC17 são: Engenharia para Testabilidade e Dicas de Teste & Truques.
  • O próximo passo é a automação: Integração Contínua e Entrega Contínua. Comece com Apple’s Automating the Test Process with Xcode Server e xcodebuild, e o artigo de entrega contínua da Wikipedia, que se baseia na experiência da ThoughtWorks.
  • Se você já tem um aplicativo mas ainda não escreveu testes para ele, você pode querer se referir a Working Effectively with Legacy Code by Michael Feathers, porque código sem testes é código legado!
  • Jon Reid’s Quality Coding arquivos de amostra de aplicativos são ótimos para aprender mais sobre o desenvolvimento de aplicativos com Testes Driven.

raywenderlich.com Semanalmente

A newsletter raywenderlich.com é a maneira mais fácil de se manter atualizado sobre tudo o que você precisa saber como desenvolvedor móvel.

Receba um resumo semanal de nossos tutoriais e cursos, e receba um curso gratuito por e-mail em profundidade como um bônus!

Avaliação média

4.7/5

Adicionar uma avaliação para este conteúdo

Entrar para adicionar uma avaliação

90 avaliações

Deixe uma resposta

O seu endereço de email não será publicado.