Skip to the content.

6.6 Έλεγχος Ποιότητας κώδικα

© Γιάννης Κωστάρας


< Δ >

Εισαγωγή

Δεν αρκεί μόνο η συγγραφή κώδικα. Σύμφωνα με τις καλές τεχνικές προγραμματισμού θα πρέπει να μεριμνήσουμε και για την ποιότητα του κώδικά μας, γράφοντας κώδικα χωρίς λάθη (bugs), εφαρμόζοντας π.χ. την Αρχή DRY (Don’t Repeat Yourself) κλπ. Στο μάθημα αυτό θα δούμε εργαλεία και τεχνικές που μας βοηθούν στο σκοπό αυτό.

Unit Testing

Μια πολύ δημοφιλής τεχνική ελέγχου λαθών είναι το unit testing. Άλλες τεχνικές είναι η ανασκόπηση του κώδικά σας από κάποιον άλλο (peer review) κ.ά. Εδώ θα επικεντρωθούμε στο unit testing (ή έλεγχο μονάδας κώδικα).

Η τεχνική ξεκίνησε από τον Kent Beck ως μέρος της προτιμώμενης μεθοδολογίας του Extreme Programming (XP) μέρος της σχετικής μεθοδολογίας Test Driven Development (TDD). Με βάση αυτή τη μεθοδολογία, όταν έχουμε να επιλύσουμε κάποιο προγραμματιστικό πρόβλημα, δεν ξεκινάμε να γράφουμε κώδικα για το πρόβλημα, αλλά αντιθέτως γράφουμε ένα μικρό πρόγραμμα ελέγχου για να ελέγξουμε κατά πόσο το παραγόμενο αποτέλεσμα είναι σωστό. Φυσικά, καθώς δεν έχει γραφτεί ακόμα ο κώδικας, το πρόγραμμα ελέγχου αποτυγχάνει και ο προγραμματιστής θα πρέπει να γράψει τον κώδικα ώστε να κάνει τον πρόγραμμα ελέγχου να επιτύχει.

Η τεχνική ονομάζεται “έλεγχος μονάδας κώδικα” καθώς γράφουμε κώδικα ελέγχου στο επίπεδο της μεθόδου, δηλ. ελέγχουμε κάθε μέθοδο του προγράμματός μας αν συμπεριφέρεται όπως πιστεύουμε ή όπως μας το επιβάλλουν οι απαιτήσεις κλπ. Όταν μελλοντικά χρειαστεί να αλλάξετε κάποιες μεθόδους, ξανατρέχοντας τα unit tests βεβαιώνεστε ότι δε προσθέσατε νέα bugs.

Υπάρχουν διάφορες βιβλιοθήκες unit testing:

Πολλές φορές, για να μπορέσετε να ελέγξετε ένα τμήμα κώδικα χρειάζεται να αρχικοποιήσετε πολλές άλλες κλάσεις π.χ. για να επικοινωνήσετε με πόρους τους συστήματος, π.χ. με ΒΔ. Στο επίπεδο του testing δε χρειάζεται να φορτώνουμε κάθε φορά πόρους του συστήματος (εκτός κι αν θέλουμε να τους τεστάρουμε) καθώς τα unit tests πρέπει να εκτελούνται γρήγορα. Υπάρχουν για το σκοπό αυτό άλλα είδους testing frameworks που λέγονται mocking frameworks (ψευδείς δοκιμές). Αυτά κάνουν το πρόγραμμά μας να νομίζει ότι επικοινωνεί με τον πόρο ενώ στην πραγματικότητα επικοινωνεί με κλάσεις που συμπεριφέρονται σα να επικοινωνούσε το πρόγραμμά μας με τον πόρο. Τα πιο δημοφιλή είναι τα εξής:

Στη συνέχεια θα επικεντρωθούμε στην τελευταία έκδοση του JUnit.

JUnit 5

Η έκδοση 5 του JUnit είναι γραμμένη από την αρχή. Η αρχιτεκτονική του αποτελείται από 3 βασικά αρθρώματα (modules):

Απαιτεί έκδοση 8 της Java η νεώτερη.

Ας δούμε πώς μπορούμε να χρησιμοποιήσουμε το JUnit, γράφοντας unit tests στο NetBeans το οποίο από την έκδοση 10 υποστηρίζει JUnit 5.

Δημιουργήστε ένα νέο έργο Java Application στην κατηγορία Java with Maven και ονομάστε το Euler.

