Jak napisać test sprawdzający łączenie imienia i nazwiska

0

Witam,
postanowiłem poduczyć się testów i robię takie proste zadanie, ale nie wiem do czego przyrównać assert? Proszę o pomoc w poprawnym zdefiniowaniu asserta, dziękuję bardzo :)

# Write a Python program that accepts the user's first and last name and prints them in reverse order with a space between them

def get_name(Name, Sumrname):
    data_list = [Name, Sumrname]
    data_list.reverse()
    data = data_list[0] + ' ' + data_list[1]
    print(data)

name = input('Give your name > ')
surname = input('Give your surname > ')

assert get_name(name, surname) == 'Jan' + ' ' + 'Kowalski'  # Tutaj mam problem z porównaniem
2

get_name nic nie zwraca, a ma tylko skutki uboczne (wypisanie tekstu), nie będzie łatwo tego przetestować.

Po drugie w testach dane testowe są zadane z góry, a nie pobierane od użytkownika.

To nie z assertem jest tu problem.

0
Saalin napisał(a):

get_name nic nie zwraca, a ma tylko skutki uboczne (wypisanie tekstu), nie będzie łatwo tego przetestować.

Po drugie w testach dane testowe są zadane z góry, a nie pobierane od użytkownika.

To nie z assertem jest tu problem.

Dzięki za podpowiedź, w sumie to mój drugi assert ;) z matmą łatwiej mi poszło ;)

2

W zasadzie Twoja funkcja jest źle napisana i można ją zrefaktoryzować. Oczywiście test da się napisać:

from unittest.mock import patch

def get_name(name, surname):
  print(f"{surname} {name}")

@patch('builtins.print')
def test_get_name(mock_print):
    get_name("Jan", "Kowalski")
    mock_print.assert_called_with("Kowalski Jan")

Edit:
Ponieważ ten kod wywołał dyskusję, a odpowiedź została zaakceptowana to warto dodać komentarz. Ten test oczywiście działa, ale nie jest dobrą praktyką, bo ujawnia szczegóły implementacji samej funkcji. Test zawiera informację, że funkcja get_name zawiera funkcję print, a więc jest zależność między testem a bieżącą implementacją.

0
Pyxis napisał(a):

W zasadzie Twoja funkcja jest źle napisana i można ją zrefaktoryzować. Oczywiście test da się napisać:

from unittest.mock import patch

def get_name(name, surname):
  print(f"{surname} {name}")

@patch('builtins.print')
def test_get_name(mock_print):
    get_name("Jan", "Kowalski")
    mock_print.assert_called_with("Kowalski Jan")

Bardzo dziękuję za pomoc :)

1
Pyxis napisał(a):

W zasadzie Twoja funkcja jest źle napisana i można ją zrefaktoryzować. Oczywiście test da się napisać:

from unittest.mock import patch

def get_name(name, surname):
  print(f"{surname} {name}")

@patch('builtins.print')
def test_get_name(mock_print):
    get_name("Jan", "Kowalski")
    mock_print.assert_called_with("Kowalski Jan")

Można, ale po co, skoro naturalnie można zwrócić wartość i ją testować? Autor nie napisał w życiu jednego testu, a dostaje i akceptuje odpowiedź z jakąś patologiczną praktyką, która normalnie nawet by do głowy nie przyszła. Zamiast pisać funkcje jak człowiek to będzie teraz mockował print...

Jeśli już szukać gdzieś problemu to może tutaj, a nie w tym, że nie użył f-stringa.

0
Saalin napisał(a):
Pyxis napisał(a):

W zasadzie Twoja funkcja jest źle napisana i można ją zrefaktoryzować. Oczywiście test da się napisać:

from unittest.mock import patch

def get_name(name, surname):
  print(f"{surname} {name}")

@patch('builtins.print')
def test_get_name(mock_print):
    get_name("Jan", "Kowalski")
    mock_print.assert_called_with("Kowalski Jan")

Można, ale po co, skoro naturalnie można zwrócić wartość i ją testować? Autor nie napisał w życiu jednego testu, a dostaje i akceptuje odpowiedź z jakąś patologiczną praktyką, która normalnie nawet by do głowy nie przyszła. Zamiast pisać funkcje jak człowiek to będzie teraz mockował print...

Jeśli już szukać gdzieś problemu to może tutaj, a nie w tym, że nie użył f-stringa.

Może i tak, ale przynajmniej "Pyxis" napisał kod z którym mogę coś się pouczyć, a nie mało konstrukcyjna odpowiedź z informacjami które już wiem ;), tylko muszę nauczyć się pracować ze stringami bo int float czy liczby zespolone to banał w oganięciu.

4

Nooo, tylko że to nie wygląda jak dobry test, będzie Ci utrudniał refaktorowanie tego kodu w przyszłości.

Zamiast próbować uczyć się pisać testy po to żeby był, lepiej by było gdybyś znalazł jakiś tutorial, który nie skupia się na technicznych aspektach (jak np assert), tylko na tym jak pisać dobre testy, tak żeby nie zapędzić się test-hell.

Jeśli faktycznie masz zadanie które brzmi, Write a Python program that accepts the user's first and last name and prints them in reverse order with a space between them

