Przejdź do głównej treści
INF.04-02-25.06-SGCzerwiec 2025 · 30 kroków · ok. 86 min · 36 kryteriów z klucza

Rozwiązanie krok po kroku: Szyfr Cezara w aplikacjach (Python + Tkinter)

Rozwiązanie dotyczy arkusza z szyfrem Cezara w wersji Python + Tkinter - cały egzamin w jednym języku. W konsoli funkcja szyfrująca tekst przesunięciem liter o klucz (z zawijaniem alfabetu, kluczem ujemnym i większym niż 26), w okienku Tkintera ten sam szyfr z przyciskiem „Zaszyfruj”, polem wyniku z zaokrąglonymi rogami i zapisem do pliku przez systemowe okno „Zapisywanie jako”, a do tego pięć testów jednostkowych z tabeli 4 w bibliotece unittest i dokument egzamin.

Kroki idą zdanie po zdaniu za treścią arkusza - każdy zaczyna się poleceniem z arkusza, a kod pokazuje dokładnie te linie, które je spełniają. Funkcja szyfrująca powstaje etapami, a po każdym etapie program jest uruchamiany - widać, na czym polega błąd wersji bez zawijania i dlaczego w Pythonie klucz ujemny „sam działa”. Na końcu dwa kroki porównują ten wariant z rozwiązaniem w C# i WPF (druga zakładka).

Co widać w podglądzie. Wyniki programu konsolowego i testów pochodzą z prawdziwego uruchomienia kodu z danego kroku (Python 3.12, unittest), a każdy wynik szyfrowania porównałem z niezależnym wzorcem. Okno to symulacja w przeglądarce, ale układ kontrolek pochodzi z prawdziwego Tkintera (program z kroku uruchomiony, położenie i rozmiar każdej kontrolki odczytane), a działanie przycisku „Zaszyfruj” sprawdziłem w symulacji i w prawdziwym oknie z tym samym wynikiem. Zapis do pliku sprawdziłem w prawdziwym oknie - plik zawiera zaszyfrowany tekst.

