Python unittesti täielik juhend: põhialust rakendusteni

1. Mis on Python unittest?

unittest on Pythoni standardraamatukogus sisalduv üksustestimise raamistik, mis on oluline tööriist koodi kvaliteedi tagamiseks. See võimaldab arendajatel testida koodi iga osa eraldi ja avastada vead varakult. Samuti aitab see pideva arenduse käigus veenduda, et koodi muudatused ei riku olemasolevaid funktsioone.

Üksustestimise tähtsus

Kood muutudes keerukamaks, muutub järjest keerulisemaks kontrollida, kas erinevad osad töötavad õigesti koos. Üksustestide kasutuselevõtt aitab vältida ootamatuid vigu väikeste muudatuste tõttu ja säilitada kogu programmi stabiilsust.

2. unittesti põhiline kasutamine

unittesti põhiline on luua klass, mis pärib unittest.TestCasei, ning määratleda selles testmeetodid. Testmeetodites kasutatakse selliseid väite meetodeid nagu assertEqual(), et võrrelda oodatud tulemust tegeliku tulemusega.

Põhiline testinäide

Järgmine kood on lihtne näide add(a, b) funktsiooni testimiseks.
import unittest

# Testitav kood
def add(a, b):
    return a + b

# Testiklass
class TestAddFunction(unittest.TestCase):

    def test_add_integers(self):
        result = add(2, 3)
        self.assertEqual(result, 5)

if __name__ == '__main__':
    unittest.main()
Selles koodis testitakse, kas add() funktsioon töötab õigesti. assertEqual() meetod kontrollib, et oodatav väärtus ja tegelik tulemus on võrdsed. Sel viisil saab kinnitada, et funktsioon töötab õigesti mitmes olukorras.

Testi laiendamine

Saab kasutada mitut testmeetodit, et testida funktsiooni käitumist erinevate sisendite korral. Näiteks on võimalik testida ujukomaarvude või stringide ühendamist.
def test_add_floats(self):
    result = add(2.5, 3.5)
    self.assertAlmost(result, 6.0, places=2)

def test_add_strings(self):
    result = add("Hello, ", "World!")
    self.assertEqual(result, "Hello, World!")
Nii saab testides funktsiooni käitumist erinevate andmetüüpide korral kinnitada, et funktsioon töötab õigesti erinevates olukordades.
RUNTEQ(ランテック)|超実戦型エンジニア育成スクール

3. setUp() ja tearDown() kasutamine

Testide enne ja pärast teatud toimingute automaatseks käivitamiseks kasutatakse setUp() ja tearDown() meetodeid. Sellega saab testide käivitamise eelnevalt teha vajalikud ettevalmistused ja testide lõpus teha puhastust.

setUp() näide

setUp() meetod kutsutakse alati enne iga testimeetodi käivitamist ning seda saab kasutada ühiste initsialiseerimistoimingute koondamiseks.
def setUp(self):
    self.temp_value = 42

tearDown() näide

tearDown() meetod käivitatakse iga testimeetodi järel ning see teostab järeltoiminguid ja ressursside vabastamist. Näiteks saab seda kasutada andmebaasiühenduse sulgemiseks või ajutiste failide kustutamiseks.
def tearDown(self):
    self.temp_value = None
Nii saab vähendada testkoodi üleküllust ja säilitada puhtamat koodi.

4. Sõltuvuste testimine mokkide abil

Kui testitav kood sõltub välistest ressurssidest (andmebaas, API jne), saab sõltuvuse osad asendada mokkidega, mis parandab testide käivitamise kiirust ja võimaldab ennustatavaid teste. Kui kasutada Python’i unittest.mock moodulit, on see lihtne teostada.

Mokkide näide

Allolevas koodis asendatakse time_consuming_function() nimega pikaajaline funktsioon mokiga.
from unittest.mock import patch

class TestAddFunction(unittest.TestCase):

    @patch('my_module.time_consuming_function')
    def test_add_with_mock(self, mock_func):
        mock_func.return_value = 0
        result = add(2, 3)
        self.assertEqual(result, 5)
Selles näites kasutatakse mokki, et testida ilma time_consuming_function kutsumata. Sellega saab testimise aega lühendada ja saada täpseid tulemusi.

5. Erandite käsitlemine ja kohandatud väide

unittest-is on võimalik ka erandite käitlemist testida. Näiteks, et kindlaks teha, et erand tekib õigesti teatud olukorras, kasutatakse assertRaises().

Erandite käitlemise test