to jego implementacja faktycznie mogłaby wyglądać tak:

def get_name(Name, Sumrname):
    data_list = [Name, Sumrname]
    data_list.reverse()
    data = data_list[0] + ' ' + data_list[1]
    print(data)

name = input('Give your name > ')
surname = input('Give your surname > ')
get_name(name, surname)

Ale test nie powinien wyglądać tak jak to zostało zaproponowane, powinno to wyglądać jakoś tak:

application_input = "Jan\nKowalski"
application_output = execute(application_input)
assert application_output == "Kowalski Jan"

Przy czym execute() to byłaby funkcja która wsadza wejście na standard input, i sprawdza standard output. W ten sposób napiszesz testy, który faktycznie testuje to co robi program (czyli input() i print()).

Jeśli nie chcesz tego robić, to możesz napisać test tylko pod get_name(), ale wtedy powinieneś zabrać z niej print() i nie mockować go w ogóle. Czyli musisz się zdecydować: zjeść ciastko lub mieć ciastko. Czyli albo piszesz test który bierze print() oraz input() na poważnie, i testuje je jak się powinno testować (czyli stubując stdin oraz stdout), albo nie testujesz ich w ogóle i wołasz samą get_name(). Dodawanie takich cudów jak @patch('builtins.print') jest dosłownie najgorszym wyjściem, które przyniesie Ci tylko nieszczęście.

Jeśli napiszesz test który robi mock_print.assert_called_with("Kowalski Jan"), to nie będziesz mógł potem zrefaktorować print() na np dwa printy, albo w zasadzie na nic innego bez zmiany testu - a to znaczy że test stanie się rigid, nie odporny na zmiany - nie rób tego.

2

Na tym etapie tak naprawdę to nawet funkcji nie potrzebujesz, a Ty jeszcze pytasz o test.

Zauważ, że Twoja funkcja zaciemnia kod jaki zawiera pod sobą.

Typowy Pythonista wolałby zobaczyć f-stringa, niż odwołanie do kolejnej pobocznej funkcji. Rozumiem, gdyby funkcja robiła coś specyficznego, coś nad czym warto się zatrzymać na jakiś czas, by podkreślić, ale jeśli operacja jest trywialna to przepalasz zarówno swój jak i czyjś czas. Wolałbyś widzieć w kodzie: add(10, 20) czy 10 + 20 ?

Załóżmy, że funkcja ma jednak robi coś w specyficzny sposób. No to pierw warto ocenić czy to zachowanie da ograniczyć do tego co w rzeczywistości warto testować. Jeśli masz opcję napisać funkcję bez input/print, lecz produkującą i zwracającą wynik to warto z tego skorzystać.

Mocki / stuby warto, ale w przegranej sytuacji np. gdy testujesz interakcję, ale z jakiś względów nie możesz jej przeprowadzić.

0
assert lacz ("Jan", "Nowak") == "Jan Nowak"
0

funkcja, którą chcesz testować ma efekty uboczne (print) i jak tam nie ma return to domyślnie jest return None. To po pierwsze, a po drugie dla takich funkcji możesz na moment zmienić standardowe wyjście, np. tak:

import sys
from io import StringIO

def get_name(Name, Sumrname):
    data_list = [Name, Sumrname]
    data_list.reverse()
    data = data_list[0] + ' ' + data_list[1]
    print(data) # sep='' ?

name = input('Give your name > ')
surname = input('Give your surname > ')

oldout = sys.stdout
buf = StringIO()
sys.stdout = buf

######################
get_name(name, surname)
######################

sys.stdout = oldout
buf.seek(0)
napis = buf.read()

assert napis == 'Kowalski' + ' ' + 'Jan' + '\n' # \n dla print bez sep=''

wygodniej będzie jak ta funkcja nie będzie zawierała printa, tylko zwracała napis return data, a wywołujący (np. tester) zrobi z nią co zechce.

2
znowutosamo napisał(a):

Na tym etapie tak naprawdę to nawet funkcji nie potrzebujesz, a Ty jeszcze pytasz o test.

Zauważ, że Twoja funkcja zaciemnia kod jaki zawiera pod sobą.

Typowy Pythonista wolałby zobaczyć f-stringa, niż odwołanie do kolejnej pobocznej funkcji. Rozumiem, gdyby funkcja robiła coś specyficznego, coś nad czym warto się zatrzymać na jakiś czas, by podkreślić, ale jeśli operacja jest trywialna to przepalasz zarówno swój jak i czyjś czas. Wolałbyś widzieć w kodzie: add(10, 20) czy 10 + 20 ?

No, polemizowałbym.

Funkcje, nawet trywialne, jeśli mają odpowiednią nazwę mogą być zasadne, np do zmniejszenia poziomów abstrakcji, do wyjaśnienia czemu te dwie rzeczy są dodawane; dobra nazwa funkcji może być bardziej wymowna niż operatory. Np kod return 105 / 195 *195; jest trywialne, ale mało czytelne. Lepsze byłoby np bmi(195, 105) a dodatkowo możesz dodać named arguments, bmi(height=195, weight=105).

1 użytkowników online, w tym zalogowanych: 0, gości: 1