Σημείωση! Μέχρι τη στιγμή που γράφονται αυτές οι γραμμές, το JUnit 5 δεν υποστηρίζει πλέον το ant που είναι ένας από τους πιο διαδεδομένους τρόπους ‘χτισίματος’ για Java (το ant, όπως μάθαμε σε προηγούμενο μάθημα, είναι κάτι αντίστοιχο του make της C), αλλά μόνο Maven. Κατά συνέπεια, δημιουργώντας ένα έργο Java –> Java Application, που βασίζεται στο ant, τα unit tests δε θα δουλέψουν.

Δημιουργήστε μια νέα κλάση με όνομα Euler. Θα δοκιμάσουμε να επιλύσουμε μερικά από τα προβλήματα του Euler.

Το πρώτο πρόβλημα είναι το παρακάτω:

public class Euler {

    /**
     * Problem 1 <br/>
     * Multiples of 3 and 6. If we list all the natural numbers below 10 that
     * are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these
     * multiples is 23. Find the sum of all the multiples of 3 or 5 below
     * 1000.
     *
     * @param upperLimit 10 or 1000
     * @return the sum of multiples of 3 or 5 below {@code upperLimit}
     */
    public static long sumOf_3_5(int upperLimit) {
        return 0;
    }

}

Προτού υλοποιήσουμε τη στατική μέθοδο, ακολουθώντας το παράδειγμα του TDD, θα γράψουμε πρώτα το unit test γι’ αυτήν.

Επιλέξτε την κλάση Euler στο παράθυρο Projects, δεξί κλικ και επιλέξτε Tools –> Create/Update Tests. Θα εμφανιστεί το παράθυρο Create/Update Tests (βλ. Εικόνα 6.6.1).

Εικόνα 6.6.1 Διαλογικό παράθυρο Create/Update Tests του NetBeans

Πατώντας ΟΚ, το NetBeans θα δημιουργήσει την κλάση EulerTest μέσα στον κατάλογο Test Packages και θα προσθέσει τις βιβλιοθήκες του JUnit 5 μέσα στον κατάλογο Test Libraries.

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class EulerTest {
    
    public EulerTest() {
    }
    
    @BeforeAll
    public static void setUpClass() {
    }
    
    @AfterAll
    public static void tearDownClass() {
    }
    
    @BeforeEach
    public void setUp() {
    }
    
    @AfterEach
    public void tearDown() {
    }

    /**
     * Test of sumOf_3_5 method, of class Euler.
     */
    @Test
    public void testSumOf_3_5() {
        System.out.println("sumOf_3_5");
        int upperLimit = 0;
        long expResult = 0L;
        long result = Euler.sumOf_3_5(upperLimit);
        assertEquals(expResult, result);
        // TODO review the generated test code and remove the default call to fail.
        fail("The test case is a prototype.");
    }
}

Βλέπουμε ότι το NetBeans παρήγαγε μια μέθοδο testSumOf_3_5() με το annotation @Test που δηλώνει ότι αυτή είναι ένα unit test, και παρήγαγε και έναν σκελετό κώδικα για να μας βοηθήσει.

Πατήστε δεξί κλικ πάνω στο έργο Euler και επιλέξτε Test. Το NetBeans θα εκτελέσει όλα τα unit tests και φυσικά θα εμφανίσει Tests passed: 0,00%. Αν δεν εμφανιστεί το παράθυρο Test Results, εμφανίστε το από το μενού Window –> IDE Tools –> Test Results.

Με βάση την εκφώνιση του προβλήματος αλλάξτε το unit test όπως φαίνεται στη συνέχεια:

    @Test
    public void testSumOf_3_5() {
        System.out.println("sumOf_3_5");
        assertEquals(23, Euler.sumOf_3_5(10));
        //assertEquals(1, Euler.sumOf_3_5(1000));
    }

Η assertEquals() είναι ένας ισχυρισμός (assertion) όπου δέχεται ως πρώτο όρισμα τι αναμένεται ως αποτέλεσμα και ως δεύτερο το πραγματικό αποτέλεσμα που τεστάρει. Υπάρχουν φυσικά πολλά assertions όπως assertTrue(), assertNull() κ.ά. Υποστηρίζονται ακόμα και υποθέσεις (assumptions) δηλ. συνθήκες που πρέπει να ισχύουν για να έχει νόημα κάποιο test (π.χ. assumeTrue(), assumingThat()). Αν μια υπόθεση αποτύχει, δε σημαίνει ότι απέτυχε το test, αλλά ότι απλά το συγκεκριμένο test δεν έχει νόημα ή ότι δεν παρέχει καμιά χρήσιμη πληροφορία.

Φυσικά, αν ξανατρέξετε την εντολή Test (ή δεξί κλικ στην κλάση Euler και Test File), πάλι θ’ αποτύχει το unit test καθώς η μέθοδος επιστρέφει 0.

