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

Jak napisać test sprawdzający łączenie imienia i nazwiska
Mr Coin
  • Rejestracja:ponad 2 lata
  • Ostatnio:około 2 lata
  • Postów:72
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 :)

Kopiuj
# 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
edytowany 1x, ostatnio: Riddle
SA
  • Rejestracja:około 12 lat
  • Ostatnio:około 2 godziny
  • Postów:1431
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.

edytowany 1x, ostatnio: Saalin
Mr Coin
  • Rejestracja:ponad 2 lata
  • Ostatnio:około 2 lata
  • Postów:72
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 ;)

edytowany 1x, ostatnio: Riddle
Pyxis
  • Rejestracja:ponad 7 lat
  • Ostatnio:około 19 godzin
2

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

Kopiuj
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ą.

edytowany 3x, ostatnio: Pyxis
Mr Coin
  • Rejestracja:ponad 2 lata
  • Ostatnio:około 2 lata
  • Postów:72
0
Pyxis napisał(a):

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

Kopiuj
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 :)

SA
  • Rejestracja:około 12 lat
  • Ostatnio:około 2 godziny
  • Postów:1431
1
Pyxis napisał(a):

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

Kopiuj
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.

edytowany 1x, ostatnio: Saalin
Mr Coin
  • Rejestracja:ponad 2 lata
  • Ostatnio:około 2 lata
  • Postów:72
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ć:

Kopiuj
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.

Pyxis
Po prostu zamiast print(f"{surname} {name}") wstaw return f"{surname} {name}" i wtedy możesz użyć asercji, podobnie jak to zrobiłeś w 1. poście.
Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:około 2 godziny
  • Lokalizacja:Laska, z Polski
  • Postów:10056
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:

Kopiuj
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:

Kopiuj
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.

edytowany 3x, ostatnio: Riddle
Zobacz pozostałe 4 komentarze
Riddle
@Mr Coin: A jakiś przykład gdzie kodu jest więcej niż 10 linijek?
Mr Coin
Na razie z testów nie mam więcej, na obecną chwilę "ćwiczę je sobie" ale robię dwie duże apki na Portfolio (są na git Hub) (Scrapping oraz Front) i tu planuję zrobić jakieś profesjonalne testy.
Mr Coin
Co do Front będę Backend w Django robił i tam planuję testować kod
Riddle
@Mr Coin: Nie wychodzą Ci testy prostych funkcji, a chcesz się zabrać za pisanie testów pod framework?
Mr Coin
Ogólnie jestem po Bootcamp i Djagno - nawet zaawansowane, wiem że daleko do znajomości praktycznej ale małymi krokami do przodu, dyplom w Django zrobiłem ale fakt testów było 4h w porównaniu do D3 gdzie było ok 140h, więc testy słabo i muszę sam to ogarnąć.
ZN
ZN
  • Rejestracja:około 2 lata
  • Ostatnio:prawie 2 lata
  • Postów:65
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ć.

Mr Coin
Dziękuję za radę :)
Manna5
  • Rejestracja:prawie 6 lat
  • Ostatnio:około 23 godziny
  • Lokalizacja:Kraków
  • Postów:639
0
Kopiuj
assert lacz ("Jan", "Nowak") == "Jan Nowak"

Zdrowia życzę.
JV
  • Rejestracja:ponad 6 lat
  • Ostatnio:około 2 miesiące
  • Postów:242
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:

Kopiuj
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.

LukeJL
Tylko sposób, który podałeś, to overengineering i hakierka, żeby tylko przykryć wadliwą już od początku implementację. Z czego myślę, że zdajesz sobie sprawę, skoro piszesz 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., jednak tak jak @Saalin napisał 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...
Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:około 2 godziny
  • Lokalizacja:Laska, z Polski
  • Postów:10056
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).

edytowany 1x, ostatnio: Riddle
BG
Zapis return 105/195*195 jest nie tylko nieczytelny ale też błędny;) Oczywiście jeśli mialby służyć do liczenia BMI
ZN
znowutosamo
Tylko zwróć uwagę, że odnoszę się do konkretnego przypadku, a Ty czytasz to tak jakby regułę / zasadę. Trochę nielogiczne podejście.
Riddle
Wolałbyś widzieć w kodzie: add(10, 20) czy 10 + 20 ah tak?
BG
Albo: vec1*vec2 czy dot_product(vec1, vec2) ?
ZN
znowutosamo
nazwy są OK jeśli coś podkreślają, jeśli ich znaczenie określa zakres stosowanej operacji. Jeśli nazwa nic nie podkreśla wtedy jest szumem. Przykład z bmi już ze swojej definicji ma znaczenie, osoba widząc wzór i widząc nazwę zrozumie o co chodzi. Natomiast nie każda operacja wymaga nazwy. Zauważ, że jak zwiększasz wartość o 1 to nie robisz dla niej osobnej funkcji, tak samo jak pobierasz pierwszy element z kolekcji to nie robisz na siłę w języku dedykowanej funkcji o nazwie first(values), bo w pythonie lepiej czyta się values[0].
ZN
znowutosamo
powiązany z tym pobocznym wątkiem jest warty uwagi post: https://stuartsierra.com/2015/08/10/clojure-donts-redundant-map
Riddle
Zgadzam się że czasem lepsza jest funkcja z odpowiednią nazwą, a czasem nie. Odebrałem Twój post tak jakbyś mówił że zawsze trywialne funkcje są pase i lepiej je zinline'ować.
ZN
znowutosamo
Jak już wcześniej wspomniałem, pisałem o konkretnym przypadku. Oczywiście możesz odebrać to jak chcesz, ale nim zaczniesz kogoś poprawiać, spróbuj pierw zapytać. Nasze doświadczenia mogą się różnić.
LukeJL
@Bartłomiej Golenko to jest problematyczne, bo jeśli oznaczymy vec1 * vec2 jako iloczyn skalarny, to w jaki sposób oznaczymy iloczyn wektorowy? więc może lepiej mieć funkcje dot i cross.
Riddle
@LukeJL: No przecież jemu właśnie o to chodzi :> Że lepiej mieć funkcje.
Riddle
@znowutosamo: I tym konkretnym przypadkiem o którym mówisz było co konkretnie? Przykład autora postu z imieniem, czy ta funkcja add()?
ZN
znowutosamo
@Riddle odwrócenie imienia, ale add służyło jako przykład, aby zobrazować, że wyprowadzanie nazw na pewnym etapie traci swój sens.
Riddle
@znowutosamo: No to to "zobrazowanie" było nietrafione moim zdaniem, bo może sugerować że wyciąganie takich operacji do funkcji traci sens - co nie jest prawdą.
ZN
znowutosamo
Twoim zdaniem.. Zwróć uwagę, że nie tylko Ty tutaj siedzisz i Twoje zdanie niekoniecznie może się liczyć. Rozumiem, że masz pociąg do niekończących się dyskusji, aby Twoje zdanie było na górze, ale proszę odpuść czasami, bo to żenująco wygląda. Tak samo jak prywatne proszenie bym zmienił słowo jakim określam Twoje zachowanie. Powinieneś zrobić sobie dzień przerwy od internetu albo wyjść na spacer.
Riddle
Prywatna wiadomość żebyś usunął z komentarza wulgaryzm była jako moderator.
ZN
znowutosamo
Jako moderator przewrażliwiony na punkcie własnej osoby :D
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)