Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

35
Práticas de Desenvolvimento de Software Aula 7. 06/04/2015. Oferecimento Aula 7 - Tratamento de exceção; Testes automatizados

Transcript of Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

Page 1: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

Práticas de Desenvolvimento de Software

Aula 7. 06/04/2015.

Oferecimento

Aula 7 - Tratamento de exceção; Testes automatizados

Page 2: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

Parte 1 Tratamento de exceção

Page 3: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

Definição

Tratamento de exceção é um mecanismo existente em algumas linguagens de programação para sinalizar e tratar a ocorrência de falhas que

acontecem durante a execução de um programa.

Tratamento de exceção

Observação: tratamento de exceção também existe em hardware, mas o foco desta aula é o uso deste mecanismo em software.

Page 4: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

Tipos de falhas (1)

Tipos de falhasErros no uso de uma biblioteca

# HTTP client API for Ruby

require 'net/http'

 

# Try to download Google's home page

Net::HTTP.get('http://www.google.com')

# NoMethodError: undefined method `hostname' for "http://www.google.com":String

# from /Users/deborasetton/.rvm/ruby/2.2.0/net/http.rb:478:in `get_response'

 

# Try again, with URI object instead of String

uri = URI('http://www.google.com')

Net::HTTP.get(uri) # => String

Exemplos adaptados de: http://avdi.org/talks/exceptional-ruby-2011-02-04/

Page 5: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

Tipos de falhas (2)

Situações não-previstas

if (value.is_a?(String))  # We can deal with a stringelsif (value.is_a?(Array))  # We can deal with an array tooelse  # Uh oh... I don't know what to do here.end

print 'Type a number N from 1 to 10: 'number = gets.chomp.to_i # User types: "I don't want to"puts "(100 % N) is: #{100 % number}"# ZeroDivisionError: divided by 0# from (pry):9:in `__pry__'

Tipos de falhas

Exemplos adaptados de: http://avdi.org/talks/exceptional-ruby-2011-02-04/

Page 6: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

Tipos de falhas (3)

Erros de programação

arr = [1, 2, 3]

puts "The last element incremented by 1 is: #{arr[3] + 1}"

# NoMethodError: undefined method `+' for nil:NilClass

# from (pry):2:in `__pry__'

Falhas em recursos externos

Net::HTTP.get('http://www.google.com')

# Net::ReadTimeout: Net::ReadTimeout

# from /Users/deborasetton/.rvm/ruby/2.2.0/net/protocol.rb:158:in `rescue in rbuf_fill'

Tipos de falhas

Exemplos adaptados de: http://avdi.org/talks/exceptional-ruby-2011-02-04/

Page 7: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

Ocorrência de falha durante a execução

ProgramaBibilioteca 1

Bibilioteca 1

ruby app.rb

$ ruby app.rb /Users/deborasetton/Dev/github/infosimples/praticas-devsoft-2015-01/week7/code/lib2.rb:14:in `get_value': undefined method `+' for nil:NilClass (NoMethodError) from /Users/deborasetton/Dev/github/infosimples/praticas-devsoft-2015-01/week7/code/lib1.rb:10:in `convert' from /Users/deborasetton/Dev/github/infosimples/praticas-devsoft-2015-01/week7/code/example.rb:6:in `start' from /Users/deborasetton/Dev/github/infosimples/praticas-devsoft-2015-01/week7/code/example.rb:2:in `run' from /Users/deborasetton/Dev/github/infosimples/praticas-devsoft-2015-01/week7/code/example.rb:17:in `<main>'

… get_value()

start()

convert()

Resultado da execução:

Page 8: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

Exceção (1)

If code in one routine encounters an unexpected condition that it doesn't know how to handle, it throws an exception,

essentially throwing up its hands and yelling "I don't know what to do about this–I sure hope somebody else knows how

to handle it!

Exceção

Steve McConnel — Code Complete