Ένα νέο χαρακτηριστικό του JUnit 5 είναι το σχόλιο μεταγλώττισης @DisplayName:

    @DisplayName("sumOf_3_5")
    @Test
    public void testSumOf_3_5() {
        assertEquals(23, Euler.sumOf_3_5(10));
        //assertEquals(1, Euler.sumOf_3_5(1000));
    }

Υλοποιώντας την sumOf_3_5():

    public static long sumOf_3_5(int upperLimit) {
        long sum = 0;
        for (int len = 3; len < upperLimit; len++) {
            if (len % 3 == 0 || len % 5 == 0) {
                sum += len;
            }
        }
        return sum;
    }

και ξανατρέχοντας το unit test, βλέπουμε ότι αυτή τη φορά η μπάρα έγινε πράσινη. Επιτυχία!

Βγάζοντας το σχόλιο από τη 2η γραμμή της testSumOf_3_5() μπορούμε να βρούμε το αποτέλεσμα και να ενημερώσουμε τη γραμμή ώστε να ξαναεπιστρέψουμε στις δάφνες της πράσινης μπάρας.

Άλλα χρήσιμα annotations:

@Disabled("test is skipped")

αν δεν επιθυμείτε να συμπεριλάβετε αυτόν τον έλεγχο.

Μπορείτε ακόμα να δημιουργήσετε ομάδες από ελέγχους με τη βοήθεια των @Tag και @Tags:

@Tag("core-test")
@Tags({@Tag("persistence-test"), @Tag("performance-test")})

Το NetBeans δημιούργησε και τις παρακάτω μεθόδους:

    @BeforeAll
    public static void setUpClass() {
    }
    
    @AfterAll
    public static void tearDownClass() {
    }
    
    @BeforeEach
    public void setUp() {
    }
    
    @AfterEach
    public void tearDown() {
    }

Οι @BeforeEach και @AfterEach εκτελούνται πριν και αμέσως μετά από κάθε @Test, π.χ. για ν’ αρχικοποιήσουν τα δεδομένα που χρησιμοποιούν οι μέθοδοι ελέγχου. Π.χ. αν κάθε έλεγχος χρειάζεται μια άδεια λίστα τότε:

    @BeforeEach
    public void setUp() {
    	items = new LinkedList<>();
    }
    
    @AfterEach
    public void tearDown() {
    }

Αντίστοιχα οι @BeforeAll και @AfterAll εφαρμόζονται σε στατικές μόνο μεθόδους και εκτελούνται μια φορά πριν την αρχή (μετά το τέλος) όλων των ελέγχων.

Η έκδοση 5 του JUnit υποστηρίζει και εμφωλιασμένους ελέγχους:

class Test {

	@Nested
	class NestedTest {
		//...
	}
}

Unit testing στο BlueJ

Η δημιουργία unit test κλάσεων στο BlueJ είναι πολύ εύκολη.

  1. Κάντε δεξί κλικ σε μια κλάση και επιλέξτε Create Test Class από το αναδυόμενο μενού.

Θα δημιουργηθεί μια νέα κλάση όπως η παρακάτω:

//package school;

import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

/**
 * The test class StudentTest.
 *
 * @author  (your name)
 * @version (a version number or a date)
 */
public class StudentTest
{
    /**
     * Default constructor for test class StudentTest
     */
    public StudentTest()
    {
    }

    /**
     * Sets up the test fixture.
     *
     * Called before every test case method.
     */
    @BeforeEach
    public void setUp()
    {
    }

    /**
     * Tears down the test fixture.
     *
     * Called after every test case method.
     */
    @AfterEach
    public void tearDown()
    {
    }
}

ενώ το διάγραμμα κλάσεων θα ενημερωθεί όπως στην ακόλουθη εικόνα.

Εικόνα 6.6.2 Test classes στο BlueJ

Στατικά εργαλεία ελέγχου κώδικα (Static Code Analyzers)

Αυτά ελέγχουν τον κώδικά σας και ψάχνουν για λάθη που μπορεί να σας έχουν ξεφύγει, π.χ. να έχετε ξεχάσει ν’ αρχικοποιήσετε μια μεταβλητή που τη χρησιμοποιείται αργότερα, ή χρήση του τελεστή == για σύγκριση αντικειμένων κλπ. Προσέξτε ότι τα εργαλεία αυτά προσφέρουν συμβουλές για πιθανά προβλήματα. Δε σημαίνει ότι όλα τα “λάθη” που βρίσκουν είναι πράγματι λάθη (false positives).

Το NetBeans παρέχει πρόσθετα (plugins) μόνο για τα δυο πρώτα για τα οποία θα μιλήσουμε σ’ αυτό το μάθημα. Για τα δυο τελευταία αναφερθείτε στις αντίστοιχες ιστοσελίδες.

