Η διεπαφή του εκτελεστή δοκιμών


Οι Test Runners έχουν τη μία και μοναδική ευθύνη να παίρνουν μια λύση, να εκτελούν όλα τα test και να επιστρέφουν μια τυποποιημένη έξοδο. Όλες οι αλληλεπιδράσεις με τον ιστότοπο του Exercism γίνονται αυτόματα και δεν αποτελούν μέρος αυτής της προδιαγραφής.

Εκτέλεση

  • Ένας Test Runner θα πρέπει να παρέχει ένα εκτελέσιμο script. Περισσότερες πληροφορίες μπορείς να βρεις στο αρχείο docker.md.
  • Το script θα λαμβάνει τρεις παραμέτρους:
    • Το slug της άσκησης (π.χ. two-fer).
    • Μια διαδρομή προς έναν κατάλογο εισόδου (με κάθετο στο τέλος) που περιέχει τα αρχεία της υποβληθείσας λύσης και τυχόν άλλα αρχεία της άσκησης. Αυτός ο κατάλογος θα πρέπει να θεωρείται μόνο για ανάγνωση. Τεχνικά είναι δυνατό να γράψεις μέσα του, αλλά είναι καλύτερα να χρησιμοποιείς το /tmp για προσωρινά αρχεία (π.χ. για τη μεταγλώττιση πηγαίων αρχείων).
    • Μια διαδρομή προς έναν κατάλογο εξόδου (με κάθετο στο τέλος). Αυτός ο κατάλογος είναι εγγράψιμος.
  • Το script πρέπει να γράφει ένα αρχείο results.json στον κατάλογο εξόδου.
  • Ο test runner πρέπει να τερματίζει με κωδικό εξόδου 0 αν έχει εκτελεστεί με επιτυχία, ανεξάρτητα από την κατάσταση των test.

Επιτρεπόμενος χρόνος εκτέλεσης

Ο test runner έχει στη διάθεσή του 100% της CPU και 3GB μνήμης για ένα παράθυρο 20 δευτερολέπτων ανά λύση. Μετά από 20 δευτερόλεπτα, η διεργασία σταματά και αναφέρει timeout.

Note

Συνιστούμε ανεπιφύλακτα να ακολουθήσεις το έγγραφο με τις βέλτιστες πρακτικές απόδοσης για να μειώσεις την πιθανότητα timeout.

Μορφή εξόδου

Τα παρακάτω πεδία υποστηρίζονται στα αρχεία results.json:

Ανώτατο επίπεδο

Έκδοση

κλειδί: version, τύπος: number, παρουσία: υποχρεωτική

έκδοση: 1, 2, 3

Η έκδοση της προδιαγραφής την οποία ακολουθεί αυτό το αρχείο:

  • 1: Για track των οποίων ο test runner δεν μπορεί να παρέχει πληροφορίες για μεμονωμένα test.
  • 2: Για track των οποίων ο test runner μπορεί να εμφανίζει πληροφορίες για μεμονωμένα test. Ελάχιστη απαιτούμενη έκδοση για track με Concept Exercises.
  • 3: Για track των οποίων ο test runner μπορεί να συνδέει μεμονωμένα test με μια εργασία.

Κατάσταση

κλειδί: status, τύπος: string, παρουσία: υποχρεωτική

έκδοση: 1, 2, 3

Οι παρακάτω συνολικές καταστάσεις είναι έγκυρες:

  • pass: Όλα τα test πέρασαν
  • fail: Τουλάχιστον ένα test έχει κατάσταση fail ή error
  • error: Δεν εκτελέστηκε κανένα test (αυτό συνήθως σημαίνει σφάλμα μεταγλώττισης ή συντακτικό σφάλμα)

Η κατάσταση error θα πρέπει να χρησιμοποιείται μόνο αν όλα τα test παρουσίασαν σφάλμα. Στις μεταγλωττιζόμενες γλώσσες αυτό είναι συνήθως αποτέλεσμα του ότι ο κώδικας δεν μπορεί να μεταγλωττιστεί. Στις διερμηνευόμενες γλώσσες πρόκειται για σφάλμα χρόνου εκτέλεσης, όπως ένα συντακτικό σφάλμα που εμποδίζει το αρχείο να αναλυθεί.

