Γεννήτριες δοκιμών


Η γεννήτρια δοκιμών είναι ένα κομμάτι λογισμικού ειδικό για κάθε track, που δημιουργεί αυτόματα τις δοκιμές μιας άσκησης εξάσκησης. Αυτό γίνεται μετατρέποντας τα JSON test cases της άσκησης σε δοκιμές στη γλώσσα προγραμματισμού του track.

Οφέλη

Μερικά από τα οφέλη μιας γεννήτριας δοκιμών είναι:

  1. Οι ασκήσεις μπορούν να προστεθούν πιο γρήγορα
  2. Αυτοματοποιεί τα "βαρετά" κομμάτια της προσθήκης μιας άσκησης
  3. Εύκολος συγχρονισμός των δοκιμών με τα τελευταία κανονικά δεδομένα

Περιπτώσεις χρήσης

Σε γενικές γραμμές, τρέχεις μια γεννήτρια δοκιμών για έναν από τους εξής λόγους:

  1. Να δημιουργήσεις τις δοκιμές για μια νέα άσκηση
  2. Να ενημερώσεις τις δοκιμές μιας υπάρχουσας άσκησης

Δημιουργία δοκιμών για νέα άσκηση

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

Ενημέρωση των δοκιμών μιας υπάρχουσας άσκησης

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

Σημείο εκκίνησης

Υπάρχουν δύο πιθανά σημεία εκκίνησης όταν υλοποιείς μια γεννήτρια δοκιμών για μια άσκηση:

  1. Η άσκηση είναι νέα και επομένως δεν έχει καθόλου δοκιμές
  2. Η άσκηση υπάρχει ήδη και επομένως έχει ήδη δοκιμές
Caution

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

Σχεδιασμός

Σε γενικές γραμμές, τα αρχεία δοκιμών δημιουργούνται με έναν από τους εξής τρόπους:

  • Κώδικα: τα αρχεία δοκιμών δημιουργούνται (κυρίως) μέσω κώδικα
  • Πρότυπα: τα αρχεία δοκιμών δημιουργούνται (κυρίως) με πρότυπα

Έχουμε διαπιστώσει ότι η προσέγγιση με κώδικα οδηγεί σε αρκετά περίπλοκο κώδικα για τη γεννήτρια δοκιμών, ενώ η προσέγγιση με πρότυπα είναι πιο απλή.

Αυτό που προτείνουμε είναι η εξής ροή:

  1. Διάβασε τα κανονικά δεδομένα της άσκησης
  2. Εξαίρεσε τα test cases που είναι σημειωμένα ως include = false στο αρχείο tests.toml της άσκησης
  3. Μετέτρεψε τα κανονικά δεδομένα της άσκησης σε μορφή που μπορεί να χρησιμοποιηθεί σε ένα πρότυπο
  4. Πέρασε τα κανονικά δεδομένα της άσκησης σε ένα πρότυπο ειδικό για την άσκηση

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

  • Κάνει προφανές το πώς δημιουργούνται τα αρχεία δοκιμών
  • Κάνει τον εντοπισμό σφαλμάτων πιο εύκολο
  • Κάνει ασφαλή την επεξεργασία τους, χωρίς τον κίνδυνο να σπάσει κάποια άλλη άσκηση
Caution

Όταν σχεδιάζεις τη γεννήτρια δοκιμών, προσπάθησε να:

  • Ελαχιστοποιήσεις την προεπεξεργασία των κανονικών δεδομένων μέσα στη γεννήτρια δοκιμών
  • Μειώσεις τη σύζευξη μεταξύ των προτύπων

Υλοποίηση

Η γεννήτρια δοκιμών συνήθως (κατά κύριο λόγο) γράφεται στη γλώσσα του track.

Caution

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

Μορφοποίηση

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

Κανονικά δεδομένα

Τα βασικά δεδομένα με τα οποία δουλεύει η γεννήτρια δοκιμών είναι το canonical-data.json file μιας άσκησης. Αυτό το αρχείο ορίζεται στο αποθετήριο exercism/problem-specifications, το οποίο ορίζει κοινά μεταδεδομένα για πολλές ασκήσεις στο Exercism.

Caution

Δεν έχουν όλες οι ασκήσεις αρχείο canonical-data.json! Αν δεν έχουν, θα χρειαστεί να δημιουργήσεις τις δοκιμές χειροκίνητα, αφού δεν υπάρχουν δεδομένα για να δουλέψει η γεννήτρια δοκιμών.

Δομή

Τα κανονικά δεδομένα ορίζονται σε ένα αντικείμενο JSON. Αυτό το αντικείμενο περιέχει ένα πεδίο "cases" που περιέχει τα test cases. Αυτά τα test cases (κανονικά) αντιστοιχούν ένα προς ένα σε δοκιμές στο track σου.