Sinaliza que uma falha ocorreu e interrompe o fluxo normal de execução

I don't know what to do about this–I sure hope somebody else

knows!

Page 9: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

Exceção (2)

• A execução pode ser continuada apenas se alguma rotina da pilha de execução (call stack) conseguir lidar com essa falha, ou "tratar a exceção". Caso contrário, o programa termina.

• O termo "exceção" pode se referir tanto ao evento excepcional que aconteceu quanto à estrutura de dados que contém informações sobre esse evento. As informações básicas normalmente acessíveis são:

• Tipo do erro • Mensagem de erro • Stack trace (mostra o caminho percorrido pelo programa até chegar

ao ponto em que a exceção foi "lançada")

Sinaliza que uma falha ocorreu e interrompe o fluxo normal de execução

Exceção

Page 10: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

Tratando uma exceção — Sintaxe em Ruby (1)

1 begin

2   # Do something

4 rescue SomeError

5   # This is executed when a SomeError exception

6   # is raised

7 rescue AnotherError => error

8   # Here, the exception object is referenced from the

9   # `error' variable

10 rescue

11   # This catches all exceptions derived from StandardError

12   retry # This executes the begin section again

13 else

14   # This is executed only if no exceptions were raised

15 ensure

16   # This is always executed, exception or not

17 end

Tratando uma exceção: sintaxe em Ruby

Adaptado de: http://en.wikipedia.org/wiki/Exception_handling_syntax

Page 11: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

1 begin2   # O `begin` inicia um bloco de código que é considerado "arriscado",3   # isto é, sabe-se que algum ponto desse bloco poderá falhar e lançar uma4   # exceção. O código começa a ser executado normalmente e, se tudo correr bem,5   # continua até o final desse bloco.6 rescue SomeError7   # Esse bloco só será executado se acontecer uma exceção no bloco anterior e,8   # além disso, apenas se a exceção for do tipo (classe) SomeError.9 rescue AnotherError => error

10   # Esse bloco só será executado se a exceção for do tipo AnotherError.11   # Além disso, aqui nós temos acesso aos dados da exceção através do objeto12   # `error`.13 rescue14   # Esse bloco será executado se alguma exceção for lançada de um tipo que não15   # seja nem SomeError nem AnotherError.16   # Em outras palavras, esse bloco "trata" todas as exceções não capturadas em17   # blocos anteriores (e que sejam derivadas de StandardError).18  19   # Em um bloco "rescue", é possível usar "retry" para tentar novamente.20   retry21 else22   # Esse bloco só será executado em caso de sucesso23 ensure24   # Esse bloco será executado sempre (em caso de sucesso ou de erro) e por25   # último.26 end

Tratando uma exceção — Sintaxe em Ruby (2)

Tratando uma exceção: descrição

Page 12: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

Exceções podem ser tratadas em qualquer ponto da call stack

ProgramaBibilioteca 1

Bibilioteca 1

ruby app.rb

… get_value()

start()

convert()

rescue ErrorX

rescue ErrorYrescue ErrorZ

• Quando uma exceção é lançada, o programa realiza uma busca na call stack, tentando encontrar um exception handler que declare saber tratar exceções daquele tipo.

• Isso significa que exceções podem ser tratadas em qualquer ponto da call stack, e não apenas pelo último método chamado.

• A ideia é que cada ponto do código fique responsável por tratar o erro da maneira como for melhor para o seu contexto, e só trate os erros que fizer sentido tratar.

Page 13: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

Lançando uma exceção — Sintaxe em Ruby

Lançando uma exceção: sintaxe em Ruby

# Por padrão, a exceção é do tipo RuntimeError

raise 'An error just happened!'

 

# Você também pode dizer qual o tipo a ser usado (nesse caso, ArgumentError)

raise ArgumentError, 'Variable "arr" should be an Array, but is a Hash'

