Εισαγωγή σε δοκιμές βάσει ακινήτων στην Python

Σε αυτό το άρθρο θα μάθουμε μια μοναδική και αποτελεσματική προσέγγιση στη δοκιμή που ονομάζεται δοκιμή βάσει ιδιοκτησίας. Θα χρησιμοποιήσουμε Python , pytest και Hypothesis για να εφαρμόσουμε αυτήν την προσέγγιση δοκιμών.

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

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

Κάθε μεγάλο μαγικό τέχνασμα αποτελείται από τρία μέρη ή πράξεις. Το πρώτο μέρος ονομάζεται "The Pledge". Ο μάγος σας δείχνει κάτι συνηθισμένο : μια τράπουλα, ένα πουλί ή έναν άνδρα. Σας δείχνει αυτό το αντικείμενο. Ίσως σας ζητά να το ελέγξετε για να δείτε εάν είναι πράγματι πραγματικό, αμετάβλητο, φυσιολογικό. Αλλά φυσικά… μάλλον δεν είναι.

Μέρος 1: Δοκιμή βάσει παραδείγματος

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

  • δοθεί μια δοκιμαστική είσοδος I
  • όταν περάσει στη λειτουργία υπό δοκιμή
  • θα πρέπει να επιστρέψει μια έξοδο O

Έτσι, βασικά δίνουμε μια σταθερή είσοδο και αναμένουμε μια σταθερή έξοδο.

Για να κατανοήσουμε αυτήν την έννοια με απλούς όρους:

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

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

  1. πάρτε ένα μπλε πλαστικό ακατέργαστο ( σταθερά δεδομένα δοκιμής )
  2. τροφοδοτήστε το πλαστικό στο μηχάνημα
  3. περιμένετε μια μπλε πλαστική μπάλα ως έξοδο ( σταθερή έξοδος δοκιμής )

Ας δούμε την ίδια προσέγγιση με προγραμματικό τρόπο.

Προαπαιτούμενο: βεβαιωθείτε ότι έχετε εγκαταστήσει το Python (έκδοση 2.7 ή παραπάνω) και το pytest .

Δημιουργήστε μια δομή καταλόγου ως εξής:

- demo_tests/ - test_example.py

Θα γράψουμε μια μικρή λειτουργία sumμέσα στο αρχείο test_example.py. Αυτό δέχεται δύο αριθμούς - num1και num2 - ως παραμέτρους και επιστρέφει ως αποτέλεσμα την προσθήκη και των δύο αριθμών.

def sum(num1, num2): """It returns sum of two numbers""" return num1 + num2

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

import pytest
#make sure to start function name with testdef test_sum(): assert sum(1, 2) == 3

Εδώ μπορείτε να δείτε ότι, περνάμε τις δύο τιμές 1και 2και περιμένουν το ποσό επιστροφής 3.

Εκτελέστε τις δοκιμές διασχίζοντας το demo_testsφάκελο και, στη συνέχεια, εκτελέστε την ακόλουθη εντολή:

pytest test_example.py -v

Είναι αρκετός αυτός ο έλεγχος για να επαληθευτεί η λειτουργικότητα της sumσυνάρτησης;

Ίσως σκέφτεστε, φυσικά όχι. Θα γράψουμε περισσότερες δοκιμές χρησιμοποιώντας τη pytest parametrizeδυνατότητα που θα εκτελέσει αυτήν τη test_sumλειτουργία για όλες τις δεδομένες τιμές.

import pytest
@pytest.mark.parametrize('num1, num2, expected',[(3,5,8), (-2,-2,-4), (-1,5,4), (3,-5,-2), (0,5,5)])def test_sum(num1, num2, expected): assert sum(num1, num2) == expected

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

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

Έτσι, ανακαλύψαμε το πρώτο σημείο πόνου με αυτήν τη μέθοδο δοκιμής:

Θέμα 1: Η πληρότητα των δοκιμών εξαρτάται από το άτομο που γράφει το τεστ

Μπορεί να επιλέξουν να γράψουν 5 ή 50 ή 500 δοκιμαστικές θήκες, αλλά εξακολουθούν να μην είναι σίγουροι αν έχουν καλύψει με ασφάλεια τις περισσότερες, αν όχι όλες, τις ακραίες θήκες.

