Doctest Whitespace

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Vega
User
Beiträge: 28
Registriert: Sonntag 29. Januar 2017, 12:03

Hallo,

ich habe folgendes Problem:

Ich bekomme bei meinem Programm in der Console folgende Ausgabe:

999

Wenn ich einen Docktest für das Programm schreibe erhalte ich:

Expected:
Hier steht ein Tab bzw. Whitespaces 999
Got:
999
BlackJack

@Vega: Ein Tab sollte da nicht stehen, und halt auch nicht zu viele Leerzeichen.
Vega
User
Beiträge: 28
Registriert: Sonntag 29. Januar 2017, 12:03

Ja, das wundert mich ja gerade, weiß jemand was da passiert ist bzw. was ich machen kann?

>>> counter([1, 5, 6])
156

das ist z.B. eine meiner Doctests

und jetzt steht da:
Test failed..

Expected:
hier wieder die Leerzeichen 156
Got:
156

Also vor dem erwarteten Wert, werden einfach Lesezeichen eingefügt..
BlackJack

@Vega: Die werden da nicht einfach eingefügt, die werden da schon so stehen. Zeig doch mal ein komplettes Beispiel das man nachvollziehen kann. Also einen kompletten Code auf den man Doctest loslassen kann, wo dieses Problem auftritt.

Wenn ich zum Beispiel das hier habe:

Code: Alles auswählen

def counter(items):
    """
    >>> counter([1, 5, 6])
    156
    """
    return int(''.join(map(str, items)))
Hat `doctest` kein Problem damit:
[codebox=text file=Unbenannt.txt]$ python -m doctest -v forum3.py
Trying:
counter([1, 5, 6])
Expecting:
156
ok
1 items had no tests:
forum3
1 items passed all tests:
1 tests in forum3.counter
1 tests in 2 items.
1 passed and 0 failed.
Test passed.[/code]
Vega
User
Beiträge: 28
Registriert: Sonntag 29. Januar 2017, 12:03

Code: Alles auswählen

value = int(''.join(str(k) for k in lst))
print("%i" % value, end='\r')
davor ist noch eine Schleife, aber das tut hier, denk ich, nix zur Sache.

nun habe ich wie oben, den Doctest.

Du sagst ja, dass die Leerzeichen da stehen, aber das Expected geb ich ja ein, also müsste ich die Leerzeichen hingeschrieben haben, was ich jedoch nicht habe.

Das Programm gibt die richtige Ausgabe, nur das Expected des Tests ist falsch oder verstehe ich da was falsch?

Danke im voraus =)
Zuletzt geändert von Anonymous am Mittwoch 7. Juni 2017, 16:36, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
__deets__
User
Beiträge: 14540
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das ist doch schon wieder kein kompletter Code, den man selbst durch einen Doctest jagen kann. Insofern - doch, das was davor ist, tut zur Sache. Mit irgendwelchen Codeschnipseln die noch nicht mal selbststaendig lauffaehig sind kann hier niemand etwas anfangen.
BlackJack

@Vega: In Deinem Doctest wird eine Funktion `counted()` aufgerufen. In dem Quelltext sehe ich diese Funktion nicht. Und gibt Deine Funktion etwas zurück oder gibt sie etwas aus? Wenn Du versuchst die Ausgabe zu prüfen, dann muss der Vergleichswert genau so aussehen — ich frage mich gerade wie man einen Wagenrücklauf so normal mit einem Editor in die Datei bekommt, ohne ein Zeilenende und Doctest so etwas überhaupt sinnvoll prüfen könnte.
Vega
User
Beiträge: 28
Registriert: Sonntag 29. Januar 2017, 12:03

Code: Alles auswählen

class Counter:
    """

    >>> counter = Counter([9, 9, 9])
    >>> counter.increment()
    >>> counter.count
    [0, 0, 1]
    >>> counter.count = [0, 9, 9]
    >>> counter.increment()
    >>> counter.count
    [1, 0, 0]
    >>> counter.increment()
    >>> counter.count
    [1, 0, 1]
    >>> counter = Counter([2, 3, 4])
    >>> counter.count = [2, 3, 4]
    >>> counter.increment()
    >>> counter.count
    [0, 0, 1]

    """

    def __init__(self, lst):
        self.count = [0] * len(lst)
        self.lst = lst

    def increment(self):

        self.count[len(self.count) - 1] += 1
        for i in range(len(self.count) - 1, -1, -1):
            if self.count[i] > self.lst[i]:
                self.count[i] = 0
                self.count[i - 1] += 1
            else:
                break


