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.
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
.
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.
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.
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 |
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 |