Αυτό μας φέρνει στο δεύτερο σημείο του πόνου μας:

Τεύχος 2 - Μη ισχυρές δοκιμές λόγω ασαφούς / διφορούμενης κατανόησης απαιτήσεων

Όταν μας είπαν να γράψουμε τη sumλειτουργία μας , ποιες συγκεκριμένες λεπτομέρειες μεταφέρθηκαν;

Μας είπαν:

  • τι είδους είσοδο πρέπει να αναμένει η λειτουργία μας;
  • πώς πρέπει να συμπεριφέρεται η λειτουργία μας σε απρόσμενα σενάρια εισόδου;
  • τι είδους έξοδο πρέπει να επιστρέψει η λειτουργία μας;

Για να είμαστε πιο ακριβείς, αν λάβετε υπόψη τη sumλειτουργία που έχουμε γράψει παραπάνω:

  • ξέρουμε εάν num1, num2πρέπει να είναι intή float; Μπορούν επίσης να σταλούν ως τύποι string ή άλλοι τύποι δεδομένων;
  • ποια είναι η ελάχιστη και μέγιστη τιμή num1και num2που πρέπει να υποστηρίξουμε;
  • πώς πρέπει να συμπεριφέρεται η συνάρτηση εάν λαμβάνουμε nullεισόδους;
  • θα πρέπει η έξοδος επιστρέφεται από τη συνάρτηση αθροίσματος είναι intή floatή stringή οποιοδήποτε άλλο τύπο δεδομένων;
  • σε ποια σενάρια πρέπει να εμφανίζει μηνύματα σφάλματος;

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

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

def sum(num1, num2): """Buggy logic""" if num1 == 3 and num2 == 5: return 8 elif num1 == -2 and num2 == -2 : return -4 elif num1 == -1 and num2 == 5 : return 4 elif num1 == 3 and num2 == -5: return -2 elif num1 == 0 and num2 == 5: return 5

Τώρα ας βυθίσουμε σε δοκιμές βάσει ιδιοκτησίας για να δούμε πώς αυτά τα σημεία πόνου μετριάζονται εκεί.

Η δεύτερη πράξη ονομάζεται "The Turn". Ο μάγος παίρνει το συνηθισμένο κάτι και το κάνει να κάνει κάτι εξαιρετικό. Τώρα ψάχνετε το μυστικό… αλλά δεν θα το βρείτε, γιατί φυσικά δεν ψάχνετε πραγματικά. Δεν θέλετε πραγματικά να μάθετε. Θέλετε να ξεγελαστείτε.

Μέρος 2: Δοκιμή βάσει ιδιοκτησίας

Εισαγωγή και δοκιμή δημιουργίας δεδομένων

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

Τα πλαίσια δοκιμών βάσει ιδιοκτησίας ελέγχουν την αλήθεια των ιδιοτήτων. Μια ιδιότητα είναι μια δήλωση όπως: για όλους (x, y,…) όπως η προϋπόθεση (x, y,…) διατηρεί την ιδιότητα (x, y,…) είναι αλήθεια .

To understand this let’s go back to our plastic ball generating machine example.

The property based testing approach of that machine will be:

  1. take a huge selection of plastics as input (all(x, y, …))
  2. make sure all of them are colored (precondition(x, y, …))
  3. the output satisfies following property (property(x, y, …)) -
  • output is round/spherical in shape
  • output is colored
  • color of the output is one of the colors present in color band

Notice how from fixed values of input and output we have generalized our test data and output in such a way that the property should hold true for all the valid inputs. This is property-based testing.

Also, notice that when thinking in terms of properties we have to think harder and in a different way. Like when we came up with the idea that since our output is a ball it should be round in shape, another question will strike you - whether the ball should be hollow or solid?

So, by making us think harder and question more about the requirement, the property-based testing approach is making our implementation of the requirement robust.

Now, let’s return to our sum function and test it by using the property-based approach.

The first question which arises here is: what should be the input of the sum function?

For the scope of this article we will assume that any pair of integers from the integer set is a valid input.

So, any set of integer values lying in the above coordinate system will be a valid input to our function.

The next question is: how to get such input data?

The answer to this is: a property-based testing library provides you the feature to generate huge set of desired input data following a precondition.