CheckStyle

Για να προσθέσετε το CheckStyle plugin:

  1. Κλικ στο μενού Tools –> Plugins –> Available Plugins για ν’ ανοίξετε το διαλογικό παράθυρο Plugins και επιλέξτε την καρτέλα Settings.
  2. Πατήστε το κουμπί Add και προσθέστε το εξής URL: http://www.sickboy.cz/checkstyle/autoupdate/autoupdate-3.xml με όνομα π.χ. CheckStyle.
  3. Επιλέγοντας την καρτέλα Available Plugins και δίνοντας στο πεδίο Search CheckStyle θα εμφανιστούν δυο plugins τα οποία μπορείτε να τα εγκαταστήσετε ακολουθώντας τον οδηγό.

Εικόνα 6.6.3 Εμφάνιση συμβουλών CheckStyle στο NetBeans

Sonarlint

Το Sonarlint βελτιώνει σημαντικά την ποιότητα του κώδικά μας με την ανάλυσή του και την υποστήριξη εκατοντάδων κανόνων στατικής ανάλυσης (static analysis) για την ανίχνευση κοινών λαθών, δύσκολων σφαλμάτων και ζητημάτων ασφαλείας. Όπως θα δούμε είναι εύκολο στη χρήση και πάρα πολύ χρήσιμο.

  1. Κατεβάστε το plugin για NetBeans από εδώ και εγκαταστήστε το όπως έχουμε μάθει.
  2. Αφού το εγκαταστήσετε, κάντε δεξί κλικ σε μια κλάση και επιλέξτε Tools -> Analyze with SonarLint. Θα εμφανιστεί το παράθυρο SonarLint Analyzer Window στο κάτω μέρος με τα διάφορα προβλήματα που βρήκε ο αναλυτής.
  3. Για να δείτε του διαθέσιμους κανόνες που χρησιμοποιεί, κάντε κλικ στο μενού Window -> Sonar Rule Details.

Μπορείτε να μάθετε περισσότερα για το plugin εδώ και για το πώς δουλεύει εδώ.

Εικόνα 6.6.4 Εμφάνιση συμβουλών SonarLint στο NetBeans

Πρόσθετο PMD στο BlueJ

Η εγκατάσταση προσθέτων (plugins) στο BlueJ περιγράφεται εδώ. Στην ίδια ιστοσελίδα μπορείτε να κατεβάσετε και διάφορα πρόσθετα. Εκεί θα δείτε και το “PMD as a BlueJ extension” το οποίο μπορείτε να κατεβάσετε και να εγκαταστήσετε στον υποφάκελο extensions2 του BlueJ όπως περιγράφεται στην ιστοσελίδα. Για να επιβεβαιώσετε ότι εγκαταστάθηκε, επανεκκινήστε το BlueJ αν ήταν ήδη ανοικτό και κάντε κλικ στο Help -> Installed Extensions. Θα εμφανιστεί το αντίστοιχο διαλογικό παράθυρο που σας δείχνει ποια πρόσθετα έχουν εγκατασταθεί.

Θα πρέπει να σετάρετε που βρίσκεται το PMD το οποίο πρέπει να το κατεβάσετε ξεχωριστά και να το αποσυμπιέσετε σε κάποιον φάκελο. Στο BlueJ κάντε κλικ στο Tools -> PreferencesBlueJ -> Preferences στο MacOS), μετά στην καρτέλα Extensions και δώστε το Path to PMD installation. Πατήστε ΟΚ.

Κάντε δεξί κλικ σε μια κλάση ενός έργου και επιλέξτε PMD: Check code από το αναδυόμενο μενού. Θα εμφανιστεί ένα παράθυρο με ότι προβλήματα βρήκε το PMD.

Ασκήσεις

  1. Γράψτε unit tests για τις μεθόδους των κλάσεων του έργου School.
  2. Τρέξτε τα CheckStyle και SonarLint plugins στο έργο School. Τι λάθη σας βγάζει; Είναι έγκυρα; Ποια από αυτά θα διορθώσετε και ποια όχι;
  3. Αν χρησιμοποιείτε BlueJ, εγκαταστήστε το πρόσθετο PMD και δοκιμάστε το στις κλάσεις του έργου School.

Πηγές

  1. “JUnit 5”
  2. Java Magazine, Issue Nov/Dec 2016.
  3. Beck K. & Andres S. (2005), Extreme Programming Explained, 2nd Edition, Pearson.
  4. Lahoda J. & Stashkova A., Static Code Analysis in the NetBeans IDE Java Editor.

< Δ >