Jak liczą się punkty. Klucz ma 36 kryteriów (R.1.1 - R.4.10), każde za 1 punkt. Próg zdania części praktycznej to 75%, czyli 27 punktów - można stracić 9 kryteriów i nadal zdać. Przy każdym kroku jest napisane, które kryteria spełnia; egzaminator ocenia spełnienie kryteriów z klucza, nie zgodność z tym kodem.

  1. Etap 1: Przygotowanie

    Krok 1 · po tym etapie masz 0 z 36 kryteriów (pkt)

    Krok 1: Folder zdającego i trzy podfoldery

    ok. 2 min
    Polecenie z arkusza

    Utwórz folder i nazwij go numerem zdającego. W folderze utwórz podfoldery: konsolowa, desktopowa, testy.

    Na pulpicie zakładasz folder o nazwie z numerem PESEL, a w nim konsolowa, desktopowa i testy. W tym arkuszu zrzuty ekranu trafiają do folderu danej aplikacji (nie do osobnego folderu dokumentacji), a plik egzamin - bezpośrednio do folderu z numerem zdającego. Arkusz nie ma archiwum z materiałami.

  2. Etap 2: Aplikacja konsolowa

    Kroki 2-8 · po tym etapie masz 17 z 36 kryteriów (pkt)

    Krok 2: Projekt konsola w PyCharm i funkcja main

    ok. 3 min
    Polecenie z arkusza

    Za pomocą narzędzi do tworzenia aplikacji konsolowych utwórz program implementujący algorytm szyfru Cezara. Zastosowany obiektowy język programowania zgodny z zainstalowanym na stanowisku egzaminacyjnym: C++ lub C#, lub Java, lub Python. Podejście obiektowe lub strukturalne.

    Wybierasz Pythona i podejście strukturalne (same funkcje). W PyCharm: New Project, nazwa konsola, plik main.py.

    Program główny od razu zamykasz w funkcji main() wywoływanej pod warunkiem if __name__ == "__main__":. Tu to szczególnie ważne: w części III plik z testami zaimportuje z main.py funkcję szyfrującą. Import wykonuje cały plik - bez tego warunku testy najpierw zatrzymałyby się na pytaniu o tekst do zaszyfrowania. Warunek jest prawdziwy tylko wtedy, gdy plik uruchamiasz, a nie importujesz.

    konsola/main.py Python nowy plik
    def main():
        pass
    
    
    if __name__ == "__main__":
        main()
    Wynik programu po tym kroku
    > python main.py
  3. Krok 3: Funkcja encrypt: dwa parametry, pętla po literach

    Kryteria: , , , ,

    R.2.1 Algorytm (nie musi być poprawny) jest zaimplementowany w funkcji lub metodzie zwracającej wartość typu napisowego

    R.2.2 Metoda/funkcja przyjmuje dwa parametry wejściowe typu napisowego i całkowitego, wartości tych parametrów zostały wczytane z klawiatury. Dopuszcza się w podejściu obiektowym metodę bezparametrową, jeśli tekst jawny i klucz są polami klasy

    R.2.3 Metoda/funkcja zawiera pętlę działającą na wszystkich literach tekstu jawnego (wejściowego)

    R.1.3 Użyte polskie lub angielskie nazewnictwo metod / funkcji. Nazewnictwo jest znaczące

    R.1.5 Zastosowane typy pasują do problemu, np. int, string

    ok. 3 min
    Polecenie z arkusza

    Szyfrowanie odbywa się w osobnej metodzie lub funkcji, która zawiera dwa argumenty - tekst jawny oraz klucz - i zwraca tekst zaszyfrowany.

    Szkielet funkcji: parametry text i key (R.2.2), wynik zwracany przez return - napis budowany w zmiennej result (R.2.1, R.1.5). Pętla for letter in text przechodzi przez każdy znak tekstu (R.2.3) i na razie dokleja go do wyniku bez zmian - funkcja zwraca tekst taki sam jak na wejściu.

    konsola/main.py Python zmienione: 7 linii
    def encrypt(text, key):
        result = ""
        for letter in text:
            result += letter
        return result
    
    
    def main():
        pass
    
    
    if __name__ == "__main__":
        main()

    Wynik programu bez zmian - taki jak po kroku 2.

  4. Krok 4: Program główny: tekst i klucz z klawiatury

    Kryteria:

    R.1.7 Program podejmuje zrozumiałą komunikację z użytkownikiem: komunikaty są znaczące, np. "Podaj tekst", "Podaj klucz"

    ok. 3 min
    Polecenie z arkusza

    Tekst oraz klucz są wczytywane z klawiatury w programie głównym. Sprawdź działanie metody lub funkcji. Program powinien nawiązać czytelną komunikację z użytkownikiem.

    input(...) wypisuje pytanie i zwraca wpisany tekst - tekst do szyfrowania zostaje napisem, a klucz zamieniasz na liczbę całkowitą przez int(...) (R.2.2). Wynik funkcji wypisujesz z komunikatem (R.1.7). Podgląd to prawdziwe uruchomienie: funkcja jeszcze nie szyfruje, więc „abc” z kluczem 3 wychodzi bez zmian - to dobry moment, żeby sprawdzić, że program działa i rozmawia z użytkownikiem.

    konsola/main.py Python zmienione: 3 linie
    def encrypt(text, key):
        result = ""
        for letter in text:
            result += letter
        return result
    
    
    def main():
        text = input("Podaj tekst do zaszyfrowania: ")
        key = int(input("Podaj klucz: "))
        print("Tekst zaszyfrowany: " + encrypt(text, key))
    
    
    if __name__ == "__main__":
        main()
    Wynik programu po tym kroku
    > python main.py
    Podaj tekst do zaszyfrowania: abc
    Podaj klucz: 3
    Tekst zaszyfrowany: abc
  5. Krok 5: Przesunięcie litery o klucz, spacja bez zmian

    Kryteria: , ,

    R.2.4 Gdy klucz = 0, tekst nie jest zmieniany. Znak spacji nie jest szyfrowany

    R.2.5 Algorytm dla każdej litery tekstu wejściowego przesuwa ją o wartość klucza

    R.1.2 Kod zapisany z wcięciami dla zagnieżdżeń bloków

    ok. 3 min
    Polecenie z arkusza

    Szyfr Cezara polega na zamianie każdej litery tekstu jawnego na literę tekstu szyfrowanego przesuniętego w alfabecie o stałą liczbę znaków zwaną kluczem. Znak spacji pozostaje w zaszyfrowanym tekście bez zmian.

    W Pythonie znak i jego kod to dwie różne rzeczy, a zamieniają je dwie funkcje: ord("a") zwraca kod znaku (97), a chr(97) - znak o danym kodzie. chr(ord(letter) + key) przesuwa więc literę o klucz (R.2.5). Spację przepisujesz bez zmian (R.2.4).

    Podgląd: „abc” z kluczem 3 to „def” - dobrze. Ale „xyz” z kluczem 3 daje „{|}” - za literą z w tablicy znaków stoją nawiasy klamrowe, a nie znowu a. Brakuje zawijania alfabetu.

    Typowe błędy

    result += letter + key to błąd TypeError - w Pythonie do napisu nie da się dodać liczby. Trzeba przejść przez kod znaku: chr(ord(letter) + key).

    konsola/main.py Python zmienione: 4 linie
    def encrypt(text, key):
        result = ""
        for letter in text:
            if letter == " ":
                result += " "
            else:
                result += chr(ord(letter) + key)
        return result
    
    
    def main():
        text = input("Podaj tekst do zaszyfrowania: ")
        key = int(input("Podaj klucz: "))
        print("Tekst zaszyfrowany: " + encrypt(text, key))
    
    
    if __name__ == "__main__":
        main()
    Wynik programu po tym kroku
    > python main.py
    Podaj tekst do zaszyfrowania: abc
    Podaj klucz: 3
    Tekst zaszyfrowany: def
    
    > python main.py
    Podaj tekst do zaszyfrowania: xyz
    Podaj klucz: 3
    Tekst zaszyfrowany: {|}
  6. Krok 6: Zawijanie alfabetu resztą z dzielenia

    Kryteria:

    R.2.6 Algorytm przy przesunięciu poza ostatnią/pierwszą literę alfabetu powoduje zawijanie (powrót do jego początku/końca) . Metoda dowolna, np. warunki, modulo, lub inne

    ok. 4 min
    Polecenie z arkusza

    Przykładowo dla klucza k = 3, każdą literę alfabetu z tabeli 1 należy zamienić odpowiednią literą z tabeli 2. Natomiast dla klucza k = -3, każdą literę alfabetu z tabeli 1 należy zamienić odpowiednią literą z tabeli 3.

    Liczysz na numerach liter jak w tabeli 1 (a = 0, ..., z = 25):

    • ord(letter) - ord("a") - numer litery,
    • (position + key) % 26 - przesunięcie z zawijaniem: reszta z dzielenia przez 26 sprowadza wynik do zakresu 0-25, więc po z (25) przychodzi znowu a (0) (R.2.6),
    • chr(ord("a") + shifted) - z numeru z powrotem litera.

    Podgląd: „xyz” z kluczem 3 to „abc”, a „def” z kluczem -3 - „abc” (odszyfrowanie, tabela 3).

    konsola/main.py Python zmienione: 3 linie
    def encrypt(text, key):
        result = ""
        for letter in text:
            if letter == " ":
                result += " "
            else:
                position = ord(letter) - ord("a")
                shifted = (position + key) % 26
                result += chr(ord("a") + shifted)
        return result
    
    
    def main():
        text = input("Podaj tekst do zaszyfrowania: ")
        key = int(input("Podaj klucz: "))
        print("Tekst zaszyfrowany: " + encrypt(text, key))
    
    
    if __name__ == "__main__":
        main()
    Wynik programu po tym kroku
    > python main.py
    Podaj tekst do zaszyfrowania: xyz
    Podaj klucz: 3
    Tekst zaszyfrowany: abc
    
    > python main.py
    Podaj tekst do zaszyfrowania: def
    Podaj klucz: -3
    Tekst zaszyfrowany: abc
  7. Krok 7: Klucz ujemny, zero i większy niż długość alfabetu

    Kryteria: , , , ,

    R.2.7 Szyfr działa poprawnie dla klucza większego od długości alfabetu (np. klucz = klucz % 26 lub inna metoda )

    R.2.4 Gdy klucz = 0, tekst nie jest zmieniany. Znak spacji nie jest szyfrowany

    R.2.9 Po wprowadzeniu tekstu jawnego i wartości klucza w konsoli jest wyświetlany zaszyfrowany tekst (na zrzucie i obowiązkowo w kodzie)

    R.1.1 Kod źródłowy zapisany w sposób czytelny: instrukcje w osobnych liniach, stosowane spacje pomiędzy operatorami, konsekwentnie stosowana wybrana konwencja dla nawiasów klamrowych instrukcji blokowej

    R.1.4 Użyte polskie lub angielskie nazewnictwo pól lub zmiennych. Nazewnictwo jest znaczące. Wyjątkami od reguły są zmienne bufor, tmp, iteratory pętli. Kryterium nie jest spełnione tylko wtedy, gdy zmienne nie istnieją lub ich nazwy nic nie znaczą, np. x, tab, tablica, foo, kolekcja

    ok. 3 min
    Polecenie z arkusza

    Klucz szyfrowania jest liczbą całkowitą (wartości dodatnie i ujemne, może być większy od 26). Dla klucza k = 0 tekst jawny jest równy tekstowi zaszyfrowanemu.

    Tu w Pythonie nie trzeba dopisywać ani linii - i warto wiedzieć dlaczego. Operator % w Pythonie przy dodatnim dzielniku zwraca zawsze resztę nieujemną: -29 % 26 daje 23, a 29 % 26 - 3. Wyrażenie (position + key) % 26 sprowadza więc do zakresu 0-25 każdy klucz: ujemny, zero i większy niż 26 (R.2.7). W C#, Javie i C++ jest inaczej - tam -29 % 26 to -3 i trzeba osobnej poprawki (w wariancie C# robią to key % 26 i dodane 26).

    Podgląd to trzy uruchomienia, o które prosi arkusz: klucz 29 (jak 3), -29 (jak -3) i 0 - tekst bez zmian (R.2.4, R.2.9).

    Typowe błędy

    Przeniesienie wzoru do C# albo Javy bez zmian ((position + key) % 26) psuje szyfrowanie z kluczem ujemnym - tam reszta z dzielenia liczby ujemnej jest ujemna. Pamiętaj o tym, jeśli przesiadasz się z Pythona.

    Wynik programu po tym kroku
    > python main.py
    Podaj tekst do zaszyfrowania: abc
    Podaj klucz: 29
    Tekst zaszyfrowany: def
    
    > python main.py
    Podaj tekst do zaszyfrowania: abc
    Podaj klucz: -29
    Tekst zaszyfrowany: xyz
    
    > python main.py
    Podaj tekst do zaszyfrowania: ala ma kota
    Podaj klucz: 0
    Tekst zaszyfrowany: ala ma kota
  8. Krok 8: Zrzuty konsola1 - konsola3 i folder konsolowa

    Kryteria: , ,

    R.1.6 Podjęta próba uruchomienia kodu, co jest udokumentowane zrzutem przedstawiającym uruchomiony program lub jego kompilację

    R.2.8 Program uruchamia się w konsoli, co jest udokumentowane zrzutem ekranu

    R.4.8 Zapisany przynajmniej jeden zrzut ekranu z uruchomienia lub kompilacji aplikacji konsolowej, na zrzucie widoczne jest środowisko, w którym powstała aplikacja

    ok. 4 min
    Polecenie z arkusza

    Po uruchomieniu programu wykonaj zrzuty ekranu dokumentujące interakcje programu dla różnych wartości klucza (0, ujemna, dodatnia). Zrzuty zapisz w folderze konsolowa pod nazwami konsola1.png, konsola2.png, itd. W folderze konsolowa powinno znaleźć się archiwum całego projektu o nazwie konsola.zip, skopiowany z projektu plik z kodem źródłowym programu oraz plik wykonywalny, jeżeli istnieje.

    Trzy uruchomienia w PyCharm i zrzut całego ekranu ze środowiskiem: klucz 0 (konsola1), ujemny (konsola2), dodatni (konsola3) - zrzuty w folderze konsolowa (R.1.6, R.2.8, R.4.8). W tym samym folderze: konsola.zip z całym projektem (po dopisaniu testów z części III - są w tym samym projekcie) i main.py. Pliku wykonywalnego Python nie tworzy.

    Wynik programu po tym kroku
    > python main.py
    Podaj tekst do zaszyfrowania: ala ma kota
    Podaj klucz: 0
    Tekst zaszyfrowany: ala ma kota
    
    > python main.py
    Podaj tekst do zaszyfrowania: def
    Podaj klucz: -3
    Tekst zaszyfrowany: abc
    
    > python main.py
    Podaj tekst do zaszyfrowania: xyz
    Podaj klucz: 3
    Tekst zaszyfrowany: abc
  9. Etap 3: Aplikacja desktopowa

    Kroki 9-17 · po tym etapie masz 28 z 36 kryteriów (pkt)

    Krok 9: Projekt desktopowa: okno Tkinter

    ok. 3 min
    Polecenie z arkusza

    Za pomocą dostępnego na stanowisku egzaminacyjnym środowiska programistycznego wykonaj aplikację desktopową będącą interfejsem graficznym programu utworzonego w konsoli.

    Okno budujesz biblioteką Tkinter (jest w standardowej instalacji Pythona). W PyCharm drugi projekt, desktopowa, z plikiem main.py: import tkinter as tk, okno główne root = tk.Tk() i na końcu root.mainloop() - pętla zdarzeń, która czeka na kliknięcia aż do zamknięcia okna. Wygląd okna zapisujesz w kodzie, kontrolka po kontrolce.

    desktopowa/main.py Python nowy plik
    import tkinter as tk
    
    root = tk.Tk()
    
    root.mainloop()
    Okno aplikacji po tym kroku

    Symulacja ekranu w przeglądarce - w Android Studio / Visual Studio wygląd może się nieco różnić.

  10. Krok 10: Tytuł, rozmiar i tło okna

    Kryteria: ,

    R.3.1 Utworzona aplikacja desktopowa z tytułem okna "Szyfrowanie. Wykonane przez" dalej wstawiony numer zdającego, aplikacja zawiera co najmniej jedną kontrolkę wynikającą z treści zadania

    R.3.3 Zastosowane kolory tła: CadetBlue (#5F9EA0) dla okna, LightBlue (#ADD8E6) dla przycisków

    ok. 2 min
    Polecenie z arkusza

    Tytuł okna „Szyfrowanie. Wykonane przez ”, dalej wstawiony numer zdającego. Kolor tła okna CadetBlue (#5F9EA0).

    title z arkusza - na egzaminie po „Wykonane przez” wpisujesz numer PESEL, tu stoi Bartosz Bryniarski (R.3.1). geometry("985x555") - rozmiar w pikselach jak na obrazie 1. Kolor tła zapisujesz w stałej BACKGROUND - w Tkinterze każda etykieta i ramka ma własne tło, więc ten kolor wpiszesz jeszcze kilka razy (R.3.3).

    desktopowa/main.py Python zmienione: 5 linii
    import tkinter as tk
    
    BACKGROUND = "#5F9EA0"
    
    root = tk.Tk()
    root.title("Szyfrowanie. Wykonane przez Bartosz Bryniarski")
    root.geometry("985x555")
    root.configure(bg=BACKGROUND)
    
    root.mainloop()
    Okno aplikacji po tym kroku

    Symulacja ekranu w przeglądarce - w Android Studio / Visual Studio wygląd może się nieco różnić.

  11. Krok 11: Lewa kolumna: napis i pole klucza

    Kryteria: ,

    R.3.2 W oknie zawarte: trzy napisy, pole edycyjne i wielowierszowe, pole do wypisywania szyfru i dwa przyciski

    R.3.4 Zastosowane kolory czcionki: AntiqueWhite (#FAEBD7) napisów (etykiet) , AliceBlue (#F0F8FF) dla pola z tekstem zaszyfrowanym

    ok. 3 min
    Polecenie z arkusza

    Kontrolki rozmieszczone zgodnie z obrazem 1, z określonym formatowaniem: napisy „Podaj wartość klucza”, „Podaj tekst”, „Tekst zaszyfrowany” w kolorze czcionki AntiqueWhite (#FAEBD7). Pole edycyjne do wprowadzania wartości klucza.

    Okno ma trzy kolumny: pola po lewej, przycisk „Zaszyfruj” pośrodku, wynik po prawej. Kolumny układa siatka grid okna, a w lewej kolumnie stoi ramka left (tk.Frame), w której kontrolki leżą jedna pod drugą - tu wygodniejszy jest menedżer pack. sticky="n" przykleja ramkę do góry komórki.

    Etykieta dostaje kolor tekstu fg=LABEL_COLOR (AntiqueWhite, R.3.4), większą czcionkę (font=("Segoe UI", 15) - krój i rozmiar w punktach) i tło okna. Pod nią małe pole klucza tk.Entry o szerokości 8 znaków - pack bez anchor stawia je na środku, jak na obrazie 1 (R.3.2).

    desktopowa/main.py Python zmienione: 8 linii
    import tkinter as tk
    
    BACKGROUND = "#5F9EA0"
    LABEL_COLOR = "#FAEBD7"
    LABEL_FONT = ("Segoe UI", 15)
    
    root = tk.Tk()
    root.title("Szyfrowanie. Wykonane przez Bartosz Bryniarski")
    root.geometry("985x555")
    root.configure(bg=BACKGROUND)
    
    left = tk.Frame(root, bg=BACKGROUND)
    left.grid(row=0, column=0, padx=(20, 0), sticky="n")
    tk.Label(left, text="Podaj wartość klucza", font=LABEL_FONT, fg=LABEL_COLOR, bg=BACKGROUND).pack(anchor="w", pady=(10, 0))
    key_entry = tk.Entry(left, width=8)
    key_entry.pack(pady=(15, 0))
    
    root.mainloop()
    Okno aplikacji po tym kroku

    Symulacja ekranu w przeglądarce - w Android Studio / Visual Studio wygląd może się nieco różnić.

  12. Krok 12: Pole wielowierszowe na tekst

    Kryteria:

    R.3.2 W oknie zawarte: trzy napisy, pole edycyjne i wielowierszowe, pole do wypisywania szyfru i dwa przyciski

    ok. 2 min
    Polecenie z arkusza

    Pole edycyjne wielowierszowe do wprowadzania tekstu z włączonym zawijaniem wierszy.

    tk.Entry mieści jeden wiersz - pole wielowierszowe to tk.Text. Rozmiar podajesz w znakach i wierszach (width=50, height=17), a wrap="word" włącza zawijanie: długi tekst przechodzi do następnego wiersza całymi słowami (R.3.2). Domyślnie Text pisze czcionką o stałej szerokości, więc ustawiasz zwykły krój. Druga etykieta „Podaj tekst” w tym samym stylu.

    desktopowa/main.py Python zmienione: 3 linie
    import tkinter as tk
    
    BACKGROUND = "#5F9EA0"
    LABEL_COLOR = "#FAEBD7"
    LABEL_FONT = ("Segoe UI", 15)
    
    root = tk.Tk()
    root.title("Szyfrowanie. Wykonane przez Bartosz Bryniarski")
    root.geometry("985x555")
    root.configure(bg=BACKGROUND)
    
    left = tk.Frame(root, bg=BACKGROUND)
    left.grid(row=0, column=0, padx=(20, 0), sticky="n")
    tk.Label(left, text="Podaj wartość klucza", font=LABEL_FONT, fg=LABEL_COLOR, bg=BACKGROUND).pack(anchor="w", pady=(10, 0))
    key_entry = tk.Entry(left, width=8)
    key_entry.pack(pady=(15, 0))
    tk.Label(left, text="Podaj tekst", font=LABEL_FONT, fg=LABEL_COLOR, bg=BACKGROUND).pack(anchor="w", pady=(25, 0))
    text_box = tk.Text(left, width=50, height=17, wrap="word", font=("Segoe UI", 10))
    text_box.pack(pady=(20, 0))
    
    root.mainloop()
    Okno aplikacji po tym kroku

    Symulacja ekranu w przeglądarce - w Android Studio / Visual Studio wygląd może się nieco różnić.

  13. Krok 13: Pole wyniku: ramka z zaokrąglonymi rogami

    Kryteria: ,

    R.3.2 W oknie zawarte: trzy napisy, pole edycyjne i wielowierszowe, pole do wypisywania szyfru i dwa przyciski

    R.3.4 Zastosowane kolory czcionki: AntiqueWhite (#FAEBD7) napisów (etykiet) , AliceBlue (#F0F8FF) dla pola z tekstem zaszyfrowanym

    ok. 4 min
    Polecenie z arkusza

    Pole, w którym wyświetlony jest zaszyfrowany tekst o cechach: kolor czcionki AliceBlue (#F0F8FF), obramowanie: linia ciągła koloru AntiqueWhite (#FAEBD7), rogi zaokrąglone.

    Kontrolki Tkintera nie mają zaokrąglonych rogów - takie pole trzeba narysować. Służy do tego tk.Canvas, płótno, na którym tworzy się figury i napisy:

    • funkcja rounded_rectangle rysuje wielokąt (create_polygon) z punktów ułożonych wokół prostokąta, a smooth=True wygładza go krzywymi - narożniki stają się łukami. To znany przepis, który warto mieć w głowie,
    • outline=LABEL_COLOR, width=3 - linia ciągła AntiqueWhite grubości 3 pikseli,
    • create_text wstawia napis: fill=RESULT_COLOR to kolor tekstu (AliceBlue, R.3.4), anchor="nw" - punkt (30, 30) jest lewym górnym rogiem napisu, width=360 - po tylu pikselach tekst się zawija.

    create_text zwraca numer elementu na płótnie - zapisujesz go w result_text, bo po nim podmienisz tekst wyniku. highlightthickness=0 usuwa ramkę zaznaczenia wokół płótna (R.3.2).

    Typowe błędy

    tk.Label(..., relief="solid", bd=3) też daje ramkę wokół tekstu, ale z prostymi rogami i w kolorze tekstu etykiety - kolor ramki i zaokrąglenie wymagają rysowania na Canvas.

    desktopowa/main.py Python zmienione: 17 linii
    import tkinter as tk
    
    BACKGROUND = "#5F9EA0"
    LABEL_COLOR = "#FAEBD7"
    RESULT_COLOR = "#F0F8FF"
    LABEL_FONT = ("Segoe UI", 15)
    
    
    def rounded_rectangle(canvas, x1, y1, x2, y2, radius, **options):
        points = [x1 + radius, y1, x2 - radius, y1, x2, y1, x2, y1 + radius,
                  x2, y2 - radius, x2, y2, x2 - radius, y2, x1 + radius, y2,
                  x1, y2, x1, y2 - radius, x1, y1 + radius, x1, y1]
        return canvas.create_polygon(points, smooth=True, **options)
    
    
    root = tk.Tk()
    root.title("Szyfrowanie. Wykonane przez Bartosz Bryniarski")
    root.geometry("985x555")
    root.configure(bg=BACKGROUND)
    
    left = tk.Frame(root, bg=BACKGROUND)
    left.grid(row=0, column=0, padx=(20, 0), sticky="n")
    tk.Label(left, text="Podaj wartość klucza", font=LABEL_FONT, fg=LABEL_COLOR, bg=BACKGROUND).pack(anchor="w", pady=(10, 0))
    key_entry = tk.Entry(left, width=8)
    key_entry.pack(pady=(15, 0))
    tk.Label(left, text="Podaj tekst", font=LABEL_FONT, fg=LABEL_COLOR, bg=BACKGROUND).pack(anchor="w", pady=(25, 0))
    text_box = tk.Text(left, width=50, height=17, wrap="word", font=("Segoe UI", 10))
    text_box.pack(pady=(20, 0))
    
    right = tk.Frame(root, bg=BACKGROUND)
    right.grid(row=0, column=2, sticky="n")
    tk.Label(right, text="Tekst zaszyfrowany", font=LABEL_FONT, fg=LABEL_COLOR, bg=BACKGROUND).pack(anchor="w", pady=(10, 0))
    result_canvas = tk.Canvas(right, width=420, height=345, bg=BACKGROUND, highlightthickness=0)
    result_canvas.pack(pady=(15, 0))
    rounded_rectangle(result_canvas, 3, 3, 417, 342, 50, outline=LABEL_COLOR, fill=BACKGROUND, width=3)
    result_text = result_canvas.create_text(30, 30, anchor="nw", width=360, text="", fill=RESULT_COLOR, font=("Segoe UI", 13))
    
    root.mainloop()
    Okno aplikacji po tym kroku

    Symulacja ekranu w przeglądarce - w Android Studio / Visual Studio wygląd może się nieco różnić.

  14. Krok 14: Przyciski Zaszyfruj i Zapisz szyfr w pliku

    Kryteria: , ,

    R.3.2 W oknie zawarte: trzy napisy, pole edycyjne i wielowierszowe, pole do wypisywania szyfru i dwa przyciski

    R.3.3 Zastosowane kolory tła: CadetBlue (#5F9EA0) dla okna, LightBlue (#ADD8E6) dla przycisków

    R.3.7 W stanie początkowym aplikacja wyświetla wszystkie kontrolki rozmieszczone zgodnie z obrazem 1 arkusza egzaminacyjnego (w uruchomionej aplikacji lub na zrzucie i obowiązkowo w kodzie)

    ok. 2 min
    Polecenie z arkusza

    Przyciski o kolorze tła LightBlue (#ADD8E6).

    „Zaszyfruj” stoi w środkowej kolumnie siatki okna (column=1), odsunięty od góry (pady=(300, 0)) na wysokość pola tekstu; padx=25 daje odstęp od kolumn obok. „Zapisz szyfr w pliku” stoi w prawej ramce pod polem wyniku, szerokość width=35 znaków. Oba z tłem LightBlue (R.3.3). Wygląd okna jest kompletny - porównaj z obrazem 1 (R.3.7).

    desktopowa/main.py Python zmienione: 6 linii
    import tkinter as tk
    
    BACKGROUND = "#5F9EA0"
    LABEL_COLOR = "#FAEBD7"
    RESULT_COLOR = "#F0F8FF"
    BUTTON_COLOR = "#ADD8E6"
    LABEL_FONT = ("Segoe UI", 15)
    
    
    def rounded_rectangle(canvas, x1, y1, x2, y2, radius, **options):
        points = [x1 + radius, y1, x2 - radius, y1, x2, y1, x2, y1 + radius,
                  x2, y2 - radius, x2, y2, x2 - radius, y2, x1 + radius, y2,
                  x1, y2, x1, y2 - radius, x1, y1 + radius, x1, y1]
        return canvas.create_polygon(points, smooth=True, **options)
    
    
    root = tk.Tk()
    root.title("Szyfrowanie. Wykonane przez Bartosz Bryniarski")
    root.geometry("985x555")
    root.configure(bg=BACKGROUND)
    
    left = tk.Frame(root, bg=BACKGROUND)
    left.grid(row=0, column=0, padx=(20, 0), sticky="n")
    tk.Label(left, text="Podaj wartość klucza", font=LABEL_FONT, fg=LABEL_COLOR, bg=BACKGROUND).pack(anchor="w", pady=(10, 0))
    key_entry = tk.Entry(left, width=8)
    key_entry.pack(pady=(15, 0))
    tk.Label(left, text="Podaj tekst", font=LABEL_FONT, fg=LABEL_COLOR, bg=BACKGROUND).pack(anchor="w", pady=(25, 0))
    text_box = tk.Text(left, width=50, height=17, wrap="word", font=("Segoe UI", 10))
    text_box.pack(pady=(20, 0))
    
    encrypt_button = tk.Button(root, text="Zaszyfruj", bg=BUTTON_COLOR)
    encrypt_button.grid(row=0, column=1, padx=25, pady=(300, 0), sticky="n")
    
    right = tk.Frame(root, bg=BACKGROUND)
    right.grid(row=0, column=2, sticky="n")
    tk.Label(right, text="Tekst zaszyfrowany", font=LABEL_FONT, fg=LABEL_COLOR, bg=BACKGROUND).pack(anchor="w", pady=(10, 0))
    result_canvas = tk.Canvas(right, width=420, height=345, bg=BACKGROUND, highlightthickness=0)
    result_canvas.pack(pady=(15, 0))
    rounded_rectangle(result_canvas, 3, 3, 417, 342, 50, outline=LABEL_COLOR, fill=BACKGROUND, width=3)
    result_text = result_canvas.create_text(30, 30, anchor="nw", width=360, text="", fill=RESULT_COLOR, font=("Segoe UI", 13))
    save_button = tk.Button(right, text="Zapisz szyfr w pliku", bg=BUTTON_COLOR, width=35)
    save_button.pack(pady=(25, 0))
    
    root.mainloop()
    Okno aplikacji po tym kroku

    Symulacja ekranu w przeglądarce - w Android Studio / Visual Studio wygląd może się nieco różnić.

  15. Krok 15: Zaszyfruj: funkcja z konsoli i klucz domyślnie 0

    Kryteria: , ,

    R.3.5 Zdefiniowana przynajmniej jedna metoda obsługująca kliknięcie przycisku (może być pusta)

    R.3.6 Logika aplikacji jest obsłużona przy wykorzystaniu metody / funkcji szyfrującej

    R.3.8 Po wprowadzeniu tekstu i klucza oraz wybraniu przycisku „Zaszyfruj” zostaje wypisany tekst zaszyfrowany (w uruchomionej aplikacji lub na zrzucie i obowiązkowo w kodzie)

    ok. 4 min
    Polecenie z arkusza

    Klucz szyfrowania i tekst szyfrowany są pobierane z odpowiednich kontrolek. Szyfrowanie odbywa się po wprowadzeniu tekstu i klucza i wybraniu przycisku „Zaszyfruj”. Jeżeli w polu przeznaczonym na wpisanie klucza nie została wpisana wartość liczbowa całkowita, po naciśnięciu przycisku, do szyfrowania, należy zastosować wartość klucza 0. Do wykonania zadania można posłużyć się funkcją / metodą z aplikacji konsolowej.

    Funkcję encrypt kopiujesz z konsoli bez zmian (R.3.6). Obsługa przycisku encrypt_clicked, przekazana mu w command=encrypt_clicked (R.3.5):

    • try / except ValueError - int(...) przy tekście, który nie jest liczbą całkowitą (puste pole, „trzy”, „3.5”), zgłasza błąd ValueError; zamiast przerywać, blok except ustawia klucz na 0 - dokładnie to, czego wymaga arkusz,
    • text_box.get("1.0", "end-1c") - cała treść pola Text: od wiersza 1, znaku 0, do końca bez ostatniego znaku nowej linii, który Text zawsze dokłada,
    • itemconfig(result_text, text=...) podmienia tekst elementu na płótnie (R.3.8).

    Wpisz w podglądzie klucz 3 i tekst „abc”, kliknij „Zaszyfruj” - „def” (obraz 2). Z kluczem „x” tekst zostaje bez zmian.

    Typowe błędy

    text_box.get() bez argumentów to błąd - Text wymaga zakresu („od”, „do”). A "1.0", "end" dołącza do tekstu znak nowej linii, który po zaszyfrowaniu zostanie na końcu wyniku.

    desktopowa/main.py Python zmienione: 23 linie
    import tkinter as tk
    
    BACKGROUND = "#5F9EA0"
    LABEL_COLOR = "#FAEBD7"
    RESULT_COLOR = "#F0F8FF"
    BUTTON_COLOR = "#ADD8E6"
    LABEL_FONT = ("Segoe UI", 15)
    
    
    def encrypt(text, key):
        result = ""
        for letter in text:
            if letter == " ":
                result += " "
            else:
                position = ord(letter) - ord("a")
                shifted = (position + key) % 26
                result += chr(ord("a") + shifted)
        return result
    
    
    def rounded_rectangle(canvas, x1, y1, x2, y2, radius, **options):
        points = [x1 + radius, y1, x2 - radius, y1, x2, y1, x2, y1 + radius,
                  x2, y2 - radius, x2, y2, x2 - radius, y2, x1 + radius, y2,
                  x1, y2, x1, y2 - radius, x1, y1 + radius, x1, y1]
        return canvas.create_polygon(points, smooth=True, **options)
    
    
    
    def encrypt_clicked():
        try:
            key = int(key_entry.get())
        except ValueError:
            key = 0
        plain_text = text_box.get("1.0", "end-1c")
        result_canvas.itemconfig(result_text, text=encrypt(plain_text, key))
    
    
    root = tk.Tk()
    root.title("Szyfrowanie. Wykonane przez Bartosz Bryniarski")
    root.geometry("985x555")
    root.configure(bg=BACKGROUND)
    
    left = tk.Frame(root, bg=BACKGROUND)
    left.grid(row=0, column=0, padx=(20, 0), sticky="n")
    tk.Label(left, text="Podaj wartość klucza", font=LABEL_FONT, fg=LABEL_COLOR, bg=BACKGROUND).pack(anchor="w", pady=(10, 0))
    key_entry = tk.Entry(left, width=8)
    key_entry.pack(pady=(15, 0))
    tk.Label(left, text="Podaj tekst", font=LABEL_FONT, fg=LABEL_COLOR, bg=BACKGROUND).pack(anchor="w", pady=(25, 0))
    text_box = tk.Text(left, width=50, height=17, wrap="word", font=("Segoe UI", 10))
    text_box.pack(pady=(20, 0))
    
    encrypt_button = tk.Button(root, text="Zaszyfruj", bg=BUTTON_COLOR, command=encrypt_clicked)
    encrypt_button.grid(row=0, column=1, padx=25, pady=(300, 0), sticky="n")
    
    right = tk.Frame(root, bg=BACKGROUND)
    right.grid(row=0, column=2, sticky="n")
    tk.Label(right, text="Tekst zaszyfrowany", font=LABEL_FONT, fg=LABEL_COLOR, bg=BACKGROUND).pack(anchor="w", pady=(10, 0))
    result_canvas = tk.Canvas(right, width=420, height=345, bg=BACKGROUND, highlightthickness=0)
    result_canvas.pack(pady=(15, 0))
    rounded_rectangle(result_canvas, 3, 3, 417, 342, 50, outline=LABEL_COLOR, fill=BACKGROUND, width=3)
    result_text = result_canvas.create_text(30, 30, anchor="nw", width=360, text="", fill=RESULT_COLOR, font=("Segoe UI", 13))
    save_button = tk.Button(right, text="Zapisz szyfr w pliku", bg=BUTTON_COLOR, width=35)
    save_button.pack(pady=(25, 0))
    
    root.mainloop()
    Okno aplikacji po tym kroku

    Symulacja ekranu w przeglądarce - w Android Studio / Visual Studio wygląd może się nieco różnić.

  16. Krok 16: Zapis szyfru w pliku przez okno systemowe

    Kryteria: , ,

    R.3.5 Zdefiniowana przynajmniej jedna metoda obsługująca kliknięcie przycisku (może być pusta)

    R.3.9 Po wybraniu przycisku "Zapisz w pliku" pojawia się okno zapisywania do pliku (w uruchomionej aplikacji lub na zrzucie i obowiązkowo w kodzie)

    R.3.10 W zapisanym pliku znajduje się tekst zwrócony funkcją szyfrującą niezależnie od jej poprawności

    ok. 4 min
    Polecenie z arkusza

    Po wybraniu przycisku „Zapisz szyfr w pliku”, zostanie otwarte systemowe okno dialogowe (obraz 3) do zapisu pliku. W pliku ma zostać zapisany zaszyfrowany tekst.

    Systemowe okno „Zapisywanie jako” otwiera filedialog.asksaveasfilename z modułu tkinter.filedialog (dopisujesz import). Funkcja zwraca pełną ścieżkę wybranego pliku albo pusty napis, gdy użytkownik kliknie „Anuluj” - stąd if path:. defaultextension dopisze .txt, gdy nazwa jest bez rozszerzenia, a filetypes podpowiada typ pliku (R.3.9).

    Zapis: with open(path, "w", encoding="utf-8") as file: otwiera plik do zapisu i sam go zamyka po wyjściu z bloku, a file.write(...) zapisuje tekst odczytany z płótna przez itemcget(result_text, "text") - to, co wypisała funkcja szyfrująca (R.3.10).

    W podglądzie okno zapisu jest uproszczone i niczego nie zapisuje na dysk; w prawdziwym oknie Tk sprawdziłem, że plik powstaje i zawiera zaszyfrowany tekst.

    Typowe błędy

    open(path, "w") bez encoding="utf-8" zapisze plik w kodowaniu systemu (na polskim Windows cp1250) - dla małych liter a-z nie ma różnicy, ale polskie litery w tekście jawnym dałyby inny plik niż w edytorze UTF-8.

    desktopowa/main.py Python zmienione: 10 linii
    import tkinter as tk
    from tkinter import filedialog
    
    BACKGROUND = "#5F9EA0"
    LABEL_COLOR = "#FAEBD7"
    RESULT_COLOR = "#F0F8FF"
    BUTTON_COLOR = "#ADD8E6"
    LABEL_FONT = ("Segoe UI", 15)
    
    
    def encrypt(text, key):
        result = ""
        for letter in text:
            if letter == " ":
                result += " "
            else:
                position = ord(letter) - ord("a")
                shifted = (position + key) % 26
                result += chr(ord("a") + shifted)
        return result
    
    
    def rounded_rectangle(canvas, x1, y1, x2, y2, radius, **options):
        points = [x1 + radius, y1, x2 - radius, y1, x2, y1, x2, y1 + radius,
                  x2, y2 - radius, x2, y2, x2 - radius, y2, x1 + radius, y2,
                  x1, y2, x1, y2 - radius, x1, y1 + radius, x1, y1]
        return canvas.create_polygon(points, smooth=True, **options)
    
    
    
    def encrypt_clicked():
        try:
            key = int(key_entry.get())
        except ValueError:
            key = 0
        plain_text = text_box.get("1.0", "end-1c")
        result_canvas.itemconfig(result_text, text=encrypt(plain_text, key))
    
    
    
    def save_clicked():
        path = filedialog.asksaveasfilename(defaultextension=".txt", filetypes=[("Plik tekstowy", "*.txt")])
        if path:
            with open(path, "w", encoding="utf-8") as file:
                file.write(result_canvas.itemcget(result_text, "text"))
    
    
    root = tk.Tk()
    root.title("Szyfrowanie. Wykonane przez Bartosz Bryniarski")
    root.geometry("985x555")
    root.configure(bg=BACKGROUND)
    
    left = tk.Frame(root, bg=BACKGROUND)
    left.grid(row=0, column=0, padx=(20, 0), sticky="n")
    tk.Label(left, text="Podaj wartość klucza", font=LABEL_FONT, fg=LABEL_COLOR, bg=BACKGROUND).pack(anchor="w", pady=(10, 0))
    key_entry = tk.Entry(left, width=8)
    key_entry.pack(pady=(15, 0))
    tk.Label(left, text="Podaj tekst", font=LABEL_FONT, fg=LABEL_COLOR, bg=BACKGROUND).pack(anchor="w", pady=(25, 0))
    text_box = tk.Text(left, width=50, height=17, wrap="word", font=("Segoe UI", 10))
    text_box.pack(pady=(20, 0))
    
    encrypt_button = tk.Button(root, text="Zaszyfruj", bg=BUTTON_COLOR, command=encrypt_clicked)
    encrypt_button.grid(row=0, column=1, padx=25, pady=(300, 0), sticky="n")
    
    right = tk.Frame(root, bg=BACKGROUND)
    right.grid(row=0, column=2, sticky="n")
    tk.Label(right, text="Tekst zaszyfrowany", font=LABEL_FONT, fg=LABEL_COLOR, bg=BACKGROUND).pack(anchor="w", pady=(10, 0))
    result_canvas = tk.Canvas(right, width=420, height=345, bg=BACKGROUND, highlightthickness=0)
    result_canvas.pack(pady=(15, 0))
    rounded_rectangle(result_canvas, 3, 3, 417, 342, 50, outline=LABEL_COLOR, fill=BACKGROUND, width=3)
    result_text = result_canvas.create_text(30, 30, anchor="nw", width=360, text="", fill=RESULT_COLOR, font=("Segoe UI", 13))
    save_button = tk.Button(right, text="Zapisz szyfr w pliku", bg=BUTTON_COLOR, width=35, command=save_clicked)
    save_button.pack(pady=(25, 0))
    
    root.mainloop()
    Okno aplikacji po tym kroku

    Symulacja ekranu w przeglądarce - w Android Studio / Visual Studio wygląd może się nieco różnić.

  17. Krok 17: Zrzuty desktop1 - desktop3 i folder desktopowa

    Kryteria:

    R.4.9 Zapisany przynajmniej jeden zrzut ekranu z uruchomienia lub kompilacji aplikacji desktopowej, na zrzucie widoczne jest środowisko, w którym powstała aplikacja

    ok. 4 min
    Polecenie z arkusza

    Wykonaj zrzuty ekranu dokumentujące wszystkie stany aplikacji. Zrzuty zapisz w folderze desktopowa pod nazwami desktop1.png, desktop2.png, itd. W folderze desktopowa powinno znaleźć się archiwum całego projektu o nazwie desktopowa.zip, skopiowane z projektu pliki źródłowe, które były modyfikowane w czasie egzaminu oraz plik wykonywalny, jeżeli istnieje.

    Zrzuty całego ekranu ze środowiskiem: desktop1 - stan początkowy (obraz 1), desktop2 - po zaszyfrowaniu (obraz 2), desktop3 - otwarte okno „Zapisywanie jako” (obraz 3) - w folderze desktopowa (R.4.9). W tym samym folderze: desktopowa.zip i main.py (interfejs i logika w jednym pliku). Pliku wykonywalnego nie ma.

    Podgląd to prawdziwy zrzut obszaru okna tej aplikacji (Tkinter, Windows 11) po zaszyfrowaniu „abc” kluczem 3.

    Okno aplikacji po tym kroku
    Okno aplikacji po kroku 17
  18. Etap 4: Testy jednostkowe

    Kroki 18-27 · po tym etapie masz 36 z 36 kryteriów (pkt)

    Krok 18: Plik testów: unittest i import funkcji encrypt

    ok. 3 min
    Polecenie z arkusza

    Wykonaj testy jednostkowe funkcji lub metody realizującej szyfr Cezara z aplikacji konsolowej. Do napisania testów wykorzystaj dostępną na stanowisku egzaminacyjnym bibliotekę do testów jednostkowych dla języka programowania, w którym została napisana aplikacja konsolowa.

    Dla Pythona biblioteką do testów jest unittest - część standardowej instalacji, nic nie trzeba doinstalowywać. W projekcie konsola obok main.py tworzysz plik test_caesar.py: nazwa zaczyna się od test, po tym przedrostku unittest (i PyCharm) rozpoznają pliki z testami.

    • from main import encrypt - importujesz prawdziwą funkcję z programu konsolowego, nie jej kopię; działa to dzięki warunkowi if __name__ == "__main__": w main.py (import nie uruchamia programu),
    • testy grupuje klasa dziedzicząca po unittest.TestCase,
    • unittest.main() na dole pozwala uruchomić plik jak zwykły program.
    konsola/test_caesar.py Python nowy plik
    import unittest
    
    from main import encrypt
    
    
    class TestEncrypt(unittest.TestCase):
        pass
    
    
    if __name__ == "__main__":
        unittest.main()
    Wynik programu po tym kroku
    > python -m unittest -v test_caesar
    
    ----------------------------------------------------------------------
    Ran 0 tests in 0.000s
    
    NO TESTS RAN
  19. Krok 19: Test 1: Dane podstawowe

    Kryteria: ,

    R.4.3 Zapisany test jednostkowy 1, który przy danych wejściowych abc, 3 oczekuje wyniku def

    R.4.1 Zapisana co najmniej jedna metoda testująca, w której została wykorzystana asercja, zastosowane nazwy odzwierciedlające cel testu

    ok. 2 min
    Polecenie z arkusza

    Tabela 4. Test 1: Dane podstawowe (klucz dodatni, wartość mniejsza od długości alfabetu). Dane wejściowe: tekst jawny „abc”, klucz 3. Oczekiwana wartość po zaszyfrowaniu: „def”. Każdy przypadek testowy powinien być sprawdzany za pomocą asercji oddzielną metodą.

    Test 1 z tabeli 4: „Dane podstawowe (klucz dodatni, wartość mniejsza od długości alfabetu)”. Metoda testująca to metoda klasy, której nazwa zaczyna się od test_ - tylko takie unittest uruchamia. Resztę nazwy piszesz w konwencji Pythona (małe litery i podkreślenia), tak żeby mówiła, co sprawdza (R.4.1). self.assertEqual(rzeczywiste, oczekiwane) porównuje wynik encrypt("abc", 3) z napisem „def” - przy różnicy test kończy się niepowodzeniem i pokazuje obie wartości. Każdy przypadek to osobna metoda, jak wymaga arkusz.

    konsola/test_caesar.py Python zmienione: 2 linie
    import unittest
    
    from main import encrypt
    
    
    class TestEncrypt(unittest.TestCase):
        def test_positive_key_shifts_letters(self):
            self.assertEqual(encrypt("abc", 3), "def")
    
    
    if __name__ == "__main__":
        unittest.main()

    Wynik programu bez zmian - taki jak po kroku 18.

  20. Krok 20: Test 2: „Zawijanie”

    Kryteria:

    R.4.4 Zapisany test jednostkowy 2, który przy danych wejściowych xyz, 3 oczekuje wyniku abc

    ok. 2 min
    Polecenie z arkusza

    Tabela 4. Test 2: „Zawijanie” (gdy litery w tekście i klucz wychodzą poza alfabet). Dane wejściowe: tekst jawny „xyz”, klucz 3. Oczekiwana wartość po zaszyfrowaniu: „abc”. Każdy przypadek testowy powinien być sprawdzany za pomocą asercji oddzielną metodą.

    Test 2 z tabeli 4: „„Zawijanie” (gdy litery w tekście i klucz wychodzą poza alfabet)” - encrypt("xyz", 3) ma dać „abc”. Ten sam wzór, osobna metoda z nazwą mówiącą o celu testu.

    konsola/test_caesar.py Python zmienione: 3 linie
    import unittest
    
    from main import encrypt
    
    
    class TestEncrypt(unittest.TestCase):
        def test_positive_key_shifts_letters(self):
            self.assertEqual(encrypt("abc", 3), "def")
    
        def test_letters_past_z_wrap_to_start(self):
            self.assertEqual(encrypt("xyz", 3), "abc")
    
    
    if __name__ == "__main__":
        unittest.main()

    Wynik programu bez zmian - taki jak po kroku 18.

  21. Krok 21: Test 3: Odszyfrowanie

    Kryteria:

    R.4.5 Zapisany test jednostkowy 3, który przy danych wejściowych def, -3 oczekuje wyniku abc

    ok. 2 min
    Polecenie z arkusza

    Tabela 4. Test 3: Odszyfrowanie (klucz ujemny). Dane wejściowe: tekst jawny „def”, klucz -3. Oczekiwana wartość po zaszyfrowaniu: „abc”. Każdy przypadek testowy powinien być sprawdzany za pomocą asercji oddzielną metodą.

    Test 3 z tabeli 4: „Odszyfrowanie (klucz ujemny)” - encrypt("def", -3) ma dać „abc”. Ten sam wzór, osobna metoda z nazwą mówiącą o celu testu.

    konsola/test_caesar.py Python zmienione: 3 linie
    import unittest
    
    from main import encrypt
    
    
    class TestEncrypt(unittest.TestCase):
        def test_positive_key_shifts_letters(self):
            self.assertEqual(encrypt("abc", 3), "def")
    
        def test_letters_past_z_wrap_to_start(self):
            self.assertEqual(encrypt("xyz", 3), "abc")
    
        def test_negative_key_decrypts(self):
            self.assertEqual(encrypt("def", -3), "abc")
    
    
    if __name__ == "__main__":
        unittest.main()

    Wynik programu bez zmian - taki jak po kroku 18.

  22. Krok 22: Test 4: Klucz większy niż długość alfabetu

    Kryteria:

    R.4.6 Zapisany test jednostkowy 4, który przy danych wejściowych abc, 29 oczekuje wyniku def

    ok. 2 min
    Polecenie z arkusza

    Tabela 4. Test 4: Klucz większy niż długość alfabetu. Dane wejściowe: tekst jawny „abc”, klucz 29. Oczekiwana wartość po zaszyfrowaniu: „def”. Każdy przypadek testowy powinien być sprawdzany za pomocą asercji oddzielną metodą.

    Test 4 z tabeli 4: „Klucz większy niż długość alfabetu” - encrypt("abc", 29) ma dać „def”. Ten sam wzór, osobna metoda z nazwą mówiącą o celu testu.

    konsola/test_caesar.py Python zmienione: 3 linie
    import unittest
    
    from main import encrypt
    
    
    class TestEncrypt(unittest.TestCase):
        def test_positive_key_shifts_letters(self):
            self.assertEqual(encrypt("abc", 3), "def")
    
        def test_letters_past_z_wrap_to_start(self):
            self.assertEqual(encrypt("xyz", 3), "abc")
    
        def test_negative_key_decrypts(self):
            self.assertEqual(encrypt("def", -3), "abc")
    
        def test_key_greater_than_alphabet_uses_remainder(self):
            self.assertEqual(encrypt("abc", 29), "def")
    
    
    if __name__ == "__main__":
        unittest.main()

    Wynik programu bez zmian - taki jak po kroku 18.

  23. Krok 23: Test 5: Spacja w tekście

    Kryteria:

    R.4.7 Zapisany test jednostkowy 5, który przy danych wejściowych ab cd, 2 oczekuje wyniku cd ef

    ok. 2 min
    Polecenie z arkusza

    Tabela 4. Test 5: Spacja w tekście. Dane wejściowe: tekst jawny „ab cd”, klucz 2. Oczekiwana wartość po zaszyfrowaniu: „cd ef”. Każdy przypadek testowy powinien być sprawdzany za pomocą asercji oddzielną metodą.

    Test 5 z tabeli 4: „Spacja w tekście” - encrypt("ab cd", 2) ma dać „cd ef”. Ten sam wzór, osobna metoda z nazwą mówiącą o celu testu.

    konsola/test_caesar.py Python zmienione: 3 linie
    import unittest
    
    from main import encrypt
    
    
    class TestEncrypt(unittest.TestCase):
        def test_positive_key_shifts_letters(self):
            self.assertEqual(encrypt("abc", 3), "def")
    
        def test_letters_past_z_wrap_to_start(self):
            self.assertEqual(encrypt("xyz", 3), "abc")
    
        def test_negative_key_decrypts(self):
            self.assertEqual(encrypt("def", -3), "abc")
    
        def test_key_greater_than_alphabet_uses_remainder(self):
            self.assertEqual(encrypt("abc", 29), "def")
    
        def test_space_is_not_encrypted(self):
            self.assertEqual(encrypt("ab cd", 2), "cd ef")
    
    
    if __name__ == "__main__":
        unittest.main()

    Wynik programu bez zmian - taki jak po kroku 18.

  24. Krok 24: Uruchomienie testów

    Kryteria:

    R.4.2 Co najmniej 1 metoda testująca porównuje wartość oczekiwaną z wartością zwracaną przez metodę szyfrującą i jest ona uruchomiona, co jest udokumentowane zrzutem ekranowym

    ok. 3 min
    Polecenie z arkusza

    Po napisaniu metod testujących, uruchom wszystkie metody. Wykonaj zrzut/zrzuty ekranu dokumentujące uruchomienie testów, zrzuty zapisz w folderze testy pod nazwami test1.png, test2.png, itd.

    W PyCharm: prawy przycisk na test_caesar.py → Run 'Python tests in test_caesar' - panel z zielonymi znacznikami przy każdym teście. W terminalu to samo daje python -m unittest -v test_caesar (-v wypisuje każdy test osobno). Podgląd to prawdziwy wynik: wszystkie pięć testów przechodzi (R.4.2). unittest uruchamia metody w kolejności alfabetycznej nazw, nie w kolejności z pliku. Zrzut całego ekranu z PyCharm i wynikami - test1.png w folderze testy.

    Wynik programu po tym kroku
    > python -m unittest -v test_caesar
    test_key_greater_than_alphabet_uses_remainder (test_caesar.TestEncrypt.test_key_greater_than_alphabet_uses_remainder) ... ok
    test_letters_past_z_wrap_to_start (test_caesar.TestEncrypt.test_letters_past_z_wrap_to_start) ... ok
    test_negative_key_decrypts (test_caesar.TestEncrypt.test_negative_key_decrypts) ... ok
    test_positive_key_shifts_letters (test_caesar.TestEncrypt.test_positive_key_shifts_letters) ... ok
    test_space_is_not_encrypted (test_caesar.TestEncrypt.test_space_is_not_encrypted) ... ok
    
    ----------------------------------------------------------------------
    Ran 5 tests in 0.000s
    
    OK
  25. Krok 25: Co pokażą testy, gdy w szyfrze brakuje zawijania

    ok. 2 min
    Polecenie z arkusza

    Jeżeli metoda szyfrująca została napisana w części I niepoprawnie, testy powinny wykazać błędy. W tej części egzaminu ocenie podlegać będzie zapis i uruchomienie testu, a nie metoda szyfrująca.

    Po to są testy: podgląd to prawdziwe uruchomienie tych samych testów na wersji funkcji z kroku „Przesunięcie litery o klucz” - bez zawijania alfabetu. Przechodzą tylko testy, w których litery nie wychodzą poza z ani przed a (abc z kluczem 3, def z kluczem -3, spacja). Testy zawijania i klucza 29 kończą się niepowodzeniem (FAIL), a unittest pokazuje, co funkcja zwróciła, a czego oczekiwano: '{|}' != 'abc' - od razu widać, gdzie szukać błędu. Dla klucza 29 zamiast liter wychodzą znaki spoza alfabetu, w tym znaki sterujące, które unittest wypisuje w zapisie \x7f.

    Na egzaminie testy mają pokazać prawdę: niepowodzenie przy błędnej funkcji to poprawnie wykonana część III. Nie zmieniaj testów pod błędną funkcję.

    Wynik programu po tym kroku
    > python -m unittest -v test_caesar
    test_key_greater_than_alphabet_uses_remainder (test_caesar.TestEncrypt.test_key_greater_than_alphabet_uses_remainder) ... FAIL
    test_letters_past_z_wrap_to_start (test_caesar.TestEncrypt.test_letters_past_z_wrap_to_start) ... FAIL
    test_negative_key_decrypts (test_caesar.TestEncrypt.test_negative_key_decrypts) ... ok
    test_positive_key_shifts_letters (test_caesar.TestEncrypt.test_positive_key_shifts_letters) ... ok
    test_space_is_not_encrypted (test_caesar.TestEncrypt.test_space_is_not_encrypted) ... ok
    
    ======================================================================
    FAIL: test_key_greater_than_alphabet_uses_remainder (test_caesar.TestEncrypt.test_key_greater_than_alphabet_uses_remainder)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "test_caesar.py", line 17, in test_key_greater_than_alphabet_uses_remainder
        self.assertEqual(encrypt("abc", 29), "def")
    AssertionError: '~\x7f\x80' != 'def'
    - ~\x7f\x80
    + def
    
    
    ======================================================================
    FAIL: test_letters_past_z_wrap_to_start (test_caesar.TestEncrypt.test_letters_past_z_wrap_to_start)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "test_caesar.py", line 11, in test_letters_past_z_wrap_to_start
        self.assertEqual(encrypt("xyz", 3), "abc")
    AssertionError: '{|}' != 'abc'
    - {|}
    + abc
    
    
    ----------------------------------------------------------------------
    Ran 5 tests in 0.002s
    
    FAILED (failures=2)
  26. Krok 26: Folder testy: pliki, zrzut i archiwum

    ok. 2 min
    Polecenie z arkusza

    W folderze testy zapisz bezpośrednio pliki, których treść była modyfikowana oraz zrzuty ekranu. Utwórz archiwum ZIP zawierające cały projekt i zapisz je w folderze testy.

    W folderze testy: test_caesar.py, test1.png i archiwum ZIP całego projektu konsola - testy są w nim razem z main.py, bez którego by się nie uruchomiły.

    Wynik programu bez zmian - taki jak po kroku 25.

  27. Krok 27: Plik egzamin

    Kryteria:

    R.4.10 Dokumentacja zawiera: nazwę systemu operacyjnego, nazwy środowisk programistycznych, nazwy języków programowania (stosowanych przez zdającego) . Na płycie wszystkie wykonane projekty są spakowane oraz umieszczone pliki źródłowe i pliki wykonywalne (jeśli istnieją) na zewnątrz archiwów

    ok. 2 min
    Polecenie z arkusza

    Utwórz plik z dokumentacją i nazwij go egzamin. Dokument powinien zawierać informacje o narzędziach wykorzystanych na egzaminie: nazwę systemu operacyjnego, nazwy środowisk programistycznych, nazwy języków programowania. Dokument egzamin zapisz w folderze z numerem zdającego.

    Plik egzamin w folderze z numerem zdającego (nie w podfolderze) z trzema informacjami o narzędziach (R.4.10).

    egzamin.txt Tekst nowy plik
    System operacyjny: Windows 11
    Środowisko programistyczne: PyCharm Community (aplikacja konsolowa, desktopowa i testy)
    Języki programowania: Python (konsola, testy w bibliotece unittest, aplikacja desktopowa w bibliotece Tkinter)

    Wynik programu bez zmian - taki jak po kroku 25.

  28. Etap 5: Porównanie z wariantem C# + WPF

    Kroki 28-29 · po tym etapie masz 36 z 36 kryteriów (pkt)

    Krok 28: Szyfr i testy: Python a C#

    ok. 3 min
    Polecenie z arkusza

    Szyfr działa jedynie dla małych liter podstawowego alfabetu łacińskiego, przedstawionego w tabeli 1 (kody ASCII od 97 do 122).

    Algorytm jest ten sam, różnią się szczegóły języka - i na nich najłatwiej stracić punkt:

    • znak a liczba - w C# char jest liczbą, więc letter + key działa od razu; w Pythonie trzeba ord() i chr(),
    • reszta z dzielenia - w Pythonie -29 % 26 to 23, w C# -3; dlatego wariant C# potrzebuje key % 26 i dodanych 26, a Python nie,
    • testy - MSTest w C# to osobny projekt z odwołaniem do konsoli i atrybutami [TestClass], [TestMethod], Assert.AreEqual(oczekiwane, rzeczywiste); unittest to jeden plik obok programu, metody test_... i self.assertEqual(rzeczywiste, oczekiwane) - kolejność argumentów jest odwrotna, ale przy równości nie ma to znaczenia.

    Popularną alternatywą dla unittest jest pytest (testy jako zwykłe funkcje z assert), ale trzeba go doinstalować - na stanowisku egzaminacyjnym może go nie być, unittest jest zawsze.

    Wynik programu bez zmian - taki jak po kroku 25.

  29. Krok 29: Okno: Tkinter a WPF - co wybrać na egzaminie

    ok. 3 min
    Polecenie z arkusza

    Za pomocą dostępnego na stanowisku egzaminacyjnym środowiska programistycznego wykonaj aplikację desktopową będącą interfejsem graficznym programu utworzonego w konsoli.

    Najważniejsze różnice wobec wariantu WPF:

    • zaokrąglone rogi - w WPF wystarczy Border z CornerRadius, w Tkinterze trzeba narysować ramkę na Canvas,
    • klucz domyślnie 0 - w C# int.TryParse (zwraca false i ustawia 0), w Pythonie try / except ValueError,
    • okno zapisu - SaveFileDialog z ShowDialog() == true w WPF, filedialog.asksaveasfilename z pustym napisem przy „Anuluj” w Tkinterze,
    • pole wielowierszowe - TextBox z TextWrapping="Wrap" w WPF, osobna kontrolka Text z wrap="word" w Tkinterze (i odczyt treści z zakresem "1.0", "end-1c").

    Co wybrać? Jeśli najpewniej czujesz się w Pythonie, ten wariant pozwala zrobić cały egzamin w jednym języku i jednym środowisku. WPF ma projektant okien i prostsze zaokrąglenia, ale wymaga C# i XAML. Punkty za aplikację (R.3) i testy (R.4) są w obu technologiach te same.

    Wynik programu bez zmian - taki jak po kroku 25.

  30. Etap 6: Oddanie pracy

    Krok 30 · po tym etapie masz 36 z 36 kryteriów (pkt)

    Krok 30: Nagranie płyty

    ok. 5 min
    Polecenie z arkusza

    UWAGA: Nagraj płytę z rezultatami pracy. W folderze z numerem zdającego powinny znajdować się podfoldery desktopowa, konsolowa, testy oraz plik egzamin.

    Sprawdzasz zawartość przed nagraniem:

    • konsolowa: konsola.zip, main.py, konsola1.png - konsola3.png,
    • desktopowa: desktopowa.zip, main.py, desktop1.png - desktop3.png,
    • testy: archiwum projektu z testami, test_caesar.py, test1.png,
    • plik egzamin w folderze głównym.

    Wszystkie projekty spakowane, źródła obok archiwów. Nagrywasz, sprawdzasz odczyt płyty, opisujesz ją numerem zdającego.

    Wynik programu bez zmian - taki jak po kroku 25.

Podgląd

To jedno z możliwych rozwiązań. Egzaminator ocenia spełnienie kryteriów z klucza odpowiedzi, a nie zgodność z tym kodem - inna, poprawna droga też daje punkty.

Autor rozwiązania: . Licencja CC BY-NC 4.0 - kopiowanie i przeróbki do celów niekomercyjnych, z podaniem autora i źródła (link do tej strony) (zasady wykorzystania). Treść arkusza i klucza oceniania pochodzi z CKE.