Kysymys yksikkötestauksesta ja siihen liittyvästä koodin faktoroinnista. Asia liittyy läheisesti
tähän SO:n kysymykseen ja erityisesti
tämä kommentti kiteyttää huolenaiheeni.
Oletetaan, että jokin ulkopuolinen taho (esim. asiakasyritys) tarjoaa kirjaston tai API:n, jonka ainoa sisältö on Downloader-luokka, jonka julkiseen rajapintaan kuuluu ainoastaan download-funktio. Tämä funktio ottaa argumentikseen listan tiedostotunnisteita ja palauttaa yhden taulukon tai objektin, johon nämä pyydetyt tiedostot on jotenkin koottu. Oletetaan, että palautetusta objektista on aina mahdollista erotella yksittäisten tiedostojen sisällöt, joskaan loppukäyttäjä ei tällaisesta erottelusta ole kiinnostunut. Voidaan vaikka ajatella, että D=Downloader(); D.download(["kissa", "koira"]); palauttaa kuvan, jossa on kissan ja koiran kuvat laitettu peräkkäin yhdeksi isoksi kuvaksi.
Downloader on äärimmäisen hidas, kallis ja huono, eikä muokattavissa. Samat tiedostotunnisteet toistuu usein kun download-funktiota kutsutaan, joten helpotan tilannetta koodaamalla välimuistia hyödyntävän FastReader-luokan. Tämän luokan julkinen rajapinta koostuu read-funktiosta, jonka input ja output on täsmälleen samat kuin em. download-funktiossa. Pseudokoodi voisi näyttää vaikka tältä:
Koodi:
function read(list):
N = length(list);
images = image_array(N);
for index in 1...N:
if list[index] is in self.cache:
images[index] = self.read_cache(list[index]);
list.erase(index);
end if
end for
if list is not empty:
if self.D is uninitialized
self.D = Downloader(); # only when necessary
end if
remote_data = self.D.download(list);
remote_images = self.split_images(remote_data);
self.write_cache(list, remote_images);
images.replace_uninitialized_values(remote_images);
end if
return self.merge_images(images);
Ja yksikkötestaus voisi näyttää tältä:
Koodi:
function test_not_in_cache():
FR = FastReader();
result = FR.read(["kissa", "koira"]);
assertSameImage(result, "kissakoira.jpg"]);
function test_exists_in_cache():
FR = FastReader();
result1 = FR.read(["kissa", "koira"]);
result2 = FR.read(["kissa", "koira"]);
assertSameImage(result2, "kissakoira.jpg"]);
function test_partial_cache():
FR = FastReader();
result1 = FR.read(["kissa", "kala"]);
result2 = FR.read(["kissa", "koira"]);
assertSameImage(result2, "kissakoira.jpg"]);
Yksikkötestauksen ongelmana on, että Downloader-konstruktorin kutsu maksaa miljoona dollaria, eikä kaikilla FastReader-luokan kehittäjillä välttämättä ole Downloaderia käytössä lainkaan. Toisaalta kissan, koiran ja kalan tapauksessa kuvat tiedetään, joten olisi helppoa tehdä DownloaderMock-luokka, johon on hard-koodattu nämä kolme eläintä ja jota voisi siten hyödyntää FastReader-luokan yksikkötestauksessa. Mutta siitä päästäänkin tuohon SO:n ongelmaan, eli mock-luokkaa (en tiedä mitä on suomeksi) ei tässä voikaan käyttää, koska D on FastReaderin private-muuttuja. Keksin kyllä purkkaratkaisuja (esim. käyttää jotain kielikohtaisia erikoisominaisuuksia, tai sitten nimeää mock-luokan identtisesti varsinaisen luokan kanssa ja muuttaa testauksen ajaksi kirjastojen hakupolkuja siten, että mock-luokka "peittää" oikean luokan), mutta ne ei tunnu oikeilta.
Missä kohtaa suunnittelu meni pieleen?