• O ponto de lançamento de uma exceção e o ponto de tratamento de uma exceção podem ser muito "distantes". Exemplo: uma biblioteca de tratamento de imagens usada pelo seu programa lança uma exceção que precisará ser tratada por você.

• O tipo da exceção deve ser escolhido de acordo com o significado da falha que aconteceu

• Os tipos de exceção normalmente têm uma hierarquia entre eles

Page 14: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

Hierarquia de exceções em Ruby

Hierarquia de exceções em Ruby

Exception

NoMemoryError

ScriptError

LoadError

NotImplementedError

SyntaxError

SecurityError

StandardError

ArgumentError

EncodingError

IOError

NameError

NoMethodError

RegexpError

RuntimeError

ThreadError

TypeError

ZeroDivisionError

SystemExit

fatal

Algumas classes na hierarquia de exceções de Ruby

Você pode criar outros tipos de erros específicos para sua biblioteca, API, etc — é só definir classes que herdam das classes ao lado.

# Definição de um novo tipo

class Geometry::NameError < NameError

end

 

# Mais tarde, em algum ponto do código...

raise Geometry::NameError, "Unknown polygon: Manysides-agon"

Adaptado de: http://blog.nicksieger.com/articles/2006/09/06/rubys-exception-hierarchy/

Page 15: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

Mais sobre tratamento de exceção

• Tratar uma exceção deve se limitar ao mínimo de operações necessárias para

recuperar o estado do programa ou encerrá-lo de maneira adequada • Evite, dentro de um rescue, relançar outra exceção, de outro tipo, mudando o erro

original — isso pode mascarar a verdadeira causa do problema • Evite lançar exceções quando a situação de erro for previsível (exemplo: sempre

validar a entrada fornecida pelo usuário antes de passá-la para frente) • O abuso de exceções pode tornar o código mais difícil de compreender, porque os

caminhos possíveis para o programa não são óbvios • Alternativas existentes ao mecanismo de exceções: normalmente, códigos de erro • Diferentes maneiras de responder a uma falha: retornar um valor que indica erro

(normalmente nulo), retornar uma resposta "falsa" (fake) mas que permite a

continuação do programa, simplesmente repassar o erro, etc.

Tratamento de exceçãoRecomendações e reflexões

Page 16: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

raise "Out of time — next part, please!"

Page 17: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

Parte 2 Testes automatizados

Page 18: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

Automação de testes (1)

Refere-se ao uso de softwares especializados e à implementação de programas ou scripts (test suites)

que são usados para testar outros softwares.

Automação de testes

Page 19: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

Automação de testes (2)

Automação de testes

Bibliotecas ou frameworks de teste

Test suite: testes escritos especificamente para a

aplicação sob teste+• Exemplos: JUnit ( Java), RSpec e

Minitest (Ruby), Jasmine (JavaScript)

• São independentes da aplicação que será testada

• Normalmente escritos na mesma linguagem que a linguagem da aplicação sob teste

Page 20: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

• Detectar bugs no software, tanto com relação à implementação da funcionalidade

quando à especificação da funcionalidade em si

• Documentar a especificação do código (o que o código faz), incluindo edge-cases

• Garantir que o comportamento do programa não foi afetado "sem querer" entre

duas versões do código (testes de regressão)

• Testar com mais rapidez do que seria possível com pessoas, o que permite testar

com mais frequência e detectar e corrigir erros mais rapidamente

• Testar de forma mais barata — muitos tipos de teste podem ser automatizados com

facilidade

• Permitir que o código seja alterado ou experimentado com mais segurança

• Alcançar um nível de releases frequentes e praticamente automatizada (ver o

conceito de integração contínua)

Por que usar testes automatizados?

Testes automatizados — Vantagens

Page 21: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

• Testes automatizados não permitem afirmar que um programa sempre irá funcionar

independentemente das suas entradas ou condições de execução, mas permitem

afirmar com quais entradas e em quais condições o programa funciona