In Python, Hypothesis is a property-testing library which allows you to write tests along with pytest. We are going to make use of this library.

The entire documentation of Hypothesis is beautifully written and available ➡️ hereand I recommend you to go through it.

To install Hypothesis:

pip install hypothesis

and we are good to use hypothesis with pytest.

Now, let’s rewrite test_sum function — which we wrote earlier — with new data sets generated by Hypothesis.

from hypothesis import given
import hypothesis.strategies as st
import pytest
@given(st.integers(), st.integers())def test_sum(num1, num2): assert sum(num1, num2) == num1 + num2
  • The first line simply imports given from Hypothesis. The @given decorator takes our test function and turns it into a parametrized one. When called, this will run the test function over a wide range of matching data. This is the main entry point to Hypothesis.
  • The second line imports strategies from Hypothesis. strategies provides the feature to generate test data. Hypothesis provides strategies for most built-in types with arguments to constrain or adjust the output. As well, higher-order strategies can be composed to generate more complex types.
  • You can generate any or mix of the following things using strategies:
'nothing','just', 'one_of','none','choices', 'streaming','booleans', 'integers', 'floats', 'complex_numbers', 'fractions','decimals','characters', 'text', 'from_regex', 'binary', 'uuids','tuples', 'lists', 'sets', 'frozensets', 'iterables','dictionaries', 'fixed_dictionaries','sampled_from', 'permutations','datetimes', 'dates', 'times', 'timedeltas','builds','randoms', 'random_module','recursive', 'composite','shared', 'runner', 'data','deferred','from_type', 'register_type_strategy', 'emails'
  • Here we have generated integers()set using strategies and passed it to @given.
  • So, our test_sum function should run for all the iterations of given input.

Let’s run it and see the result.

You might be thinking, I can’t see any difference here. What’s so special about this run?

Well, to see the magical difference, we need to run our test by setting the verbose option. Don’t confuse this verbose with the -v option of pytest.

from hypothesis import given, settings, Verbosity
import hypothesis.strategies as stimport pytest
@settings(verbosity=Verbosity.verbose)@given(st.integers(), st.integers())def test_sum(num1, num2): assert sum(num1, num2) == num1 + num2

settings allows us to tweak the default test behavior of Hypothesis.

Now let’s re-run the tests. Also include -s this time to capture the stream output in pytest.

pytest test_example.py -v -s

Look at the sheer number of test-cases generated and run. You can find all sorts of cases here, such as 0, large numbers, and negative numbers.

You might be thinking, it’s impressive, but I can’t find my favorite test case pair (1,2 ) here. What if I want that to run?

Well, fear not, Hypothesis allows you to run a given set of test cases every time if you want by using the @example decorator.

from hypothesis import given, settings, Verbosity, example
import hypothesis.strategies as stimport pytest
@settings(verbosity=Verbosity.verbose)@given(st.integers(), st.integers())@example(1, 2)def test_sum(num1, num2): assert sum(num1, num2) == num1 + num2

Also, notice that each run will always generate a new jumbled up test case following the test generation strategy, thus randomizing the test run.

So, this solves our first pain point- the exhaustiveness of test cases.

Thinking hard to come up with properties to test

So far, we saw one magic of property-based testing which generates desired test data on the fly.

Now let’s come to the part where we need to think hard and in a different way to create such tests which are valid for all test inputs but unique to sum function.

1 + 0 = 10 + 1 = 15 + 0 = 5-3 + 0 = -38.5 + 0 = 8.5

Well, that’s interesting. It seems like adding 0 to a number results in the same number as sum. This is called the identity property of addition.

Let’s see one more:

2 + 3 = 53 + 2 = 5
5 + (-2) = 3-2 + 5 = 3

It looks like we found one more unique property. In addition the order of parameters doesn’t matter. Placed left or right of the + sign they give the same result. This is called the commutative property of addition.

There is one more, but I want you to come up with it.

Now, we will re-write our test_sum to test these properties:

from hypothesis import given, settings, Verbosity
import hypothesis.strategies as stimport pytest
@settings(verbosity=Verbosity.verbose)@given(st.integers(), st.integers())def test_sum(num1, num2): assert sum(num1, num2) == num1 + num2
 # Test Identity property assert sum(num1, 0) = num1 #Test Commutative property assert sum(num1, num2) == sum(num2, num1)

