Γλώσσες Προγραμματισμού Εφαρμογών - ΜΕΠΒ20 Διάλεξη 10 Δοκιμή του Κώδικα Παπαϊωάννου Αθανάσιος Π.Μ.Σ. «Εφαρμοσμένη Πληροφορική» Χειμερινό Εξάμηνο 20 16-20 17
Ορίσματα στο main module $ python test.py arg1 arg2 arg3 Το module sys της Python παρέχει πρόσβαση σε κάθε όρισμα που δίδεται από τη γραμμή εντολών σε ένα module μέσω της sys.argv Λίστα ορισμάτων sys.argv[0] είναι το όνομα του module που καλείται Αντίστοιχο με το args στη Java len(sys.argv) είναι το μήκος της λίστας ορισμάτων import sys print 'Number of arguments:', len(sys.argv), 'arguments.' print 'Argument List:', str(sys.argv) $ python test.py arg1 arg2 arg3 Number of arguments: 4 arguments. Argument List: ['test.py', 'arg1', 'arg2', 'arg3']
getopt Το module getopt βοηθά στην επεξεργασία επιλογών και ορισμάτων στη γραμμή εντολών Αυτό γίνεται με τη συνάρτηση getopt(args, options[, long_options]) options: Συμβολοσειρά γραμμάτων για πέρασμα επιλογών. Αν μια επιλογή ακολουθείται από τιμή τότε το αντίστοιχο γράμμα θα πρέπει να ακολουθείται από (:) long_options: Λίστα συμβολοσειρών επιλογών. Αν μια επιλογή ακολουθείται από τιμή τότε το αντίστοιχο γράμμα θα πρέπει να ακολουθείται από (=) >>> import getopt >>> args = '-a -b -cfoo -d bar a1 a2'.split() >>> args ['-a', '-b', '-cfoo', '-d', 'bar', 'a1', 'a2'] >>> optlist, args = getopt.getopt(args, 'abc:d:') >>> optlist [('-a', ''), ('-b', ''), ('-c', 'foo'), ('-d', 'bar')] >>> args ['a1', 'a2'] Exceptiongetopt.GetoptError: Όταν υπάρχει λάθος στα ορίσματα
Η εντολή assert Η εντολή assert ελέγχει αν μια συνθήκη είναι αληθής κατά το χρόνο εκτέλεσης του προγράμματος Αν η συνθήκη είναι ψευδής, τότε το πρόγραμμα σταματά και παράγεται ένα AssertionError assert(number_of_players < 5) assert(number_of_players < 5), msg Σε περίπτωση AssertionError επιστρέφει το msg
Δοκιμή Κώδικα Έλεγχος Λαθών Η δοκιμή του κώδικά είναι σημαντική για την ανάπτυξη ποιοτικού λογισμικού Οι δοκιμές πρέπει να είναι εύκολες στη χρήση και επαναχρησιμοποιήσιμες Πρέπει να είναι ανεξάρτητες από τον κώδικα τον οποίο ελέγχουν δηλ. όταν ελέγχεται ο κώδικας πρέπει να ελέγχεται βάσει της λειτουργικότητάς του που περιγράφεται στο docstring και όχι βάσει του ίδιου του κώδικα που έχει γραφτεί Αυτό σημαίνει ότι ελέγχει κανείς τις αποκρίσεις του κώδικα προς το χρήστη βάσει κάποιας εισόδου
Δοκιμή Κώδικα Υπάρχουν 2 βασικά στοιχεία στη δοκιμή κώδικα: Το να σκεφτεί κανείς τι πρόκειται να ελέγξει Τα εργαλεία που πρόκειται να χρησιμοποιήσει για να «τρέξει» τις δοκιμές Το πρώτο είναι επί το πλείστον ανεξάρτητο από τη γλώσσα προγραμματισμού Αλλά όχι εντελώς, αφού μερικές γλώσσες μπορεί να επιτρέπουν διαφορετικούς τύπους εισόδου Το δεύτερο εξαρτάται αρκετά από τη γλώσσα προγραμματισμού Υπάρχει μια αρκετά κοινή μεθοδολογία για τη δοκιμή κώδικα, αλλά σε μερικές γλώσσες υπάρχουν ενσωματωμένα εργαλεία για το σκοπό αυτό
Περιπτώσεις Δοκιμών (1) Τι ελέγχουμε; Δεν μπορούμε να ελέγξουμε όλες τις πιθανές εισόδους Οπότε, θα πρέπει να διαλέξουμε ένα υποσύνολο εισόδων που θα είναι αντιπροσωπευτικό Μπορούμε να έχουμε «τυπικές εισόδους» Μπορούμε να ελέγξουμε περιπτώσεις όπου υποψιαζόμαστε πιθανό προγραμματιστικό λάθος Μπορούμε να ελέγξουμε «ακραίες περιπτώσεις» που υποψιαζόμαστε ότι μπορεί να έχουν παραβλεφθεί
Περιπτώσεις Δοκιμών (2) Είναι χρήσιμο να σκέφτεται κανείς«επιθετικά» όταν επιλέγει περιπτώσεις δοκιμών Δηλαδή, θα πρέπει να προσπαθήσει να κάνει το πρόγραμμά του να «κρεμάσει» Όμως «νόμιμα», δηλαδή σύμφωνα με τις επιτρεπτές εισόδους του προγράμματος του που περιγράφονται στο docstring Σε αυτό το πεδίο ορισμού, θα πρέπει να επιλέγει όσο πιο άσχημες περιπτώσεις μπορεί
Περιπτώσεις Δοκιμών (3) Όλες οι περιπτώσεις δοκιμών για ένα κομμάτι κώδικα πρέπει να είναι μεταξύ τους ανεξάρτητες Αυτό γιατί, θα πρέπει να είναι διαφωτιστικές σχετικά με την αιτία αποτυχίας της δοκιμής Οπότε, το να έχει κανείς πολλές περιπτώσεις δοκιμών που «χτίζουν» η μία πάνω στην άλλη δεν είναι και πολύ καλή ιδέα Σημειώστε ότι αυτό είναι διαφορετικό από το να έχουμε περιπτώσεις δοκιμών που ελέγχουν συναρτήσεις, οι οποίες καλούν άλλες συναρτήσεις
Τί ελέγχουμε; Ιδεατά, κάθε συνάρτηση ελέγχεται ξεχωριστά Αυτό αποκαλείται unit testing Όταν έχουν ελεγθεί όλες οι μικρότερες συναρτήσεις, τότε πραγματοποιέιται ο έλεγχος μεγαλύτερων συναρτήσεων που καλούν τις μικρότερες Όταν γίνουν κάποιες αλλαγές στον κώδικα, τότε θα πρέπει να ξαναγίνουν όλες οι δοκιμές (που μπορεί να τον αφορούν άμεσα ή έμμεσα) Αυτό αποκαλείται regression testing
Πότε ελέγχουμε τον κώδικά μας για λάθη; Το καλύτερο είναι ο έλεγχος μιας συνάρτησης να γίνεται αμέσως μετά το γράψιμό της Η διόρθωση σφαλμάτων είναι ευκολότερη στο σημείο αυτό Συχνά είναι χρήσιμο να σκέφτεται κανείς τις περιπτώσεις δοκιμών πριν ακομή γράψει κώδικα (δηλ. περιπτώσεις χρήσης, τυπικές, δύσκολες, ακραίες) Έτσι, θα σκεφτεί τη δομή του κώδικα της συνάρτησης και το τι κάνει η συνάρτηση καλά πριν γράψει κώδικα για τη συνάρτηση Με αυτόν τον τρόπο, μπορεί να γράψει κανείς τις δοκιμές κώδικα με έναν γενικό τρόπο ανεξαρτήτως κώδικα (black-box fashion), διότι δεν ξέρει ποιος θα είναι ο κώδικας της συνάρτησης ακόμη
Πότε ελέγχουμε τον κώδικά μας για λάθη? Οι επαγγελματίες προγραμματιστές συχνά γράφουν ελέγχους λαθών πριν να γράψουν κώδικα Το να σκέφτεται κανείς περιπτώσεις ελέγχων την ώρα της σχεδίασης του κώδικα αποτελεί χρήσιμο σχεδιαστικό εργαλείο, διότι μπορεί να ανατροφοδοτήσει τον σχεδιασμό Κάνει τον κώδικα πιο εύρωστο
Γενικές Απαιτήσεις για Δοκιμές Κώδικα Προτιμούμε ατομικές δοκιμές (unit tests) για κάθε συνάρτηση Οι δοκιμές αυτές θα πρέπει να είναι ανεξάρτητες μεταξύ τους Κάποιες θα πρέπει να είναι γενικές και κάποιες επιλεγμένες με «επιθετική» λογική Θέλουμε να σχεδιάζουμε τις δοκιμές μας πριν τη συγγραφή κώδικα Κάνει τον κώδικά μας πιο εύρωστο και βελτιώνει την ανεξαρτησία των δοκιμών Θέλουμε να επανεκτελούμε τις δοκιμές όταν αλλάζουμε τον κώδικα Πώς επιτυγχάνουμε τα παραπάνω;
Δοκιμή Κώδικα στην Python Έχουμε λοιπόν κάποιες απαιτήσεις για τη δοκιμή κώδικα Είναι δύσκολο να τις ικανοποιήσουμε όλες Μέχρι τώρα, τεστάρουμε τα modules ή τις συναρτήσεις μας στο διαδραστικό φλοιό Απαιτεί πολύ δουλειά για regression testing με αυτόν τον τρόπο Θα μπορούσαμε να αποθηκεύουμε τις προηγούμενες δοκιμές μας σε ένα αρχείο, αλλά έτσι θα έπρεπε να γράψουμε κώδικα για ανάγνωση, επεξεργασία και εγγραφή αρχείων Ευτυχώς, η Python έχει ένα module που αποκαλείται Nose που διαθέτει αρκετή από τη χρήσιμη λειτουργικότητα για regression testing
Δοκιμή Κώδικα με το Nose (1) Η δοκιμή κώδικα με το Nose γίνεται ως ακολούθως: Έστω ότι θέλουμε να εξέγξουμε ένα module που ονομάζεται mod Θέλουμε να δοκιμάσουμε κάποιες ή όλες τις συναρτήσεις μέσα σε αυτό Για να το κάνουμε αυτό, δημιουργούμε ένα module με όνομα test mod Σε αυτό το module κάνουμε import nose και import mod Για κάθε συνάρτηση func του mod που θέλουμε να τεστάρουμε, γράφουμε μια συνάρτηση test func()
Δοκιμή Κώδικα με το Nose (2) Όταν εκτελούμε το mod_test: if name == ' main ': nose.run () Εναλλακτικά, nosetests Στο σώμα της συνάρτησης test func() χρησιμοποιούμε εντολές assert Η assert (boolean condition) δε θα κάνει κάτι αν η συνθήκη είναι True, αλλά θα «πετάξει» μήνυμα λάθους αν είναι False Οπότε, η test func() έχει εντολές του τύπου: assert func(input) == (expected_output) Το Nose εκτελεί αυτές τις εντολές και παράγει αποτελέσματα δοκιμών
Δοκιμή Κώδικα με το Nose (3) Δημιουργούμε ταξεις δοκιμής (test classes) βάζοντάς τις είτε σε ένα module που καταλήγει σε (ή ξεκινά με) test ή _test (testmatch) ή κάνοντάς τις θυγατρικές της unittest.testcase Για κάθε μέθοδο που ταιριάζει στο testmatch, κατασκευάζεται μια περίπτωση δοκιμής με ένα νέο στιγμιότυπο της τάξης δοκιμής Μέθοδοι setup και teardown μπορούν να οριστούν που θα τρέχουν πριν και μετά κάθε μέθοδο Σε επίπεδο τάξης Σε επίπεδο μεθόδου class TestA(object): @classmethod def setup_class(klass): """This method is run once for each class before any tests are run""" @classmethod def teardown_class(klass): """This method is run once for each class _after_ all tests are run""" def setup(self): """This method is run once before _each_ test method is executed""" def teardown(self): """This method is run once after _each_ test method is executed""" def test_init(self): a = A() assert_equal(a.value, "Some Value") assert_not_equal(a.value, "Incorrect Value") def test_return_true(self): a = A() assert_equal(a.return_true(), True) assert_not_equal(a.return_true(), False)
Έξοδος του Nose (1) Η πρώτη γραμμή των αποτελεσμάτων μας λέει το αποτέλεσμα των δοκιμών Η τελεία σημαίνει το module υπό δοκιμή πέρασε τη δοκιμή με επιτυχία (χωρίς λάθη), το F σημαίνει ότι η δοκιμή απέτυχε, και το E ότι συνέβη κάποιο λάθος Το F συμβολίζει λανθασμένη έξοδο του κώδικα και το Ε σημαίνει ότι παρουσιάστηκε μια εξαίρεση στον κώδικα Για κάθε αποτυχία ή λάθος παρέχεται αντίστοιχη πληροφορία στην έξοδο του Nose Τέλος, παρέχεται ο συνολικός αριθμός των ελέγχων που πέρασε ο κώδικας με επιτυχία, ο συνολικός αριθμός των αποτυχιών και ο συνολικός αριθμός λαθών (εξαιρέσεων)
Έξοδος του Nose (2) Η πληροφορία για τα λάθη είναι απλώς αυτή που παρέχεται από την ίδια την Python Όταν αποτυγχάνει μια δοκιμή, έχουμε ένα 'AssertionError'. Αν θέλουμε να προσθέσουμε ένα δικό μας μήνυμα σε περίπτωση αποτυχίας μιας δοκιμής, μπορούμε να βάλουμε στην εντολή assert ως ακολούθως: assert (condition), "Some String."
Γενικές Απαιτήσεις για Δοκιμές Κώδικα (ξανά) Προτιμούμε ατομικές δοκιμές (unit tests) για κάθε συνάρτηση Οι δοκιμές αυτές θα πρέπει να είναι ανεξάρτητες μεταξύ τους Κάποιες θα πρέπει να είναι γενικές και κάποιες επιλεγμένες με «επιθετική» λογική Θέλουμε να σχεδιάζουμε τις δοκιμές μας πριν τη συγγραφή κώδικα Κάνει τον κώδικά μας πιο εύρωστο και βελτιώνει την ανεξαρτησία των δοκιμών Θέλουμε να επανεκτελούμε τις δοκιμές όταν αλλάζουμε τον κώδικα Πώς επιτυγχάνουμε τα παραπάνω με το Nose;
Τεστ με Nose Unit Tests Κάθε δοκιμή στο nose είναι μια ξεχωριστή συνάρτηση, οπότε πρέπει να γραφτεί μια νέα συνάρτηση για κάθε unit test Σχεδιασμός δοκιμών πριν τη συγγραφή κώδικα Αυτό που πρέπει να γράψουμε στο nose είναι οι προδιαγραφές της συνάρτησης που θα δοκιμάζουμε, ενώ ο κώδικας της ίδιας της συνάρτησης είναι μαύρο κουτί Regression Testing Το Nose επιτρέπει πολύ εύκολα να την επανεκτέλεση όλων των δοκιμών κώδικα όποτε θέλουμε
Αν βρεθεί λάθος Αν βρεθεί κάποιο λάθος, θα πρέπει να το διορθώσεις με ιχνηλάτηση λαθών, μια διαδικασία που είναι συχνά επίπονη Υπάρχουν τρόποι να γίνει λιγότερο οδυνηρή Δοκιμάστε τον κώδικά σας έγκαιρα και συχνά Διαβάσε τα μηνύματα λάθους, και χρησιμοποιήστε τα για να δείτε αν ο κώδικας σας είναι σωστός στο σημείο που εμφανίζεται το λάθος Βρείτε το πρώτο σημείο που ο κώδικας διαφέρει από αυτό που νομίζετε ότι θα έπρεπε να είναι Εκτελέστε νοητικά τον κώδικα, ώστε αν η εκτέλεση φτάσει στο σημείο του κώδικα που ελέγχετε, ο ελεγχόμενος κώδικας να κάνει αυτό που προορίζεται
Python Package Index (pip) Η Python αποτελεί έργο ανάπτυξης λογισμικού ανοιχτού κώδικα Διαθέτει ενεργή κοινότητα προγραμματιστών που διαθέτουν τον κώδικά τους σε άλλους χρήστες Αυτό επιτρέπει στους χρήστες της Python να επωφελούνται από λύσεις που παρείχαν άλλοι και να συνεισφέρουν τις δικές τους Στο Python Packaging Index (https://pypi.python.org/pypi) είναι διαθέσιμα (με άδειες ανοιχτού κώδικα) όλα τα πακέτα που έχουν συνεισφέρει άλλοι χρήστες Μπορούμε να εγκαταστήσουμε κάποιο από αυτά τοπικά: python -m pip install SomePackage Αν έχουμε εγκαταστήσει το pip (http://www.pip-installer.org/en/latest/), τότε pip install SomePackage
pyenv https://github.com/yyuu/pyenv Διατηρεί ταυτόχρονα πολλές ανεξάρτητες μεταξύ τους εκδόσεις Python σε ένα υπολογιστή Επιτρέπει στο χρήστη να αλλάξει τη χρησιμοποιούμενη έκδοση της Python συνολικά ή ανά project ή package (φάκελο)! Ο χρήστης μπορεί δυναμικά με μια μεταβλητή περιβάλλοντος να χρησιμοποιήσει όποια έκδοση της Python επιθυμει Επιτρέπει την ταυτόχρονη αναζήτηση εντολών από πολλές εκδόσεις Python Μερικές εντολές: pyenv versions pyenv install list pyenv install 3.4.0 pyenv global 3.4.0 pyenv local 3.5.2
Σύνοψη Ορίσματα στο πρόγραμμα Έλεγχος κώδικα για λάθη Unit testing Regression testing Δημιουργία δοκιμών με Nose Διόρθωση λαθών Εγκατάσταση νέων πακέτων Python Διατήρηση πολλών εκδόσεων της Python