Next Previous Contents

2. Unit testing

Enhetstesting (eng: unit testing) er en testmetode hvor enkeltkomponenter testes hver for seg. Tanken er at når alle enkeltdelene er riktige, vil summen av delene også være riktig. Noen utviklingsmetodologier, slik som "eXtreme Programming" (XP), gjør utstrakt bruk av enhetstesting til å kvalitetssikre programvaren og utviklingsprosessen.

2.1 Test::Unit-rammeverket

Trekant

La oss anta at vi har en metode som regner ut arealet av en trekant.

  1| def trekant_areal( grunnlinje, hoyde )
  2|   grunnlinje * hoyde / 2
  3| end

Den enkleste måten å teste denne metoden vil være å kalle den. Så kan vi sammenligne forventet svar med faktisk svar.

  1|     assert_equal( 25, trekant_areal( 10, 5) )

Her bruker vi en metode som gjør sammenligningen for oss og gir oss en beskrivende tilbakemelding dersom resultatet fra metoden ikke stemmer med det vi forventer.

assert_equal-metoden er definert i biblioteket for enhetstesting så vi må pakke testen vår inn i et TestCase.

  1| require 'trekant'
  2| require 'test/unit'
  3| class TestTrekant < Test::Unit::TestCase
  4|   def test_trekant_areal
  5|     assert_equal( 25,  trekant_areal( 10, 5) )
  6|     assert_equal( 7.5, trekant_areal(  5, 3) )
  7|   end
  8| end

Når vi kjører denne koden får vi en oversikt som viser resultatet av testen. Hvor mange tester som ble kjørt, hvor mange kall til assert_*-metoder som ble gjort, hvor mange av disse feilet og om det eventuelt var noen uventede problemer.

Var du litt paff over at det ikke er noe kode som starter kjøringen av testene? De defineres jo bare som metoder i en klasse.

Rammeverket for enhetstesting gjør en del magiske triks ved hjelp av refleksjon. Den ser over alle klasser som arver fra TestCase, lager instanser og kjører alle metodene med navn som begynner på test.

En samling tester

La oss ta et større eksempel hvor vi har laget en liten klasse Vector som representerer tredimensjonale vektorer.

  1| # En klasse for tredimensjonal vektorer.
  2| class Vector
  3|   attr_reader :x, :y, :z
  4|   def initialize( x, y, z )
  5|     @x, @y, @z = x, y, z
  6|   end
  7|   def +(other)
  8|     self.class.new( @x+other.x, @y+other.y, @z+other.z )
  9|   end
 10|   def -(other)
 11|     self.class.new( @x-other.x, @y-other.y, @z-other.z )
 12|   end
 13|   def *(other)
 14|     self.class.new( @x*other.x, @y*other.y, @z*other.z )
 15|   end
 16|   def to_a
 17|     [@x, @y, @z]
 18|   end
 19| end

Når vi så begynner å bruke denne klassen, så må det jo testes slik at vi kan være trygge på at den gjør det den skal. Vi skriver da et test-tilfelle som sjekker alle offentlige metoder i klassen.

  1| require 'vector'
  2| require 'test/unit'
  3| 
  4| class TestVector < Test::Unit::TestCase
  5|   def setup
  6|     @v1 = Vector.new(1,2,3)
  7|     @v2 = Vector.new(2,3,6)
  8|   end
  9|   def test_accessor
 10|     assert_equal( 1, @v1.x )
 11|     assert_equal( 2, @v1.y )
 12|     assert_equal( 3, @v1.z )
 13|   end
 14|   def test_to_array
 15|     assert_equal( [1,2,3], @v1.to_a )
 16|     assert_equal( [2,3,6], @v2.to_a )
 17|   end
 18|   def test_plus
 19|     v = @v1 + @v2
 20|     assert_equal( [3,5,9], v.to_a )
 21|   end
 22|   def test_minus
 23|     v = @v2 - @v1
 24|     assert_equal( [1,1,3], v.to_a )
 25|   end 
 26|   def test_dot
 27|     v = @v1 * @v2   
 28|     assert_equal( [2,6,18], v.to_a )
 29|   end
 30| end

Disse testene kan da legges sammen med testene for andre klasser og moduler. Da får vi ett testprogram, med ett resultat som må sjekkes, uansett hvor stort systemet vi utvikler har blitt og hvor mange tester der er.

 

2.2 Svartboks- og hvitbokstesting

Svartbokstesting (eng: black box testing) er testing hvor man ikke bekymrer seg om hva som foregår inni enheten som testes. Man bekymrer seg bare om innput og utput, det offentlige grensesnittet.

Hvitbokstesting tar hensyn til det som foregår inni enheten som testes. Man bruker hvitbokstesting når man f.eks. forsøker å teste alle utføringsstiene (eng: execution paths) til enheten.

Ruby og hvitbokstesting

Gitt at vi har en liten klasse som søker etter forekomster av tekst i en større streng.

  1| class Finn
  2|   def initialize( tekst )
  3|     @tekst = tekst
  4|     @indeks = 0
  5|   end
  6|   def neste( soeketekst )
  7|     posisjon = @tekst.index( soeketekst, @indeks )
  8|     @indeks = if posisjon then posisjon + soeketekst.size else @indeks end
  9|     posisjon
 10|   end
 11| end
Den interne indeksen angir hvor neste søk skal fortsette fra, som jo blir første bokstav etter den vi har funnet.

  1| require 'hvitboks'
  2| require 'test/unit'
  3| 
  4| class TestFinn < Test::Unit::TestCase
  5|   def setup
  6|     @finn = Finn.new("Å være eller ikke være...")
  7|   end
  8|   def test_neste
  9|     assert_equal(  2, @finn.neste( "være" ) )
 10|     assert_equal(  6, hent_intern_indeks    )
 11|     assert_equal( 18, @finn.neste( "være" ) )
 12|     assert_equal( 22, hent_intern_indeks    )
 13|     assert_nil( @finn.neste("e") )
 14|   end
 15|   def hent_intern_indeks
 16|     @finn.instance_eval do
 17|       @indeks
 18|     end
 19|   end
 20| end


Next Previous Contents