Κάθε test case έχει μερικές ιδιότητες, με σημαντικότερες την περιγραφή, το πεδίο property, την τιμή (ή τις τιμές) εισόδου και την αναμενόμενη τιμή. Ακολουθεί ένα (μερικό) παράδειγμα του αρχείου canonical-data.json της άσκησης leap:

{
  "exercise": "leap",
  "cases": [
    {
      "uuid": "6466b30d-519c-438e-935d-388224ab5223",
      "description": "year not divisible by 4 in common year",
      "property": "leapYear",
      "input": {
        "year": 2015
      },
      "expected": false
    },
    {
      "uuid": "4fe9b84c-8e65-489e-970b-856d60b8b78e",
      "description": "year divisible by 4, not divisible by 100 in leap year",
      "property": "leapYear",
      "input": {
        "year": 1996
      },
      "expected": true
    }
  ]
}

Η κύρια ευθύνη της γεννήτριας δοκιμών είναι να μετατρέψει αυτά τα δεδομένα JSON σε δοκιμές ειδικές για το track. Να πώς θα μπορούσε το παραπάνω JSON να μετατραπεί σε κώδικα δοκιμών Nim:

import unittest
import leap

suite "Leap":
  test "year not divisible by 4 in common year":
    check isLeapYear(2015) == false

  test "year divisible by 4, not divisible by 100 in leap year":
    check isLeapYear(1996) == true

Η δομή του αρχείου canonical-data.json είναι καλά τεκμηριωμένη και διαθέτει επίσης ορισμό σχήματος JSON.

Εμφώλευση

Κάποιες ασκήσεις χρησιμοποιούν εμφώλευση στα κανονικά δεδομένα τους. Αυτό σημαίνει ότι κάθε στοιχείο σε έναν πίνακα cases μπορεί να είναι είτε:

  1. Ένα κανονικό test case (χωρίς θυγατρικά test cases)
  2. Μια ομάδα test cases (ένα ή περισσότερα θυγατρικά test cases)
Note

Μπορείς να αναγνωρίσεις τον τύπο ενός στοιχείου ελέγχοντας αν υπάρχουν πεδία που είναι αποκλειστικά για έναν τύπο στοιχείου. Πιθανώς ο καλύτερος τρόπος για να το κάνεις αυτό είναι να χρησιμοποιήσεις το κλειδί "cases", το οποίο υπάρχει μόνο στις ομάδες test cases.

Ακολουθεί ένα παράδειγμα εμφωλευμένων test cases:

{
  "cases": [
    {
      "uuid": "e9c93a78-c536-4750-a336-94583d23fafa",
      "description": "data is retained",
      "property": "data",
      "input": {
        "treeData": ["4"]
      },
      "expected": {
        "data": "4",
        "left": null,
        "right": null
      }
    },
    {
      "description": "insert data at proper node",
      "cases": [
        {
          "uuid": "7a95c9e8-69f6-476a-b0c4-4170cb3f7c91",
          "description": "smaller number at left node",
          "property": "data",
          "input": {
            "treeData": ["4", "2"]
          },
          "expected": {
            "data": "4",
            "left": {
              "data": "2",
              "left": null,
              "right": null
            },
            "right": null
          }
        }
      ]
    }
  ]
}
Caution

Αν το track σου δεν υποστηρίζει ομαδοποίηση δοκιμών, θα χρειαστεί να:

  • Διατρέξεις/επιπεδώσεις την ιεραρχία του cases ώστε να καταλήξεις μόνο στα πιο εσωτερικά (φύλλα) test cases
  • Συνδυάσεις την περιγραφή του test case με την περιγραφή (ή τις περιγραφές) του γονικού στοιχείου, ώστε να δημιουργήσεις ένα μοναδικό όνομα δοκιμής

Τιμές εισόδου και αναμενόμενες τιμές

Τα περιεχόμενα των κλειδιών input και expected ενός test case ποικίλλουν σε μεγάλο βαθμό. Στις περισσότερες περιπτώσεις είναι βαθμωτές τιμές (όπως αριθμοί, Boolean (λογική τιμή) ή συμβολοσειρές) ή απλά αντικείμενα. Ωστόσο, περιστασιακά θα συναντήσεις και πιο σύνθετες τιμές που πιθανότατα χρειάζονται λίγη προεπεξεργασία, όπως lambdas σε ψευδοκώδικα, λίστες πράξεων που εκτελούνται στον κώδικα του μαθητή και άλλα.

Σενάρια

Τα test cases έχουν ένα προαιρετικό πεδίο scenarios. Αυτό το πεδίο μπορεί να χρησιμοποιηθεί από τη γεννήτρια δοκιμών για να χειριστεί ειδικά ορισμένα test cases. Η πιο συνηθισμένη περίπτωση χρήσης είναι να αγνοήσεις ορισμένους τύπους δοκιμών, για παράδειγμα δοκιμές με το σενάριο "unicode", καθώς η γλώσσα του track σου μπορεί να μην υποστηρίζει Unicode.

