Ένας απλός οδηγός για το πώς να γράψετε δοκιμές μονάδων για έξυπνες συμβάσεις

http://upstate.agency/smart-contracts

Οι καλές δοκιμές μονάδας είναι κρίσιμες όταν πρόκειται για έξυπνη ανάπτυξη συμβολαίων. Στην πραγματικότητα, δεδομένου ότι οι έξυπνες συμβάσεις είναι αμετάβλητες και συχνά είναι υπεύθυνες για τη διαχείριση μεγάλων χρηματικών ποσών, θα μπορούσε κανείς να υποστηρίξει ότι η ανάπτυξη ορθών δοκιμών μονάδων για έξυπνες συμβάσεις είναι πιο σημαντική από τις παραδοσιακές εφαρμογές ιστού και κινητής τηλεφωνίας.

Τους τελευταίους δύο μήνες είμαστε εξαιρετικά τυχεροί να συνεργαστούμε στενά με μια εταιρεία παροχής χρηματοπιστωτικών υπηρεσιών που αναπτύσσει μια καινοτόμο πλατφόρμα για την τιμολόγηση τίτλων με κανονιστικό τρόπο. Η πλατφόρμα που θα κυκλοφορήσει σύντομα θα εισαγάγει τη δυνατότητα μετάβασης δισεκατομμυρίων δολαρίων σε τίτλους.

Για να υποστηρίξουμε την εκτόξευσή τους, συνεργαστήκαμε στενά με την ομάδα μηχανικών τους και εφαρμόσαμε εκτεταμένη δοκιμαστική κάλυψη και εκτελέσαμε τη διαδικασία ανίχνευσης σφαλμάτων. Από την εμπειρία που απομακρύνουμε με μερικές βασικές παραμέτρους, καθώς και μερικές συμβουλές και κόλπα που μπορείτε να χρησιμοποιήσετε κατά την ανάπτυξη μονάδων δοκιμών για τις έξυπνες συμβάσεις σας.

Αποκτήστε μια υγιή κατανόηση της επιχειρησιακής λογικής

Το πρώτο πράγμα που πρέπει να κάνετε είναι να βεβαιωθείτε ότι έχετε πλήρη γνώση της επιχειρηματικής λογικής της κατανεμημένης εφαρμογής (dapp). Είναι χρήσιμο να τεκμηριώσετε τα προβλήματα που θα επιλύσει το dapp σας και πώς θα τα λύσει. Θα θελήσετε επίσης να προσδιορίσετε τους διαφορετικούς περιορισμούς που έχει ο dapp σας. Για παράδειγμα, οι περισσότεροι dapps θεσπίζουν κάποια μορφή ελέγχου πρόσβασης βασισμένου σε ρόλους ή ελέγχου πρόσβασης βάσει συμβολαίου. Είναι σημαντικό να τεκμηριώσετε αυτούς τους περιορισμούς και τις επιπτώσεις που θα έχουν στη χρήση.

Όχι μόνο αυτό, θα θέλετε επίσης να μεταγράψετε τη ροή εργασίας του dapp σας. Πρέπει να ενεργοποιηθούν ορισμένες συμβάσεις πριν από άλλες; Πρέπει μια συγκεκριμένη σύμβαση να παύσει ή να διακοπεί για να εκπληρώσει μια συγκεκριμένη εργασία; κ.λπ. Αυτά είναι τα είδη των επιχειρησιακών λογικών και των ροών εργασίας που σχετίζονται απαιτήσεις που θα πρέπει να είναι καλά κατανοητή πριν από την κατάδυση και να αρχίσει την ανάπτυξη των μονάδων δοκιμών σας.

Χαρτογραφήστε τις δοκιμές της μονάδας σας

Αφού τεκμηρίσετε την επιχειρησιακή λογική και τη ροή εργασιών του dapp σας, σας ενθαρρύνουμε να χαρτογραφήσετε κάθε δοκιμή μονάδας που σχετίζεται με την αντίστοιχη λειτουργικότητα του dapp. Αυτό απαιτεί να σπάσετε το dapp κάτω, συμβόλαιο ανά συμβόλαιο και λειτουργία ανά λειτουργία και τελικά να εξειδικεύσετε όλες τις αντίστοιχες δοκιμές μονάδας που σχετίζονται με κάθε πιθανή περίπτωση χρήσης.