Μήνυμα

κλειδί: message, τύπος: string, παρουσία: υποχρεωτική αν status = error, ή όταν status = fail και version = 1

έκδοση: 1, 2, 3

Όταν η κατάσταση είναι error (δεν εκτελέστηκε σωστά κανένα test), θα πρέπει να παρέχεται το κλειδί message του ανώτατου επιπέδου. Θα πρέπει να δείχνει στον χρήστη το σφάλμα που προέκυψε. Καθώς είναι η μόνη πληροφορία που θα λάβει ο χρήστης για το πώς να κάνει debug στο πρόβλημά του, πρέπει να είναι όσο το δυνατόν πιο σαφής:

  • Απλοποίησε τις διαδρομές ώστε να είναι κάτι σαν <solution-dir>/relative/path αντί για /full/path/to, καθώς αλλιώς θα περιλαμβάνουν άχρηστα δεδομένα ειδικά για το ECR
  • Όταν είναι εφικτό ή σκόπιμο, συμπίεσε τις στοίβες που δεν προέρχονται από κώδικα του χρήστη
  • Μην εμφανίζεις ποτέ στοίβες κλήσεων χωρίς τα συμφραζόμενά τους (δηλαδή το μήνυμα σφάλματος)
  • Μην αλλάζεις το μήνυμα σφάλματος (αν είναι εφικτό), καθώς έτσι θα είναι πιο εύκολη η αναζήτηση του σφάλματος

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

Η τιμή του κλειδιού message του ανώτατου επιπέδου περιορίζεται σε 65535 χαρακτήρες. Το πραγματικό μέγιστο μήκος είναι μικρότερο αν η τιμή περιέχει χαρακτήρες πολλών byte.

Όταν η κατάσταση δεν είναι error, είτε όρισε την τιμή σε null είτε παρέλειψε εντελώς το κλειδί.

Test

κλειδί: tests, τύπος: array, παρουσία: υποχρεωτική αν status = fail ή status = pass

έκδοση: 2, 3

Πρόκειται για έναν πίνακα με τα αποτελέσματα των test, όπως προσδιορίζονται στην ενότητα "Ανά test" παρακάτω.

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

Ο λόγος είναι ότι στους μαθητές εμφανίζεται μόνο η πρώτη αποτυχία και επομένως είναι σημαντικό να εμφανίζεται η σωστή αποτυχία. Επειδή τα test στο αρχείο test είναι γενικά ταξινομημένα με τρόπο TDD, και επειδή στα Practice Exercises οι μαθητές βλέπουν το αρχείο test στον editor, η ευθυγράμμιση των αποτελεσμάτων με το αρχείο test είναι κρίσιμη.

Ανά test

Όνομα

κλειδί: name, τύπος: string, παρουσία: υποχρεωτική

έκδοση: 2, 3

Είναι το όνομα του test σε μορφή αναγνώσιμη από τον άνθρωπο.

Κώδικας test

κλειδί: test_code, τύπος: string, παρουσία: υποχρεωτική αν η άσκηση είναι Concept Exercise

έκδοση: 2, 3

Αυτό ΠΡΕΠΕΙ να υπάρχει για τα Concept Exercises και ΣΥΝΙΣΤΑΤΑΙ να υπάρχει για τα Practice Exercises. Η διαφορά σε αυτή την απαίτηση προκύπτει από το γεγονός ότι στα Concept Exercises τα test δεν εμφανίζονται στους μαθητές, οπότε η επίλυση της άσκησης μπορεί να είναι αδύνατη χωρίς να εμφανίζεται το test_code, ενώ στα Practice Exercises τα test εμφανίζονται.

Είναι το σώμα της εντολής που ελέγχεται. Για παράδειγμα, το παρακάτω test σε Ruby:

def test_duplicate_items_uniqs_list
  cart = ShoppingCart.new
  cart.add(:STARIC)
  cart.add(:MEDNEW)
  cart.add(:MEDNEW)
  assert_equal 'Newspaper, Rice', cart.items_list
end

θα πρέπει να επιστρέφει μια τιμή test_code ως εξής:

"cart = ShoppingCart.new
cart.add(:STARIC)
cart.add(:MEDNEW)
cart.add(:MEDNEW)
assert_equal 'Newspaper, Rice', cart.items_list"

(με τις αλλαγές γραμμής να αντικαθίστανται από \n ώστε το JSON να είναι έγκυρο).

Κατάσταση

κλειδί: status, τύπος: string, παρουσία: υποχρεωτική

έκδοση: 2, 3

Οι παρακάτω καταστάσεις ανά test είναι έγκυρες:

  • pass: Το test πέρασε
  • fail: Το test απέτυχε
  • error: Το test παρουσίασε σφάλμα, δηλαδή δεν επέστρεψε κάποια τιμή

Μήνυμα

κλειδί: message, τύπος: string, παρουσία: υποχρεωτική αν το status είναι fail ή error

έκδοση: 2, 3

Το κλειδί message ανά test χρησιμοποιείται για να επιστρέφει τα αποτελέσματα ενός test του οποίου το status είναι fail ή error. Θα πρέπει να είναι όσο το δυνατόν πιο αναγνώσιμο από τον άνθρωπο. Ό,τι γράφεται εδώ θα εμφανίζεται στον μαθητή όταν το test του δεν περνάει. Αν δεν υπάρχει μήνυμα αποτυχίας ή μήνυμα σφάλματος, είτε όρισε την τιμή σε null είτε παρέλειψε εντελώς το κλειδί. Επιτρέπεται επίσης να βάλεις εδώ την έξοδο της σουίτας test. Η τιμή του message δεν έχει όριο μήκους.

Έξοδος

κλειδί: output, τύπος: string, παρουσία: προαιρετική

έκδοση: 2, 3