Η δοκιμή μας είναι τώρα εξαντλητική - έχουμε επίσης μετατρέψει τις δοκιμές για να τις κάνουμε πιο ισχυρές. Έτσι, λύσαμε το δεύτερο σημείο του πόνου μας: μη ισχυρές περιπτώσεις δοκιμών .

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

Όπως λέει μια παλιά παροιμία - με ξεγελάς μια φορά, ντροπή σε σένα, με ξεγελάς δύο φορές, ντροπή για μένα.

Μπορείτε να δείτε ότι εντοπίστηκε σφάλμα. Falsifying example: test_sum(num1=0, num2=0). Αυτό σημαίνει απλώς ότι η αναμενόμενη ιδιότητά μας δεν ισχύει για αυτά τα ζεύγη δοκιμαστικών περιπτώσεων, άρα η αποτυχία.

Αλλά δεν θα χτυπήσετε ακόμα. Επειδή η εξαφάνιση κάτι δεν αρκεί. πρέπει να το επαναφέρετε. Αυτός είναι ο λόγος για τον οποίο κάθε μαγικό κόλπο έχει μια τρίτη πράξη, το πιο δύσκολο μέρος, το κομμάτι που ονομάζουμε «Το Prestige».

Μέρος 3: Συρρίκνωση αποτυχιών

Shrinking is the process by which Hypothesis tries to produce human-readable examples when it finds a failure. It takes a complex example and turns it into a simpler one.

To demonstrate this feature, let’s add one more property to our test_sum function which says num1 should be less than or equal to 30.

from hypothesis import given, settings, Verbosity
import hypothesis.strategies as stimport pytest
@settings(verbosity=Verbosity.verbose)@given(st.integers(), st.integers())def test_sum(num1, num2): assert sum(num1, num2) == num1 + num2
 # Test Identity property assert sum(num1, 0) = num1 #Test Commutative property assert sum(num1, num2) == sum(num2, num1) assert num1 <= 30

After running this test, you will get an interesting output log on the terminal here:

collected 1 item
test_example.py::test_sum Trying example: test_sum(num1=0, num2=-1)Trying example: test_sum(num1=0, num2=-1)Trying example: test_sum(num1=0, num2=-29696)Trying example: test_sum(num1=0, num2=0)Trying example: test_sum(num1=-1763, num2=47)Trying example: test_sum(num1=6, num2=1561)Trying example: test_sum(num1=-24900, num2=-29635)Trying example: test_sum(num1=-13783, num2=-20393)
#Till now all test cases passed but the next one will fail
Trying example: test_sum(num1=20251, num2=-10886)assert num1 <= 30AssertionError: assert 20251 <= 30
#Now the shrinking feature kicks in and it will try to find the simplest value for which the test still fails
Trying example: test_sum(num1=0, num2=-2)Trying example: test_sum(num1=0, num2=-1022)Trying example: test_sum(num1=-165, num2=-29724)Trying example: test_sum(num1=-14373, num2=-29724)Trying example: test_sum(num1=-8421504, num2=-8421376)Trying example: test_sum(num1=155, num2=-10886)assert num1 <= 30AssertionError: assert 155 <= 30
# So far it has narrowed it down to 155
Trying example: test_sum(num1=0, num2=0)Trying example: test_sum(num1=0, num2=0)Trying example: test_sum(num1=64, num2=0)assert num1 <= 30AssertionError: assert 64 <= 30
# Down to 64
Trying example: test_sum(num1=-30, num2=0)Trying example: test_sum(num1=0, num2=0)Trying example: test_sum(num1=0, num2=0)Trying example: test_sum(num1=31, num2=0)
# Down to 31
Trying example: test_sum(num1=-30, num2=0)Falsifying example: test_sum(num1=31, num2=0)FAILED
# And it finally concludes (num1=31, num2=0) is the simplest test data for which our property doesn't hold true.

One more good feature — its going to remember this failure for this test and will include this particular test case set in the future runs to make sure that the same regression doesn’t creep in.

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

Μπορείτε να βρείτε ολόκληρο τον κωδικό που χρησιμοποιείται εδώ στο my; repo github.

Αν σας άρεσε το περιεχόμενο δείξτε μερικά ❤️