Για να το πετύχουμε, ομαδοποιούμε τις δοκιμές μονάδων μας έτσι ώστε να χαρτογραφούνται στις αντίστοιχες λειτουργίες των συμβάσεων. Ακολουθεί ένα παράδειγμα spec για μια περίπτωση μιας χρήσης που σχετίζεται με μια λειτουργία buyTokens (_amount) σε μια σύμβαση Token:

// όταν ενεργοποιηθεί η σύμβαση
   // όταν η σύμβαση δεν έχει παύσει
      // όταν ξεκίνησε η περίοδος προσφοράς
         // όταν η περίοδος προσφοράς δεν έχει λήξει
            // θα πρέπει να επιστρέψει αλήθεια

Αντίστροφα, εδώ είναι ένα άλλο spec που σχετίζεται με τη λειτουργία buyTokens (_amount) όταν λήξει η περίοδος προσφοράς:

// όταν ενεργοποιηθεί η σύμβαση
   // όταν η σύμβαση δεν έχει παύσει
      // όταν ξεκίνησε η περίοδος προσφοράς
         // όταν η περίοδος προσφοράς έχει λήξει
             // θα πρέπει να επανέλθει

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

Απευθυνθείτε πρώτα στους τροποποιητές και, στη συνέχεια, επεξεργαστείτε τις απαιτούμενες και αν οι δηλώσεις σας διαδοχικά

Αφού καταγράψετε τις δοκιμές της μονάδας σας, σας ενθαρρύνουμε να εστιάσετε πρώτα στην αντιμετώπιση των περιπτώσεων χρήσης που σχετίζονται με τους τροποποιητές λειτουργίας σας πρώτα. Στη συνέχεια, μπορείτε να ακολουθήσετε τη λειτουργία διαδοχικά και να αναπτύξετε μονάδες δοκιμές για να αντιμετωπίσετε όλες τις περιπτώσεις χρήσης που σχετίζονται με κάθε απαίτηση και if δήλωση.

Για παράδειγμα, για μια λειτουργία όπως αυτή που ακολουθεί, θα θέλαμε πρώτα να εξετάσουμε τις περιπτώσεις χρήσης που σχετίζονται με τους τροποποιητές atStage (Stages.AuctionStarted) και validBid (), προτού ασχοληθούμε με τις δηλώσεις if και else που ακολουθούν:

/// @dev Ο πρώτος που υποβάλλει προσφορά στην τρέχουσαAskingPrice απονέμεται το NFT του δικαιούχου
/// @dev Εάν ένας πλειοδότης υπερθεματίσει στο NFT, θα κερδίσει τη δημοπρασία και η υπεραξία θα επιστραφεί
processBid ()
δημόσιο
πληρωτέος
atStage (Stages.AuctionStarted) // Αρχίστε εδώ πρώτα
validBid () // Διευθύνετε αυτές τις περιπτώσεις χρήσης δεύτερη
επιστρέφει (uint) // Ακολουθήστε τις δηλώσεις `if`,` else` και `require` που ακολουθούν
{
  προσφορά = msg.value;
  προσφορά = msg.sender;
  
  getCurrentAskingPrice ();
  εάν (bid> currentAskingPrice)
  {
    υπερχρέωση = bid.sub (τρέχουσαAskingPrice);
    bidder.transfer (υπέρβαση);
    bid = currentAskingPrice;
    stage = Stages.AuctionEnded;
    payBeneficiary ();
    sendToBidder ();
  }}
  else if (bid == currentAskingPrice)
  {
    stage = Stages.AuctionEnded;
    payBeneficiary ();
    sendToBidder ();
  }}
  αλλού
  {
    επαναφορά ("Η προσφορά είναι χαμηλότερη από την τρέχουσαAskingPrice").
  }}
}}

Για να δοκιμάσετε πότε θα πρέπει να επιστρέψετε τα συμβόλαιά σας, βρήκαμε τον βοήθημα του AssertRevert του OpenZeppelin να είναι εξαιρετικά χρήσιμος. Μπορείτε να το χρησιμοποιήσετε ως εξής:

περιμένετε assertRevert (tokenInstance.buy ({από: επενδυτής, αξία: 500}));

Λειτουργία υπερφόρτωσης και ανάγκη για κλήσεις χαμηλού επιπέδου

Καθώς συνεχίζετε να αναπτύσσετε τις δοκιμές της μονάδας σας, είναι πιθανό ότι θα αντιμετωπίσετε ελλείψεις που σχετίζονται με τα εργαλεία προγραμματιστών που χρησιμοποιείτε. Αυτό οφείλεται στο γεγονός ότι ο έξυπνος χώρος ανάπτυξης συμβολαίων εξακολουθεί να είναι πολύ καινούργιος και συνεπώς πολλά από τα εργαλεία ανάπτυξης δεν είναι ακόμα ανώριμα.