• A cobertura da suite de testes (test coverage) é uma métrica que mede a quantidade

de código testada, exercitada ou coberta pela suite de testes

• Requisitos não-funcionais (usabilidade, estabilidade, segurança, desempenho, etc)

normalmente são mais difíceis de testar

• Assim como é possível especificar um software em diferentes níveis de abstração,

também é possível testar um software em diferentes níveis e sob diferentes aspectos

• Teste caixa-preta x caixa-branca

Mais sobre testes

Testes automatizados

Page 22: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

Testes unitários

Testes de integração

Testes de aceitação

Níveis de testeDivisão normalmente aceita: 4 níveis

Testes fim-a-fim

• Verificam a funcionalidade das menores unidades que compõem o software • Em programas orientados a objeto, a unidade básica normalmente é a classe • A ideia é testar as unidades isoladamente e garantir que elas funcionam de forma

independente antes de considerar as conexões entre elas

Níveis de teste (1)

Page 23: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

Testes unitários

Testes de integração

Testes de aceitação

Testes fim-a-fim

• Têm como objetivo testar as interfaces e interações entre os componentes • Não faz parte do escopo destes testes a integração com outros sistemas

Níveis de testeDivisão normalmente aceita: 4 níveis

Níveis de teste (2)

Page 24: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

Testes unitários

Testes de integração

Testes de aceitação

Testes fim-a-fim

• Também são chamados de testes de sistema • O objetivo é testar o software do ponto de vista do usuário final, normalmente

testando um caso de uso. Exemplo: após fazer login pela primeira vez, o usuário

deve ser redirecionado para a tela de configuração de conta.

Níveis de testeDivisão normalmente aceita: 4 níveis

Níveis de teste (3)

Page 25: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

Testes unitários

Testes de integração

Testes de aceitação

Testes fim-a-fim

• Normalmente são realizados no ambiente final de operação do sistema • São usados para verificar se o software atende aos critérios de aceitação

inicialmente acordados

Níveis de testeDivisão normalmente aceita: 4 níveis

Níveis de teste (4)

Page 26: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

• Modelo tradicional de desenvolvimento (waterfall) • A fase de teste é realizada após o término da fase de desenvolvimento, por uma equipe

independente da equipe de desenvolvimento • Crítica: a fase de testes acaba sendo usada como um buffer para compensar atrasos de

fases anteriores, comprometendo os testes • Modelo ágil

• Os testes são escritos em paralelo com o restante do código da aplicação • No modelo test-driven development (TDD), os testes são escritos antes do código, de

maneira a guiar (drive) o desenvolvimento • As suites de teste são continuamente atualizadas para incluir novos casos de teste, e se

tornam os testes de regressão da aplicação • Crítica: este modelo aumenta o esforço dedicado ao desenvolvimento e manutenção de

testes

Modelos de desenvolvimentoAs diferentes formas de enxergar o papel dos testes

Modelos de desenvolvimento

Page 27: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

• RSpec: biblioteca open-source de testes para Ruby • A ideia é construir especificações executáveis do seu código • Divisão em 4 partes:

• rspec-core: http://rspec.info/documentation/3.2/rspec-core/ • rspec-expectations: http://rspec.info/documentation/3.2/rspec-expectations/ • rspec-mocks (não vamos usar hoje) • rspec-rails (não vamos usar hoje)

Testes em Ruby com RSpec

Testes em Ruby com RSpec

Page 28: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

RSpecEstrutura básica de um arquivo de teste (something_spec.rb)

RSpec.describe Stack do

  describe '#pop' do

    context 'when the stack is empty' do

      it 'raises EmptyStack exception' do

        # Actual test goes here

      end

    end

    context 'when the stack is not empty' do

      it 'returns the first element of the stack' do

        # Actual test goes here

      end

    end

  end

end

• it, specify e example são usados para declarar exemplos de como o seu código deve funcionar

• São sinônimos, você pode usar a palavra que permite a leitura melhor do código