def maximum(lst):
    """
    Increments the counter while until the counter has reached the maximum.
    :param lst: list of maximums for the counter
    :return: all counter values until the maximum

    >>> maximum([1, 1, 1])
    111

    >>> maximum([2, 1 ,0])
    210

    >>> maximum([1, 1 ,0])
    110

    """

    counter = Counter(lst)
    for i in range(0, len(lst)):
        while counter.count[i] != lst[i]:
            counter.increment()
            # print the current counter values
            countervalues = int(''.join(str(k) for k in counter.count))
            print("%i" % countervalues, end='\r')

Der Doctest des Codes gibt ja den richtigen Wert, nur den Wert, den ich eingebe, der ist falsch.
Also wird es doch nicht am Code liegen oder sehe ich das falsch?
BlackJack

@Vega: Das unterscheidet sich durch deutlich mehr als rein durch Whitespace. Ich bekomme da:
[codebox=text file=Unbenannt.txt]$ python3 -m doctest Dropbox/forum2.py
**********************************************************************
File "Dropbox/forum2.py", line 44, in forum2.maximum
Failed example:
maximum([1, 1, 1])
Expected:
111
Got:
111 1
**********************************************************************
File "Dropbox/forum2.py", line 47, in forum2.maximum
Failed example:
maximum([2, 1 ,0])
Expected:
210
Got:
210 10
**********************************************************************
File "Dropbox/forum2.py", line 50, in forum2.maximum
Failed example:
maximum([1, 1 ,0])
Expected:
110
Got:
110 10
**********************************************************************
1 items had failures:
3 of 3 in forum2.maximum
***Test Failed*** 3 failures.[/code]
Das beim ``Got:`` am Anfang keine Leerzeichen stehen, dürfte daran liegen, dass Du explizit einen Wagenrücklauf ohne Zeilenvorschub ausgibst.

Bei der `maximum()`-Funktion stimmt der dokumentierte Rückgabewert nicht. Die Funktion gibt *nichts* zurück. Die gibt nur etwas aus.

Bei den Doctests stimmen zudem die Erwartungen nicht, denn wenn man das tatsächlich aufruft, dann müsste bei drei Stellen am Ende ja immer 999 da stehen und nicht der Startwert. Allerdings würde die Funktion diese Zahlen alle übereinander ausgeben, also immer die letzte Ausgabe überschreiben. Das sind aber alles Daten die ausgegeben werden, also von Doctest auch berücksichtigt werden. Dummerweise wird man das so nicht in die Textdatei schreiben können. Jedenfalls nicht mit jedem Editor. Und wie das dann im Texteditor und anderswo (zum Beispiel hier im Forum) angezeigt würde, wäre sicher auch spannend. Zudem möchte man diese ganzen Daten sicher nicht eintippen.

`Counter.count` sollte besser `Counter.digits` heissen, denn das sind ja Ziffern des `Counter`. Und `lst` sagt ja gar nichts darüber aus was der Wert bedeutet. `max_digits` wäre vielleicht eine Idee.

Nach 999 folgt 001? Ich hätte da ja 000 erwartet.

Ob die `maximum()`-Funktion tatsächlich das macht was sie soll, wage ich zu bezweifeln.

Das Umwandeln in Zeichenkette und Zahl würde ich in die `Counter`-Klasse verlegen. `__str__()` und `__int__()` bieten sich da geradezu an. Falls der Test *eigentlich* testen soll ob der `Counter` sein Maximum erreicht hat, dann würde ich den Test auch als Methode in die Klasse verschieben.
Vega
User
Beiträge: 28
Registriert: Sonntag 29. Januar 2017, 12:03

Code: Alles auswählen