Την πλήρη λίστα σεναρίων θα τη βρεις εδώ.

Ανάγνωση αρχείων canonical-data.json

Υπάρχουν μερικές επιλογές για να διαβάσεις τα αρχεία canonical-data.json:

  1. Να τα κατεβάσεις απευθείας από το αποθετήριο problem-specifications (π.χ. https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/leap/canonical-data.json).
  2. Να προσθέσεις το αποθετήριο problem-specifications ως Git submodule στο αποθετήριο του track.
  3. Να τα διαβάσεις από την cache του configlet. Η θέση εξαρτάται από το σύστημα του χρήστη, αλλά μπορείς να χρησιμοποιήσεις την εντολή configlet info -o -v d | head -1 | cut -d " " -f 5 για να βρεις τη θέση προγραμματιστικά.

Test cases ειδικά για το track

Αν το track σου θέλει να προσθέσει επιπλέον test cases ειδικά για το track (που δεν υπάρχουν στα κανονικά δεδομένα), μια επιλογή είναι να δημιουργήσεις ένα αρχείο additional-test-cases.json, το οποίο η γεννήτρια δοκιμών μπορεί στη συνέχεια να συγχωνεύσει με το αρχείο canonical-data.json πριν το περάσει στο πρότυπο για απόδοση.

Πρότυπα

Η μηχανή προτύπων που θα χρησιμοποιήσεις θα είναι πιθανότατα ειδική για το track. Ιδανικά, θέλεις τα πρότυπά σου να είναι όσο πιο απλά γίνεται, οπότε μην ανησυχείς για την επανάληψη κώδικα και τα παρόμοια.

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

Note

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

Χρήση του configlet

Το configlet είναι το βασικό εργαλείο συντήρησης ενός track και μπορείς να το χρησιμοποιήσεις για να:

  • Δημιουργήσεις τα αρχεία μιας νέας άσκησης: τρέξε bin/configlet create --practice-exercise <slug>
  • Συγχρονίσεις το αρχείο tests.toml μιας υπάρχουσας άσκησης: τρέξε bin/configlet sync --tests --update --exercise <slug>
  • Κατεβάσεις τα κανονικά δεδομένα της άσκησης στον δίσκο (αυτό είναι παρενέργεια οποιασδήποτε από τις παραπάνω εντολές)

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

Διεπαφή γραμμής εντολών

Θα θέλεις η χρήση της γεννήτριας δοκιμών να είναι και εύκολη και ισχυρή. Για αυτό, συνιστούμε να δημιουργήσεις ένα ή περισσότερα αρχεία script.

Note

Είσαι ελεύθερος να επιλέξεις όποια μορφή αρχείου script ταιριάζει καλύτερα στο track σου. Τα shell scripts και τα PowerShell scripts είναι συνηθισμένες επιλογές που μπορούν να λειτουργήσουν καλά και οι δύο.

Ακολουθεί ένα παράδειγμα shell script που συνδυάζει το configlet και μια γεννήτρια δοκιμών για να στήσεις γρήγορα μια νέα άσκηση:

bin/fetch-configlet
bin/configlet create --practice-exercise <slug>
path/to/test-generator <slug>

Δημιουργία από την αρχή

Πριν αρχίσεις να φτιάχνεις μια γεννήτρια δοκιμών, σου προτείνουμε να ρίξεις μια ματιά σε μερικές υπάρχουσες γεννήτριες δοκιμών, για να πάρεις μια ιδέα για το πώς τις έχουν υλοποιήσει άλλα tracks:

Αν έχεις απορίες, το forum είναι το καλύτερο μέρος για να τις ρωτήσεις. Μπορεί επίσης να σου φανούν χρήσιμες οι συζητήσεις στο forum γύρω από τις γεννήτριες δοκιμών της Rust και της JavaScript.

Ελάχιστο βιώσιμο προϊόν

Συνιστούμε να φτιάχνεις τη γεννήτρια δοκιμών σταδιακά, ξεκινώντας με ένα ελάχιστο βιώσιμο προϊόν. Μια στοιχειώδης έκδοση θα διάβαζε το canonical-data.json μιας άσκησης και θα περνούσε απλώς αυτά τα δεδομένα στο πρότυπο.

Ξεκίνα εστιάζοντας σε μία μόνο άσκηση, κατά προτίμηση κάποια απλή, όπως η άσκηση leap. Μόνο όταν αυτό δουλέψει θα πρέπει να προσθέσεις σταδιακά περισσότερες ασκήσεις.

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

Note

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

Χρήση ή συνεισφορά

Το πώς θα χρησιμοποιήσεις ή θα συνεισφέρεις σε μια γεννήτρια δοκιμών εξαρτάται από το track. Ψάξε για οδηγίες στο README.md, στο CONTRIBUTING.md του track ή στον κατάλογο του κώδικα της γεννήτριας δοκιμών.