• describe e context (sinônimos) são usados para agrupar e aninhar exemplos.

• Grupos de exemplos tornam a suite de testes mais organizada e fácil de entender

RSpec — estrutura básica

Page 29: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

RSpecExecução de um arquivo de teste

$ rspec stack_spec.rb --format documentation --color

 

Stack

  #pop

    when the stack is empty

      raises EmptyStack exception

    when the stack is not empty

      returns the first element of the stack

 

Finished in 0.00114 seconds (files took 0.11435 seconds to load)

2 examples, 0 failures

Os testes funcionam como uma documentação executável

RSpec — exemplo de saída de sucesso

Page 30: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

RSpecExecução de um arquivo de teste com falhas

$ rspec stack_spec.rb --format documentation --color Stack  #pop    when the stack is empty      raises EmptyStack exception (FAILED - 1)    when the stack is not empty      returns the first element of the stack Failures:   1) Stack#pop when the stack is empty raises EmptyStack exception     Failure/Error: expect {}.to raise(EmptyStack)     EmptyStack:       EmptyStack     # ./stack_spec.rb:52:in `block (4 levels) in <top (required)>' Finished in 0.00133 seconds (files took 0.39144 seconds to load)2 examples, 1 failure Failed examples: rspec ./stack_spec.rb:51 # Stack#pop when the stack is empty raises EmptyStack exception

RSpec — exemplo de saída com falhas

Page 31: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

RSpec Expectations

• O módulo de expectations do RSpec (RSpec::Expectations) contém diversos matchers que permitem expressar, dentro de um exemplo, as saídas esperadas do programa

• Os matchers definidos pelo RSpec estão documentados neste link: http://rspec.info/documentation/3.2/rspec-expectations/

• Também é possível definir matchers customizados

RSpec Expectations

Contém matchers para implementar os testes

Page 32: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

RSpec ExpectationsAlguns exemplos de matchers

# Equivalence

expect(actual).to eq(expected)   # passes if actual == expected

 

# Comparisons

expect(actual).to be > expected  # also >=, <= and <

 

# Regular expressions

expect(actual).to match(/expression/)

 

# Types/classes

expect(actual).to be_a(expected) # passes if actual.is_a?(expected)

 

RSpec Expectations — Exemplos de matchers (1)

Page 33: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

RSpec ExpectationsAlguns exemplos de matchers

# Truthiness

expect(actual).to be_truthy   # passes if actual is truthy (not nil or false)

expect(actual).to be_falsy    # passes if actual is falsy (nil or false)

expect(actual).to be_nil      # passes if actual is nil

expect(actual).to_not be_nil  # passes if actual is not nil

 

# Expecting errors

expect { ... }.to raise_error

expect { ... }.to raise_error(ErrorClass)

 

# Collection membership

expect(actual).to include(expected)

expect(actual).to start_with(expected)

expect(actual).to end_with(expected)

RSpec Expectations — Exemplos de matchers (2)

Page 34: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

RSpec.describe 'Exceptions in Ruby' do

  specify '`raise` without a class raises RuntimeError' do

    expect { raise "Raising an error" }.to raise_error(RuntimeError)

  end

 

  specify 'RuntimeError inherits from Exception' do

    expect(RuntimeError.ancestors).to include(Exception)

  end

 

  class SomeError < RuntimeError; end

 

  specify 'you can create a new error type' do

    expect { raise SomeError, 'This is my new type' }.to(

      raise_error(SomeError))

  end

 

  specify 'exception objects carry call stack information with them' do

    begin

      raise SomeError, "I'm raising an error just to test stuff"

    rescue SomeError => ex

      expect(ex.backtrace).to be_a(Array)

    end

  end

end

RSpec — Um exemplo completo

Page 35: Tratamento de Exceção e Testes Automatizados [Práticas de Desenvolvimento de Software]

Fonte: http://xkcd.com/1319/

Automation