Για παράδειγμα, το πλαίσιο Truffle - το οποίο είναι ένα εξαιρετικό εργαλείο και ίσως το πιο δημοφιλές πλαίσιο στον χώρο του Ethereum σήμερα - δεν υποστηρίζει την υπερφόρτωση λειτουργίας. Δηλαδή, αν χρειαστεί να δοκιμάσετε δύο λειτουργίες με το ίδιο όνομα, αλλά διαφορετικές arrities, θα χρειαστεί να χρησιμοποιήσετε κλήσεις χαμηλού επιπέδου για να δοκιμάσετε τη δεύτερη λειτουργία στο ABI της σύμβασης. Εάν δεν το κάνετε, θα λάβετε ένα σφάλμα: Μη έγκυρος αριθμός επιχειρημάτων στη λειτουργία Solidity. Ας ρίξουμε μια ματιά σε ένα γρήγορο παράδειγμα.

Εάν έχετε δύο συναρτήσεις buyTokens σε μια σύμβαση, μία που δεν λαμβάνει επιχειρήματα και είναι καταχωρημένη πρώτη στο ABI και μία που δέχεται ένα (_amount) όρισμα και παρατίθεται δεύτερη στο ABI, θα χρειαστεί να χρησιμοποιήσετε μια κλήση χαμηλού επιπέδου για να δοκιμάσετε τη λειτουργία buyTokens (_amount) χρησιμοποιώντας το encodeCall όπως φαίνεται παρακάτω.

δεδομένα = encodeCall ("buyTokens", ['uint'], [100]). // 100 είναι η τιμή του όρου `_amount`
contractInstance.sendTransaction ({στοιχεία από: επενδυτής, αξία: 500})

Η κοινότητα Truffle γνωρίζει αυτό το θέμα και θα αντιμετωπιστεί σε μια επικείμενη έκδοση. Ωστόσο, τα περίεργα σενάρια, όπως αυτό, δεν είναι άτυπα όταν αναπτύσσετε έξυπνες συμβάσεις και τις αντίστοιχες δοκιμές μονάδων. Θα χρειαστεί να πάρετε χειραψία (αλλά να είστε ιδιαίτερα προσεκτικοί) με τις λύσεις που επικαλούνται από καιρό σε καιρό.

Πώς να ελέγξετε τις εσωτερικές λειτουργίες

Δεδομένου ότι οι λειτουργίες της Solidity μπορούν να έχουν διαφορετικές ορατότητες (δηλ. Δημόσιες, ιδιωτικές, εσωτερικές και εξωτερικές) αξίζει να σημειωθεί ότι οι δοκιμές ανάπτυξης μονάδων για εσωτερικές λειτουργίες είναι λίγο διαφορετικές από την ανάπτυξη τους για δημόσιες λειτουργίες. Αυτό οφείλεται στο γεγονός ότι οι εσωτερικές λειτουργίες δεν εμφανίζονται στην ABI μιας έξυπνης σύμβασης μετά τη συμπλήρωσή της. Επομένως, για να ελέγξετε τις εσωτερικές λειτουργίες, έχετε δύο επιλογές:

  1. Μπορείτε να δημιουργήσετε μια άλλη σύμβαση που κληρονομεί τη σύμβαση που δοκιμάζετε για να ελέγξετε μια εσωτερική λειτουργία
  2. Ή μπορείτε να δοκιμάσετε τη λογική μιας εσωτερικής συνάρτησης μέσα από τις άλλες λειτουργίες του συμβολαίου που καλούν την εσωτερική λειτουργία

Κάθε προσέγγιση θα κάνει τη δουλειά, αν και κάποιοι μπορεί να υποστηρίζουν ότι η λογική μιας εσωτερικής λειτουργίας από τις άλλες λειτουργίες της σύμβασης είναι πιο διαισθητική.

Handy Ganache συμβουλές και κόλπα

Όπως ανέφερα νωρίτερα, χρησιμοποιούμε κυρίως το πλαίσιο Truffle για να δημιουργήσουμε dapps για τους πελάτες μας. Η Truffle χρησιμοποιεί ένα εργαλείο που ονομάζεται Ganache το οποίο σας δίνει τη δυνατότητα να πυροδοτήσετε γρήγορα το προσωπικό σας μπλοκ μπλοκ Ethereum το οποίο μπορείτε να χρησιμοποιήσετε για να εκτελέσετε δοκιμές, να εκτελέσετε εντολές και να επιθεωρήσετε την κατάσταση ενώ ελέγχετε τον τρόπο λειτουργίας της αλυσίδας. Είναι εξαιρετικά βολικό.