Το κλειδί output ανά test θα πρέπει να χρησιμοποιείται για την αποθήκευση και την εμφάνιση οτιδήποτε εμφανίζει ο χρήστης σκόπιμα για ένα test.

  • Θα πρέπει να επισυνάπτεται σε όλα τα αποτελέσματα test που παράγουν έξοδο του χρήστη.
  • Θα πρέπει να εμφανίζεται μόνο περιεχόμενο που έχει εμφανίσει ο χρήστης χειροκίνητα, όχι η αυτόματη έξοδος του test runner.
  • Μπορείς είτε να καταγράφεις περιεχόμενο που εμφανίζεται με τους συνήθεις τρόπους (π.χ. puts στη Ruby, print στην Python ή Debug.WriteLine στη C#), είτε να παρέχεις μια μέθοδο που μπορεί να χρησιμοποιήσει ο χρήστης (π.χ. ο Test Runner της Ruby παρέχει στον χρήστη μια καθολικά διαθέσιμη μέθοδο debug, την οποία μπορεί να χρησιμοποιήσει και η οποία έχει τα ίδια χαρακτηριστικά με την τυπική μέθοδο puts).
  • Η έξοδος πρέπει να περιορίζεται στους 500 χαρακτήρες. Σε αυτή την περίπτωση είναι αποδεκτό είτε να την περικόψεις με ένα μήνυμα του τύπου "Η έξοδος περικόπηκε. Περιορίσου στους 500 χαρακτήρες" είτε να επιστρέψεις σφάλμα.

ID εργασίας

κλειδί: task_id, τύπος: number, παρουσία: προαιρετική

έκδοση: 3

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

Προς το παρόν, μόνο τα Concept Exercises έχουν καλά ορισμένες εργασίες με τις οποίες μπορείς να συνδέσεις test, αλλά αυτό μπορεί να αλλάξει στο μέλλον.

Για παράδειγμα, δες το παρακάτω αρχείο instructions.md:

# Instructions

You're going to write some code to help Lucian cook an exquisite lasagna from his favorite cook book.

## 1. Define the expected oven time in minutes

...

## 2. Calculate the remaining oven time in minutes

...

Αυτές οι οδηγίες ορίζουν δύο εργασίες:

  1. Ορισμός του αναμενόμενου χρόνου ψησίματος σε λεπτά
  2. Υπολογισμός του υπόλοιπου χρόνου ψησίματος σε λεπτά

Το αρχείο results.json θα μπορούσε τότε να έχει μια καταχώριση σαν αυτή:

{
  "name": "Expected oven time in minutes",
  "status": "pass",
  "task_id": 1,
  "test_code": "Assert.Equal(40, Lasagna.ExpectedMinutesInOven());"
}

Αυτό το test είναι τώρα συνδεδεμένο με την πρώτη εργασία: "Define the expected oven time in minutes". Σημείωσε ότι το όνομα δεν χρειάζεται να ταιριάζει με την περιγραφή της εργασίας.

Υπάρχουν διάφοροι τρόποι με τους οποίους τα track θα μπορούσαν να το υλοποιήσουν:

  • Να προσθέσεις μεταδεδομένα στα test μέσα στο αρχείο test (π.χ. με χαρακτηριστικά/σημειώσεις/σχόλια) και ο test runner να διαβάζει αυτά τα μεταδεδομένα όταν εκτελεί τα test.
  • Να αποθηκεύσεις την αντιστοίχιση ονόματος test/ID εργασίας σε ένα ξεχωριστό αρχείο (όπως το αρχείο .meta/config.json της άσκησης) και να συγχωνεύσεις αυτή την πληροφορία στο παραγόμενο αρχείο results.json.

Παραδείγματα

Αυτά είναι παραδείγματα του πώς μπορεί να μοιάζει ένα έγκυρο αρχείο results.json για τις διάφορες εκδόσεις:

Παράδειγμα v1

{
  "version": 1,
  "status": "fail",
  "message": "Failed: test_answer\nExpected: 42, actual: 3"
}

Παράδειγμα v2

{
  "version": 2,
  "status": "fail",
  "message": null,
  "tests": [
    {
      "name": "Test that the thing works",
      "status": "fail",
      "message": "Expected 42 but got 123123",
      "output": "Debugging information output by the user",
      "test_code": "assert_equal 42, answerToTheUltimateQuestion()"
    }
  ]
}

Παράδειγμα v3

{
  "version": 3,
  "status": "fail",
  "message": null,
  "tests": [
    {
      "name": "Test that the thing works",
      "status": "fail",
      "message": "Expected 42 but got 123123",
      "output": "Debugging information output by the user",
      "test_code": "assert_equal 42, answerToTheUltimateQuestion()",
      "task_id": 1
    }
  ]
}

Ζητήματα UI/UX

Όταν ένα test αποτυγχάνει

Όταν η λύση ενός μαθητή αποτυγχάνει σε ένα test, θα πρέπει να εμφανίζει κάτι σαν αυτό:

Test Code:
  <test_code>

Test Result:
  <message>

Όταν ένα test περνάει

Όταν η λύση περνάει ένα test, θα πρέπει να εμφανίζει κάτι σαν αυτό:

Test Code:
  <test_code>

Πώς να προσθέσεις μεταδεδομένα στη σουίτα test της γλώσσας σου

Όλοι οι δρόμοι οδηγούν στη Ρώμη και δεν υπάρχει προκαθορισμένος τρόπος για να φτάσεις εκεί. Μέχρι τώρα έχουν ακολουθηθεί αρκετές προσεγγίσεις:

  • Βοηθητικά αρχεία JSON που συντάσσονται χειροκίνητα και συγχωνεύονται με τα αποτελέσματα των test κατά τον χρόνο εκτέλεσης των test.
  • Αυτοματοποιημένη στατική ανάλυση της σουίτας test, που συγχωνεύεται με τα αποτελέσματα των test κατά τον χρόνο εκτέλεσης των test.
    • Αυτό μπορεί να επιτευχθεί με ανάλυση AST ή με ανάλυση κειμένου