Järgnevas näites kontrollitakse, et ZeroDivisionError tekib.
def test_divide_by_zero(self):
    with self.assertRaises(ZeroDivisionError):
        divide(1, 0)
See kood testib, et divide(1, 0) kutsutud ajal tekib ZeroDivisionError.

Kohandatud väite loomine

Kui standardne väide ei kata teatud juhtumeid, saate luua oma kohandatud väite meetodi.
def assertIsPositive(self, value):
    self.assertTrue(value > 0, f'{value} is not positive')
Kohandatud väite kasutamisega saate toetada spetsiifilisemaid teststsenaariume.

6. unittesti testide avastamise funktsioon

unittest testide avastamise funktsiooni kasutades saab projekti sees olevad kõik testfailid automaatselt leida ja käivitada. See funktsioon on eriti kasulik suurte projektide puhul.

Testide avastamise kasutamine

Testide avastamise käivitamiseks kasutatakse järgmist käsku.
python -m unittest discover
Selle tulemusena käivitatakse kõik määratud kataloogis olevad test_*.py failid. Kui soovite määrata failid või kataloogid, kasutage järgmist valikut.
python -m unittest discover -s tests -p "test_*.py"
Selle funktsiooni kasutamisega saate vältida testifailide eraldi määramise vaeva ja hallata teste tõhusalt ka suurtes projektides.

7. unittesti kasutamise näpunäited jõudluse parandamiseks

Kui testide käivitamise kiirus on aeglane, väheneb arendustõhusus. Siin tutvustame mõningaid näpunäiteid, kuidas parandada unittesti kasutavate testide jõudlust.

Faili I/O optimeerimine

Testid, mis nõuavad failide lugemist ja kirjutamist, saab kiirendada, kui need töötavad mälus. StringIO abil saab luua objektid, mis käituvad mälus nagu failid, vältides kettal I/O-d.
from io import StringIO

class TestFileOperations(unittest.TestCase):

    def test_write_to_memory(self):
        output = StringIO()
        output.write('Hello, World!')
        self.assertEqual(output.getvalue(), 'Hello, World!')
Selle meetodiga saab isegi testide, mis vajavad failidele juurdepääsu, kiirust märkimisväärselt parandada.

Mokkide kasutamine

Mokkide kasutamine võimaldab testide kiirust suurendada, vähendades väliste ressursside juurdepääsu. See aitab vältida võrgu ja andmebaasi viivitusi ning lühendada testide käivitusaega. Järgnevas näites asendatakse API-kõne mokiga.
from unittest.mock import MagicMock

class TestApiCall(unittest.TestCase):

    def test_api_response(self):
        mock_api = MagicMock(return_value={'status': 'success'})
        response = mock_api()
        self.assertEqual(response['status'], 'success')
Nii saab funktsioone testida ilma väliste ressursside sõltuvuseta, luues kiirema ja stabiilsema testikeskkonna.

8. Kokkuvõte ja järgmised sammud

Selles artiklis käsitlesime põhialuseid Python’i unittest kasutades üksustestide tegemiseks, sealhulgas seadistuse ja lõpetamise (setup/teardown) kasutamist, mokkide abil sõltuvuste testimist ning tehnikaid, mis parandavad testide jõudlust.

Peamised punktid kokkuvõttes

  • Aluskasutus: unittest.TestCase pärandades ja väite meetodite kasutamisega testide loomiseks.
  • setUp() / tearDown(): ühiste toimingute haldamine testide enne ja pärast suurendab koodi taaskasutatavust ja loetavust.
  • Mokkide kasutamine: võimaldab funktsioone testida ilma väliste ressursside sõltuvuseta, mis suurendab testimise tõhusust märkimisväärselt.
  • Testide avastamine: kasulik funktsioon, mis lihtsustab testide haldamist suurtes projektides.
  • Jõudluse parandamise tehnikad: mälus töötamine ja mokkide kasutamine võimaldavad testide käivitusaega vähendada.

Järgmised sammud

unittest põhialuste omandamisel proovi keerukamaid testimismeetodeid. Näiteks parameetrilised testid, mis võimaldavad korraga testida mitut sisendandmestikku, või koodikate tööriistade kasutamine testide ulatuse kontrollimiseks, tugevdavad kogu projekti testimisstrateegiat. Samuti on hea vaadata teisi testiraamistikke, nagu pytest, et laiendada valikuid vastavalt vajadustele. Testimine on arenduse oluline osa. Avasta vead varakult ja säilita koodi kvaliteet, võttes testimise aktiivselt kasutusele.