Αυτό που είναι πολύ ωραίο για το Ganache είναι πως μπορεί να προσαρμοστεί εύκολα για να ικανοποιήσει τις μοναδικές ανάγκες του dapp. Για παράδειγμα, έχουμε αναπτύξει δοκιμές μονάδων για dapps που απαιτούν από εμάς να δοκιμάσουμε περιπτώσεις χρήσης για δεκάδες και δεκάδες χρήστες. Με μια απλή εντολή, το Ganache μας διευκολύνει να δοκιμάσουμε με πολλούς λογαριασμούς όπως χρειαζόμαστε:

ganache-cli -a 40 // Όπου η σημαία `a` δηλώνει την εκκίνηση του Ganache με 40 δοκιμαστικούς λογαριασμούς

Επιπλέον, μπορούμε να θέσουμε τα υπόλοιπα αυτών των λογαριασμών να αρχίσουν με το ETH όσο χρειάζεται για τις δοκιμές μας. Από προεπιλογή, η Τρούφα ρυθμίζει τα υπόλοιπα στα 100 ETH. Μπορούμε εύκολα να αυξήσουμε ή να μειώσουμε αυτή την τιμή ανάλογα με τις δικές μας μοναδικές απαιτήσεις:

ganache -e 10000` // Όπου η σημαία `e` υποδηλώνει πόσο ETH κάθε λογαριασμός πρέπει να αρχίσει από προεπιλογή

Το Ganache είναι ένα εξαιρετικά χρήσιμο εργαλείο για την ανάπτυξη έξυπνων συμβάσεων για την Ethereum. Βοηθά στην εξομάλυνση της ανάπτυξης, τον μετριασμό του κινδύνου και τη βελτίωση της αξιοπιστίας του dapp.

Συνδέοντας όλα μαζί

Η δοκιμή μονάδας στον έξυπνο χώρο των συμβάσεων είναι ουσιαστικά παρόμοια με τη δοκιμή μονάδων στον παγκόσμιο ιστό και τον κινητό τομέα εφαρμογών. Η μεγαλύτερη διαφορά είναι ότι οι έξυπνες συμβάσεις σήμερα είναι αμετάβλητες και γενικά δεν προσφέρουν απλούς τρόπους για να τους επαναλάβουν με την πάροδο του χρόνου [1]. Ως εκ τούτου, η ανάπτυξη ορθών δοκιμών μονάδας είναι κρίσιμη δεδομένης της έξυπνης συμβατότητας των συμβάσεων και του γεγονότος ότι είναι συχνά υπεύθυνοι για τη διαχείριση μεγάλων χρηματικών ποσών. Ο μόνος τρόπος για να είστε σίγουροι ότι τα έξυπνα συμβόλαιά σας είναι ασφαλή και αξιόπιστα είναι να τα δοκιμάσετε σχολαστικά.

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

Ελπίζουμε αυτά τα διδάγματα από τις εμπειρίες μας να αναπτύξουν δοκιμές μονάδας είναι χρήσιμες και να υποστηρίξουν τις προσπάθειές σας για την οικοδόμηση ασφαλών και αξιόπιστων έξυπνων συμβάσεων. Μη διστάσετε να επικοινωνήσετε μαζί μας αν έχετε επιπλέον ερωτήσεις ή σκέψεις που θα θέλατε να συζητήσετε περαιτέρω με την ομάδα μας - [email protected]

Τις επόμενες ημέρες θα δημοσιεύσουμε ένα άρθρο παρακολούθησης σε αυτό, τι έμαθα από τη δοκιμή σύνθετων έξυπνων συμβολαίων.

[1] Η ομάδα Zeppelin ξεκίνησε πρόσφατα το ZeppelinOS, το οποίο παρέχει ένα σετ αλγόριθμων αναβαθμίσιμων τυποποιημένων βιβλιοθηκών και καθιστά δυνατή την αναβάθμιση των έξυπνων συμβολαίων σας χρησιμοποιώντας μοντέλα μεσολάβησης.

☞ Για να είστε ενημερωμένοι με την εργασία μας με έξυπνη ανάπτυξη συμβάσεων, ακολουθήστε μας στο Medium και το Twitter.