class Counter:
    """
    >>> counter = Counter([9, 9, 9])
    >>> counter.increment()
    >>> counter.count
    [0, 0, 1]
    >>> counter.count = [0, 9, 9]
    >>> counter.increment()
    >>> counter.count
    [1, 0, 0]
    >>> counter.increment()
    >>> counter.count
    [1, 0, 1]
    >>> counter = Counter([2, 3, 4])
    >>> counter.count = [2, 3, 4]
    >>> counter.increment()
    >>> counter.count
    [0, 0, 0]

    """

    def __init__(self, lst):
        self.count = [0] * len(lst)
        self.lst = lst

    def increment(self,):

        if self.count == self.lst:
            self.count = [0] * len(self.lst)
        else:
            self.count[len(self.count) - 1] += 1
            for i in range(len(self.count) - 1, -1, -1):
                if self.count[i] > self.lst[i]:
                    self.count[i] = 0
                    self.count[i - 1] += 1
                else:
                    break


def maximum(lst):
    """
    Increments the counter while until the counter has reached the maximum.
    :param lst: list of maximums for the counter
    :return: all counter values until the maximum

    >>> maximum([1, 1, 1])
    111

    >>> maximum([2, 1 ,0])
    210

    >>> maximum([1, 1 ,0])
    110

    """

    counter = Counter(lst)
    for i in range(0, len(lst)):
        while counter.count[i] != lst[i]:
            counter.increment()
            # print the current counter values
            countervalues = int(''.join(str(k) for k in counter.count))
            print("%i" % countervalues, end='\r')

if __name__ == '__main__':
    maximum([99, 99, 99])
Danke, habe die increment Methode verändert, nun zählt sie nach z. B. 999 auf 000.
Und die Maximum Funktion, bekommt eine Liste, welche das Maximum angibt, diese Funktion zählt nun von 0 hoch, bis dieser Wert erreicht wurde.
Wie würdest du den die Zahlen in Zeichenketten umwandeln in der Klasse?
Und wie sorge ich dafür, dass es mir diesen Zeilenvorschub bei 'Got' ausgibt.

Ich erhalte bei den Test folgendes:

Code: Alles auswählen

Failed example:
    maximum([1, 1, 1])
Expected:
    111
Got:
111
BlackJack

@Vega: Umwandeln in Zeichenkette geht in der Klasse genau so wie ausserhalb. Wenn man `__str__()` und `__int__()` implementiert, hat man halt den Vorteil, dass man dem Standardweg folgt wie man Objekte in eine Zeichenkettendarstellung oder eine ganze Zahl umwandelt. Man kann die Objekte dann beispielsweise einfach mit `print()` ausgeben, oder in mit der `format()`-Methode in eine Zeichenkette formatieren wenn das Objekt selbst weiss wie es in eine Zeichenkette umgewandelt werden kann.

Mindestens zusätzlich zu `increment()` würde ich das erhöhen des Zählers auch unter dem Namen `__next__()` zur Verfügung stellen. Dann kann man `Counter`-Objekte auch in ``for``-Schleifen verwenden.

Der Doctest wird mit diesem Code gar nicht gehen, weil Du den erwarteten Text nicht eingeben kannst. Der ist ja nicht 110, sondern '0\r1\r2\r3\r4\r…\r110\r' aber eben nicht als Zeichenkettenliteral, sondern wirklich mit diesen Steuerzeichen im Quelltext. Selbst bei Editoren mit denen man das eingeben *kann* (Vim zum Beispiel), *möchte* man das sicher nicht, denn man müsste da ja 110 Zahlen eintippen. Und *lesen* möchte das sicher niemand. Zumal da auch das Problem der Darstellung dazu kommt. Im Editor, und vielleicht auch in generierten HTML, PDF, ePub, … Dokumenten.

Problematisch ist die Vermischung von Programmlogik und Benutzerinteraktion. Wenn man beispielsweise eine Generatorfunktion hätte, die alle Werte als Elemente liefert statt sie auszugeben, dann könnte man das recht einfach prüfen, mit einem Docstring wie dem hier beispielsweise:

Code: Alles auswählen

>>> xs = list(maximum([1, 1, 0]))
>>> xs[:12]
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11']
>>> xs[98:102]
['98', '99', '100', '101']
>>> xs[-1]
'110'
Ansonsten müsste man die Funktion aus den Doctests halt einfach ausnehmen.

Edit: `maximum()` ist wirklich an sehr schlechter Name für die Funktion!
Vega
User
Beiträge: 28
Registriert: Sonntag 29. Januar 2017, 12:03

Okay, vielen Dank :wink:
Antworten