Kategória: Programozás

  • Tipikus interjú kérdés: yield

    Mennyi mindent használunk, de igazából lehet, hogy fogalmunk sincs róla, hogy mire is jó pontosan, és mikor is érdemes használni. A miértről nem is beszélve…

    Mit mesél erről a Gemini?

    A .NET-ben (C#) a yield kulcsszó pontosan ugyanazt a célt szolgálja, mint a Pythonban: IEnumerable<T> vagy IAsyncEnumerable<T> típusú adatfolyamokat hozhatsz vele létre úgy, hogy a kód lusta kiértékeléssel (lazy evaluation), elemenként adja vissza az eredményeket.

    C#-ban két formája létezik:

    • yield return <érték>: Visszaad egy elemet, majd felfüggeszti a metódus futását a következő elem kéréséig.
    • yield break: Azonnal leállítja az iterációt (megszakítja a generálást).

    Mire jó C#-ban?

    1. Memóriatakarékosság nagy adathalmazoknál

    Ha nem List<T>-et adsz vissza, nem kell a teljes gyűjteményt lefoglalni a RAM-ban.

    C#

    using System;
    using System.Collections.Generic;
    
    // PÉLDA 1: Hagyományos megközelítés (Memóriatakaréktalan)
    public List<int> GetNumbersList()
    {
        var list = new List<int>();
        for (int i = 0; i < 1_000_000; i++)
        {
            list.Add(i); // Mind az 1 milló elem belekerül a RAM-ba
        }
        return list;
    }
    
    // PÉLDA 2: yield használata (Memóriatakarékos)
    public IEnumerable<int> GetNumbersYield()
    {
        for (int i = 0; i < 1_000_000; i++)
        {
            yield return i; // Mindig csak az aktuális számot generálja le
        }
    }
    

    2. Szűrés és adatfeldolgozás yield break-kel

    A yield break használatával feltételhez kötheted az adatfolyam végét anélkül, hogy bonyolult feltételkezelést kellene írnod a ciklus köré:

    C#

    public IEnumerable<string> GetActiveUsers(List<string> users)
    {
        foreach (var user in users)
        {
            if (user == "ADMIN_STOP")
            {
                yield break; // Azonnal leállítja az elemek kiadását
            }
    
            if (!string.IsNullOrEmpty(user))
            {
                yield return user;
            }
        }
    }
    

    3. Óriási fájlok feldolgozása soronként

    Nem kell a gigabájtos fájlokat egyszerre a memóriába tölteni (File.ReadAllLines helyett):

    C#

    using System.IO;
    using System.Collections.Generic;
    
    public IEnumerable<string> ReadLargeFile(string filePath)
    {
        using (StreamReader reader = new StreamReader(filePath))
        {
            string? line;
            while ((line = reader.ReadLine()) != null)
            {
                yield return line; // Mindig csak egyetlen sort tart a memóriában
            }
        }
    }
    
    // Használat:
    foreach (string line in ReadLargeFile("huge_log.txt"))
    {
        if (line.Contains("ERROR"))
        {
            Console.WriteLine(line);
        }
    }
    

    4. Aszinkron adatfolyamok (C# 8.0+: IAsyncEnumerable<T>)

    Modern .NET-ben hálózati kéréseknél vagy adatbázis-lekérdezéseknél aszinkron módon is használhatod:

    C#

    public async IAsyncEnumerable<int> FetchDataAsync()
    {
        for (int i = 1; i <= 5; i++)
        {
            await Task.Delay(1000); // Pl. hálózati várakozás szimulálása
            yield return i;
        }
    }
    
    // Használat:
    await foreach (var item in FetchDataAsync())
    {
        Console.WriteLine(item); // Másodpercenként jelenik meg egy elem
    }
    

    Hagyományos visszatérés vs. yield C#-ban

    SzempontList<T> / Tömbyield return (IEnumerable<T>)
    Kiértékelés idejeAzonnali (A metódus hívásakor lefut a teljes ciklus)Lusta / Halasztott (Csak akkor fut, amikor a foreach kéri a következő elemet)
    MemóriahasználatMagas ($O(N)$ méretű lista a RAM-ban)Minimális ($O(1)$ állandó memóriagény)
    Sebesség (első elem)Lassú (meg kell várni a teljes lista felépítését)Azonnali (az első elem kérésére rögtön visszatér)

    Gondoltam, megkérdezem ugyanezt Typescript esetén.

    Igen, pontosan ugyanígy működik TypeScriptben és JavaScriptben is.

    A TypeScript a JavaScript generátor függvényeit (Generator Functions) használja, amelyeket az function* (csillaggal jelölt) szintaxisról lehet felismerni.

    A működési elv teljesen megegyezik a Python és a C# esetében látottakkal: a yield felfüggeszti a függvény futását, megőrzi a belső állapotot, és lusta kiértékeléssel (lazy evaluation) adja vissza az elemeket egy Generator objektumon keresztül.

    Alapvető példa TypeScriptben

    A generátor függvény visszatérési típusa Generator<T, TNext TReturn,>, ahol a T a yield-elt értékek típusa.

    TypeScript

    // A csillag (function*) jelzi, hogy ez egy generátor függvény
    function* szamGeneraltor(): Generator<number> {
      yield 1;
      yield 2;
      yield 3;
    }
    
    // Használat for...of ciklussal:
    for (const szam of szamGeneraltor()) {
      console.log(szam); // Kiírja egymás után: 1, 2, 3
    }
    

    Mire jó TypeScriptben?

    1. Memóriatakarékos és végtelen sorozatok

    Nem kell előre tömbbe gyűjteni az elemeket:

    TypeScript

    function* vegtelenAzonositoGeneral(): Generator<string> {
      let id = 1;
      while (true) {
        yield `ID_${id++}`;
      }
    }
    
    const idGen = vegtelenAzonositoGeneral();
    
    console.log(idGen.next().value); // "ID_1"
    console.log(idGen.next().value); // "ID_2"
    console.log(idGen.next().value); // "ID_3"
    

    2. Adatfolyamok delegálása (yield*)

    A TypeScriptben létezik a yield* utasítás is, amellyel átadhatod a vezérlést egy másik generátornak vagy tömbnek (bejárható objektumnak):

    TypeScript

    function* elsoResz(): Generator<number> {
      yield 1;
      yield 2;
    }
    
    function* teljesSorozat(): Generator<number> {
      yield* elsoResz(); // Delegálja az elemeket az elsoResz-ből
      yield* [3, 4, 5];  // Tömb elemeit is ki tudja adni egymás után
    }
    
    console.log([...teljesSorozat()]); // [1, 2, 3, 4, 5]
    

    3. Aszinkron generátorok (yield + async)

    Ahogy C#-ban az IAsyncEnumerable, TypeScriptben és JS-ben is létezik az AsyncGenerator (async function*). Ez hálózati adatok, lapozott API-k vagy adatfolyamok (stream) feldolgozásánál nagyon hasznos:

    TypeScript

    async function* adatokLetolteseAPIrol(): AsyncGenerator<string> {
      const oldalak = [1, 2, 3];
      
      for (const oldal of oldalak) {
        // Szimulált hálózati kérés (API hívás)
        const valasz = await fetch(`https://api.example.com/data?page=${oldal}`);
        const adat = await valasz.text();
        
        yield adat; // Mindig csak az aktuális oldal adatait adja vissza
      }
    }
    
    // Használat for await...of ciklussal:
    async function feldolgozas() {
      for await (const oldalAdat of adatokLetolteseAPIrol()) {
        console.log("Megérkezett egy oldal:", oldalAdat);
      }
    }
    

    Összehasonlítás

    NyelvFüggvény jelöléseKulcsszóBejárási felület
    C#IEnumerable<T> Get()yield returnforeach / LINQ
    Pythondef get():yieldfor item in gen
    TypeScriptfunction* get(): Generator<T>yieldfor...of / next()
  • Grafana K6 készítés Copilot-tal

    Mi is történt? Ismét a semmiből kezdtem, megkértem a Copilot-ot, hogy csináljon egy project-et. El is készült, futott, de a riportolásával nem voltam elégedett. Megkérdeztem, hogy mit ajánl rá, ő elsőre a Grafana felhős szolgáltatását ajánlotta, de mivel lokálból akartam megoldani, így kiegyeztünk a Grafana lokális verziójával. Telepített Grafanát, Dockert, InfluxDB-t. Még a Grafana dashboard-ot is vele csináltattam meg. Még koránt sincs kész, de majd megfelelő Token birtoklásával orvosolni fogom.

    Teszt kód:

    import http from 'k6/http';
    import { check, sleep } from 'k6';
    
    export const options = {
      stages: [
        { duration: '40s', target: 2 },
        { duration: '35s', target: 10 },
        { duration: '60s', target: 3 },
      ],
      thresholds: {
        http_req_failed: ['rate<0.01'],
        http_req_duration: ['p(95)<500'],
      },
    };
    
    const baseUrl = __ENV.BASE_URL || 'https://test.k6.io';
    
    export default function () {
      const response = http.get(baseUrl);
    
      check(response, {
        'status is 200': (result) => result.status === 200,
        'response is HTML': (result) => result.headers['Content-Type'].includes('text/html'),
      });
    }

    Futtatás:

    D:\Repositories\ai-k6> npm run test:grafana
    
    > ai-k6@1.0.0 test:grafana
    > npm run build && node scripts/run-grafana.mjs
    
    
    > ai-k6@1.0.0 build
    > esbuild src/smoke.ts --bundle --format=esm --platform=neutral --external:k6 --external:k6/http --outfile=dist/smoke.js
    
    
      dist\smoke.js  670b 
    
    Done in 4ms
    
             /\      Grafana   /‾‾/  
        /\  /  \     |\  __   /  /   
       /  \/    \    | |/ /  /   ‾‾\ 
      /          \   |   (  |  (‾)  |
     / __________ \  |_|\_\  \_____/ 
    
    
         execution: local
            script: dist/smoke.js
            output: InfluxDBv1 (http://localhost:8086)
    
         scenarios: (100.00%) 1 scenario, 10 max VUs, 2m45s max duration (incl. graceful stop):
                  * default: Up to 10 looping VUs for 2m15s over 3 stages (gracefulRampDown: 30s, gracefulStop: 30s)
    
    
    
      █ THRESHOLDS 
    
        http_req_duration
        ✓ 'p(95)<500' p(95)=135.05ms
    
        http_req_failed
        ✓ 'rate<0.01' rate=0.00%
    
    
      █ TOTAL RESULTS 
    
        checks_total.......: 1138    8.391855/s
        checks_succeeded...: 100.00% 1138 out of 1138
        checks_failed......: 0.00%   0 out of 1138
    
        ✓ status is 200
        ✓ response is HTML
    
        HTTP
        http_req_duration..............: avg=72.84ms min=184µs med=114.54ms max=1s    p(90)=132.88ms p(95)=135.05ms
          { expected_response:true }...: avg=72.84ms min=184µs med=114.54ms max=1s    p(90)=132.88ms p(95)=135.05ms
        http_req_failed................: 0.00%  0 out of 1138
        http_reqs......................: 1138   8.391855/s
    
        EXECUTION
        iteration_duration.............: avg=1.15s   min=1.12s med=1.14s    max=2.02s p(90)=1.15s    p(95)=1.16s   
        iterations.....................: 569    4.195928/s
        vus............................: 4      min=1         max=10
        vus_max........................: 10     min=10        max=10
    
        NETWORK
        data_received..................: 2.2 MB 16 kB/s
        data_sent......................: 86 kB  637 B/s
    
    
    
    
    running (2m15.6s), 00/10 VUs, 569 complete and 0 interrupted iterations
    default ✓ [======================================] 00/10 VUs  2m15s
    PS D:\Repositories\ai-k6> 

    Grafana

  • Custom item list plugin AI-val generálva

    Tegnap egész sok mindennel kísérleteztem, majd eszembe jutott, hogy szerettem volna már egy ideje saját adatokat menteni a WordPress blog-omba, de sose volt elég időm, kedvem újra elővenni a PHP-t, és belekezdeni a fejlesztésbe. Szóval most, hogy van AI meg Gemini, megkértem, hogy csináljon egy alap plugin-t. Semmi extra, egy egyszerű CRUD, ahol a create, update, delete az admin felületen keresztül történik, a read pedig short code-ként kirakható bárhová.

    Azt azért látom, hogy ahhoz, ami a végső célom lenne, be kell húznom egy IDE-t, és abban prompt-olva a fejlesztést, de szerintem ez így elsőre nem tűnik rossznak.

    Az eredmény ez lett:

    NévURLTípus

    A generált kód pedig így néz ki:

    <?php
    /**
     * Plugin Name: Custom Items Manager
     * Description: CRUD bővítmény egyedi táblával, admin authentikációval és publikus szűrhető UI-val (Név, URL, Típus).
     * Version: 1.0
     * Author: Developer
     */
    
    if (!defined('ABSPATH')) {
        exit;
    }
    
    class CustomItemsManager {
        private $table_name;
    
        public function __construct() {
            global $wpdb;
            $this->table_name =$wpdb->prefix . 'custom_items';
    
            // Telepítéskor adatbázis tábla létrehozása
            register_activation_hook(__FILE__, array($this, 'activate'));
    
            // Admin menü létrehozása
            add_action('admin_menu', array($this, 'add_admin_menu'));
    
            // Shortcode a publikus felülethez
            add_shortcode('custom_items_list', array($this, 'render_public_ui'));
    
            // AJAX keresés végpontok (publikus és bejelentkezett felhasználóknak is)
            add_action('wp_ajax_filter_custom_items', array($this, 'ajax_filter_items'));
            add_action('wp_ajax_nopriv_filter_custom_items', array($this, 'ajax_filter_items'));
        }
    
        // Adatbázis tábla létrehozása
        public function activate() {
            global $wpdb;
            $charset_collate =$wpdb->get_charset_collate();
    
            $sql = "CREATE TABLE {$this->table_name} (             id INT(11) NOT NULL AUTO_INCREMENT,             name VARCHAR(255) NOT NULL,             url VARCHAR(255) NOT NULL,             type VARCHAR(100) NOT NULL,             created_at DATETIME DEFAULT CURRENT_TIMESTAMP,             PRIMARY KEY  (id)         )$charset_collate;";
    
            require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
            dbDelta($sql);
        }
    
        // Admin menü regisztrációja
        public function add_admin_menu() {
            add_menu_page(
                'Elemek Kezelése',
                'Elemek Kezelése',
                'manage_options', // Csak adminisztrátorok érhetik el
                'custom-items-manager',
                array($this, 'render_admin_page'),
                'dashicons-database',
                20
            );
        }
    
        // ADMIN UI: Create, Read, Update, Delete
        public function render_admin_page() {
            if (!current_user_can('manage_options')) {
                wp_die(__('Nincs jogosultságod az oldal megtekintéséhez.'));
            }
    
            global $wpdb;
            $message = '';
    
            // TÖRLÉS (Delete)
            if (isset($_GET['action']) && $_GET['action'] === 'delete' && isset($_GET['id'])) {
                $id = intval($_GET['id']);
                check_admin_referer('delete_item_' . $id);
                $wpdb->delete($this->table_name, array('id' => $id));$message = '<div class="updated"><p>Elem sikeresen törölve.</p></div>';
            }
    
            // MENTÉS / MÓDOSÍTÁS (Create / Update)
            if (isset($_POST['submit_item'])) {
                check_admin_referer('custom_item_form_nonce');
    
                $id = isset($_POST['item_id']) ? intval($_POST['item_id']) : 0;
                $name = sanitize_text_field($_POST['name']);
                $url = esc_url_raw($_POST['url']);
                $type = sanitize_text_field($_POST['type']);
    
                if (!empty($name) && !empty($url) && !empty($type)) {
                    if ($id > 0) {                     // Update$wpdb->update($this->table_name, array('name' =>$name, 'url' => $url, 'type' =>$type), array('id' => $id));$message = '<div class="updated"><p>Elem frissítve!</p></div>';
                    } else {
                        // Create
                        $wpdb->insert($this->table_name, array('name' => $name, 'url' =>$url, 'type' => $type));$message = '<div class="updated"><p>Új elem hozzáadva!</p></div>';
                    }
                } else {
                    $message = '<div class="error"><p>Minden mező kitöltése kötelező!</p></div>';
                }
            }
    
            // Szerkesztendő elem lekérése, ha van
            $edit_item = null;
            if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id'])) {
                $edit_item =$wpdb->get_row($wpdb->prepare("SELECT * FROM {$this->table_name} WHERE id = %d", intval($_GET['id'])));
            }
    
            // Összes elem lekérdezése az admin listához
            $items = $wpdb->get_results("SELECT * FROM {$this->table_name} ORDER BY id DESC");
    
            ?>
            <div class="wrap">
                <h1>Elemek Kezelése (Admin CRUD)</h1>
                <?php echo $message; ?>
    
                <!-- CRUD Űrlap (Create/Update) -->
                <h2><?php echo $edit_item ? 'Elem Szerkesztése' : 'Új Elem Hozzáadása'; ?></h2>
                <form method="post" style="background:#fff; padding:15px; border:1px solid #ccc; max-width:500px; margin-bottom:20px;">
                    <?php wp_nonce_field('custom_item_form_nonce'); ?>
                    <input type="hidden" name="item_id" value="<?php echo $edit_item ? $edit_item->id : 0; ?>">
                    
                    <p>
                        <label>Név:</label><br>
                        <input type="text" name="name" value="<?php echo $edit_item ? esc_attr($edit_item->name) : ''; ?>" required style="width:100%;">
                    </p>
                    <p>
                        <label>URL:</label><br>
                        <input type="url" name="url" value="<?php echo $edit_item ? esc_attr($edit_item->url) : ''; ?>" required style="width:100%;">
                    </p>
                    <p>
                        <label>Típus:</label><br>
                        <input type="text" name="type" value="<?php echo $edit_item ? esc_attr($edit_item->type) : ''; ?>" placeholder="pl. Blog, Bővítmény, Dokumentáció" required style="width:100%;">
                    </p>
                    <p>
                        <input type="submit" name="submit_item" class="button button-primary" value="<?php echo $edit_item ? 'Frissítés' : 'Mentés'; ?>">
                        <?php if ($edit_item): ?>
                            <a href="?page=custom-items-manager" class="button">Mégse</a>
                        <?php endif; ?>
                    </p>
                </form>
    
                <!-- Admin Lista (Read) -->
                <h2>Létező Elemek</h2>
                <table class="wp-list-table widefat fixed striped">
                    <thead>
                        <tr>
                            <th>ID</th>
                            <th>Név</th>
                            <th>URL</th>
                            <th>Típus</th>
                            <th>Műveletek</th>
                        </tr>
                    </thead>
                    <tbody>
                        <?php if ($items): foreach ($items as$item): ?>
                            <tr>
                                <td><?php echo $item->id; ?></td>
                                <td><strong><?php echo esc_html($item->name); ?></strong></td>
                                <td><a href="<?php echo esc_url($item->url); ?>" target="_blank"><?php echo esc_html($item->url); ?></a></td>
                                <td><?php echo esc_html($item->type); ?></td>
                                <td>
                                    <a href="?page=custom-items-manager&action=edit&id=<?php echo $item->id; ?>" class="button button-small">Szerkesztés</a>
                                    <a href="<?php echo wp_nonce_url('?page=custom-items-manager&action=delete&id=' . $item->id, 'delete_item_' .$item->id); ?>" class="button button-small button-link-delete" onclick="return confirm('Biztosan törlöd?');">Törlés</a>
                                </td>
                            </tr>
                        <?php endforeach; else: ?>
                            <tr><td colspan="5">Nincs megjeleníthető elem.</td></tr>
                        <?php endif; ?>
                    </tbody>
                </table>
            </div>
            <?php
        }
    
        // PUBLIKUS UI: Shortcode kimenet és szűrő felület
        public function render_public_ui() {
            ob_start();
            ?>
            <div id="custom-items-public-container" style="max-width:800px; margin: 0 auto;">
                <!-- Szűrő Mezők -->
                <div style="display:flex; gap:10px; margin-bottom:20px; flex-wrap:wrap;">
                    <input type="text" id="filter-name" placeholder="Szűrés név alapján..." style="flex:1; padding:8px;">
                    <input type="text" id="filter-url" placeholder="Szűrés URL alapján..." style="flex:1; padding:8px;">
                    <input type="text" id="filter-type" placeholder="Szűrés típus alapján..." style="flex:1; padding:8px;">
                </div>
    
                <!-- Eredmények Táblázata -->
                <table style="width:100%; border-collapse:collapse; text-align:left;">
                    <thead>
                        <tr style="background:#f4f4f4; border-bottom:2px solid #ddd;">
                            <th style="padding:10px;">Név</th>
                            <th style="padding:10px;">URL</th>
                            <th style="padding:10px;">Típus</th>
                        </tr>
                    </thead>
                    <tbody id="custom-items-results">
                        <!-- AJAX tölti fel -->
                    </tbody>
                </table>
            </div>
    
            <script>
            document.addEventListener('DOMContentLoaded', function() {
                const nameInput = document.getElementById('filter-name');
                const urlInput = document.getElementById('filter-url');
                const typeInput = document.getElementById('filter-type');
                const resultsBody = document.getElementById('custom-items-results');
    
                function fetchItems() {
                    const params = new URLSearchParams({
                        action: 'filter_custom_items',
                        name: nameInput.value,
                        url: urlInput.value,
                        type: typeInput.value
                    });
    
                    fetch('<?php echo admin_url('admin-ajax.php'); ?>?' + params.toString())
                        .then(response => response.json())
                        .then(data => {
                            resultsBody.innerHTML = '';
                            if (data.length > 0) {
                                data.forEach(item => {
                                    const row = `
                                        <tr style="border-bottom:1px solid #eee;">
                                            <td style="padding:10px;"><strong>${escapeHtml(item.name)}</strong></td>
                                            <td style="padding:10px;"><a href="${escapeHtml(item.url)}" target="_blank" rel="noopener">${escapeHtml(item.url)}</a></td>
                                            <td style="padding:10px;">${escapeHtml(item.type)}</td>
                                        </tr>`;
                                    resultsBody.innerHTML += row;
                                });
                            } else {
                                resultsBody.innerHTML = '<tr><td colspan="3" style="padding:10px;">Nincs a szűrésnek megfelelő találat.</td></tr>';
                            }
                        });
                }
    
                function escapeHtml(text) {
                    return text.replace(/[&<>"']/g, function(m) {
                        return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[m];
                    });
                }
    
                // Eseményfigyelők a dinamikus kereséshez
                nameInput.addEventListener('input', fetchItems);
                urlInput.addEventListener('input', fetchItems);
                typeInput.addEventListener('input', fetchItems);
    
                // Első betöltés
                fetchItems();
            });
            </script>
            <?php
            return ob_get_clean();
        }
    
        // AJAX Szűrés Lekérdezése
        public function ajax_filter_items() {
            global $wpdb;
    
            $name = isset($_GET['name']) ? sanitize_text_field($_GET['name']) : '';
            $url = isset($_GET['url']) ? sanitize_text_field($_GET['url']) : '';$type = isset($_GET['type']) ? sanitize_text_field($_GET['type']) : '';
    
            $query = "SELECT name, url, type FROM {$this->table_name} WHERE 1=1";
            $args = array();
    
            if (!empty($name)) {$query .= " AND name LIKE %s";
                $args[] = '%' . $wpdb->esc_like($name) . '%';
            }
            if (!empty($url)) {$query .= " AND url LIKE %s";
                $args[] = '%' . $wpdb->esc_like($url) . '%';
            }
            if (!empty($type)) {$query .= " AND type LIKE %s";
                $args[] = '%' . $wpdb->esc_like($type) . '%';
            }
    
            $query .= " ORDER BY name ASC";
    
            if (!empty($args)) {$results = $wpdb->get_results($wpdb->prepare($query,$args));
            } else {
                $results = $wpdb->get_results($query);
            }
    
            wp_send_json($results);
        }
    }
    
    new CustomItemsManager();
  • Névnapok plugin Gemini-vel generálva

    Gondoltam egy merészet, és megkértem a Gemini-t, hogy készítsen nekem egy WordPress Plugint, ami random Mészáros Lőrinc idézeteket jelenít meg. Megcsinálta.

    “Mindenki a maga sikere kovácsa, ez bárki legyen, azt gondolom, tehát mindenki, mindenki, tehát kell hozzá, hogy valamire vigye, vagy valamire ne vigye…”— Mészáros Lőrinc

    Nem álltam itt meg, megkértem, hogy csináljon egy névnap megjelenítőt, az aktuálisra, és a soron következőre.

    Ma: Hedvig
    Holnap: Lukács

    Íme:

    Itt egy komplett, egyfájlos WordPress bővítmény, amelyet közvetlenül beépíthetsz a weboldaladba. A bővítmény a látogató böngészőjének aktuális dátumát veszi alapul, kezeli a szökőéveket (a februári névnapeltolódást is), és shortcode segítségével bárhova könnyen elhelyezhető.

    Bővítmény kóda

    Hozz létre egy magyar-nevnapok.php fájlt a /wp-content/plugins/magyar-nevnapok/ mappában, és másold bele az alábbi kódot:

    PHP

    <?php
    /**
     * Plugin Name: Magyar Névnapok (Mai és Holnapi)
     * Description: Megjeleníti a mai és a holnapi névnapot shortcode segítségével.
     * Version: 1.0
     * Author: AI Assistant
     */
    
    if (!defined('ABSPATH')) {
        exit; // Közvetlen hozzáférés tiltása
    }
    
    class Magyar_Nevnapok_Plugin {
    
        // Névnap adatbázis (Hónap -> Nap -> Névnapok)
        private static $nevnapok = [
            1 => [
                1 => 'Fruzsina', 2 => 'Ábel', 3 => 'Genovéva, Benjámin', 4 => 'Titusz, Leona', 5 => 'Simon',
                6 => 'Boldizsár', 7 => 'Attila, Ramóna', 8 => 'Gyöngyvér', 9 => 'Marcell', 10 => 'Melánia',
                11 => 'Ágota', 12 => 'Ernő', 13 => 'Veronika', 14 => 'Bódog', 15 => 'Lóránt, Lóránd',
                16 => 'Gusztáv', 17 => 'Antal, Antónia', 18 => 'Piroska', 19 => 'Sára, Márió', 20 => 'Fábián, Sebestyén',
                21 => 'Ágnes', 22 => 'Vince, Artúr', 23 => 'Zelma, Rajmund', 24 => 'Timót', 25 => 'Pál',
                26 => 'Vanda, Paula', 27 => 'Angéla, Anzelm', 28 => 'Károly, Karola', 29 => 'Adél', 30 => 'Martina, Gerda',
                31 => 'Marcella'
            ],
            2 => [
                1 => 'Ignác', 2 => 'Karolina, Aida', 3 => 'Balázs', 4 => 'Ráhel, Csenge', 5 => 'Ágota, Ingrid',
                6 => 'Dorottya, Dóra', 7 => 'Tódor, Rómeó', 8 => 'Aranka', 9 => 'Abigél, Alex', 10 => 'Elvira',
                11 => 'Bertold, Marietta', 12 => 'Lívia, Lídia', 13 => 'Ella, Linda', 14 => 'Bálint, Valentin', 15 => 'Kolos, Georgina',
                16 => 'Julianna, Lilla', 17 => 'Donát', 18 => 'Bernadett', 19 => 'Zsuzsanna', 20 => 'Aladár, Álmos',
                21 => 'Eleonóra', 22 => 'Gerzson', 23 => 'Alfréd', 24 => 'Mátyás', 25 => 'Géza',
                26 => 'Edina', 27 => 'Ákos, Bátor', 28 => 'Elemér', 29 => 'Névnap szökőévben'
            ],
            3 => [
                1 => 'Albin', 2 => 'Lujza', 3 => 'Kornélia', 4 => 'Kázmér', 5 => 'Adorján, Adrián',
                6 => 'Leonóra, Inez', 7 => 'Tamás', 8 => 'Zoltán', 9 => 'Franciska, Fanni', 10 => 'Ildikó',
                11 => 'Szilárd', 12 => 'Gergely', 13 => 'Krisztián, Ajtony', 14 => 'Matild', 15 => 'Kristóf',
                16 => 'Henrietta', 17 => 'Patrick, Gertrúd', 18 => 'Ede, Sándor', 19 => 'József, Bánk', 20 => 'Klaudia',
                21 => 'Benedek', 22 => 'Beáta, Izolda', 23 => 'Emőke', 24 => 'Gábor, Karina', 25 => 'Irén, Írisz',
                26 => 'Emánuel', 27 => 'Hajnalka', 28 => 'Gedeon, Johanna', 29 => 'Auguszta', 30 => 'Zalán',
                31 => 'Árpád'
            ],
            4 => [
                1 => 'Hugó', 2 => 'Áron', 3 => 'Buda, Richárd', 4 => 'Izidor', 5 => 'Vince',
                6 => 'Vilmos, Bíbor', 7 => 'Herman', 8 => 'Dénes', 9 => 'Erhard', 10 => 'Zsolt',
                11 => 'Leó, Szaniszló', 12 => 'Gyula', 13 => 'Ida', 14 => 'Tibor', 15 => 'Anasztázia, Tas',
                16 => 'Csongor', 17 => 'Rudolf', 18 => 'Andrea, Ilma', 19 => 'Emma', 20 => 'Tivadar',
                21 => 'Konrád', 22 => 'Csilla, Noémi', 23 => 'Béla', 24 => 'György', 25 => 'Márk',
                26 => 'Ervin', 27 => 'Zita', 28 => 'Valéria', 29 => 'Péter', 30 => 'Katalin, Kitti'
            ],
            5 => [
                1 => 'Fülöp, Jakab', 2 => 'Zsigmond', 3 => 'Tímea, Irma', 4 => 'Mónika, Flórián', 5 => 'Györgyi',
                6 => 'Ivett, Frida', 7 => 'Gizella', 8 => 'Mihály', 9 => 'Gergely', 10 => 'Ármin, Pálmira',
                11 => 'Ferenc', 12 => 'Pongrác', 13 => 'Szervác, Imelda', 14 => 'Bonifác', 15 => 'Zsófia, Szonja',
                16 => 'Mózes, Botond', 17 => 'Paszkál', 18 => 'Erik, Alexandra', 19 => 'Ivó, Milán', 20 => 'Bernát, Felícia',
                21 => 'Konstantin', 22 => 'Júlia, Rita', 23 => 'Dezső', 24 => 'Eszter, Eliza', 25 => 'Orbán',
                26 => 'Fülöp, Evelin', 27 => 'Hella', 28 => 'Emil, Csanád', 29 => 'Magdolna', 30 => 'Janka, Zsanett',
                31 => 'Angéla, Petronella'
            ],
            6 => [
                1 => 'Tünde', 2 => 'Kármen, Anita', 3 => 'Klotild, Cecília', 4 => 'Bulcsú', 5 => 'Fatime, Fatima',
                6 => 'Norbert, Cintia', 7 => 'Róbert', 8 => 'Medárd', 9 => 'Félix', 10 => 'Margit, Gréta',
                11 => 'Barnabás', 12 => 'Villő', 13 => 'Antal, Anett', 14 => 'Vazul', 15 => 'Jolán, Vid',
                16 => 'Jusztin', 17 => 'Laura, Alida', 18 => 'Arnold, Levente', 19 => 'Gyárfás', 20 => 'Rafael',
                21 => 'Alajos, Leila', 22 => 'Paulina', 23 => 'Zoltán', 24 => 'Iván', 25 => 'Vilmos',
                26 => 'János, Pál', 27 => 'László', 28 => 'Levente, Irén', 29 => 'Péter, Pál', 30 => 'Pál'
            ],
            7 => [
                1 => 'Tihamér, Annamária', 2 => 'Ottó', 3 => 'Kornél, Soma', 4 => 'Ulrik', 5 => 'Emese, Sarolta',
                6 => 'Csaba', 7 => 'Apollónia', 8 => 'Ellák', 9 => 'Lukrécia', 10 => 'Amália',
                11 => 'Nóra, Lili', 12 => 'Izabella, Dalma', 13 => 'Jenő', 14 => 'Örs, Stella', 15 => 'Henrik, Roland',
                16 => 'Vali', 17 => 'Endre, Elek', 18 => 'Frigyes', 19 => 'Emília', 20 => 'Illés',
                21 => 'Dániel, Daniella', 22 => 'Magdolna', 23 => 'Lenke', 24 => 'Kinga, Kincső', 25 => 'Kristóf, Jakab',
                26 => 'Anna, Anikó', 27 => 'Olga, Liliána', 28 => 'Szabolcs', 29 => 'Márta, Flóra', 30 => 'Judit, Xénia',
                31 => 'Oszkár'
            ],
            8 => [
                1 => 'Boglárka', 2 => 'Lehel', 3 => 'Hermina', 4 => 'Domonkos, Dominika', 5 => 'Krisztina',
                6 => 'Berta, Bettina', 7 => 'Ibolya', 8 => 'László', 9 => 'Emőd', 10 => 'Lőrinc',
                11 => 'Zsuzsanna, Tiborc', 12 => 'Klára', 13 => 'Ipoly', 14 => 'Marcell', 15 => 'Mária',
                16 => 'Ábrahám', 17 => 'Jácint', 18 => 'Ilona', 19 => 'Huba', 20 => 'István',
                21 => 'Sámuel, Hajna', 22 => 'Menyhért', 23 => 'Bence', 24 => 'Bertalan', 25 => 'Lajos, Patrícia',
                26 => 'Izsó', 27 => 'Gáspár', 28 => 'Ágoston', 29 => 'Beatrix, Erna', 30 => 'Rózsa',
                31 => 'Erika, Bella'
            ],
            9 => [
                1 => 'Egyed, Egon', 2 => 'Rezső', 3 => 'Hilda', 4 => 'Rozália', 5 => 'Viktor, Lőrinc',
                6 => 'Zakariás', 7 => 'Regina', 8 => 'Mária, Adrienn', 9 => 'Ádám', 10 => 'Nikolett, Hunor',
                11 => 'Teodóra', 12 => 'Mária', 13 => 'Kornél', 14 => 'Szeréna, Roxána', 15 => 'Enikő, Melitta',
                16 => 'Edit', 17 => 'Zsófia', 18 => 'Diána', 19 => 'Vilhelmina', 20 => 'Friderika',
                21 => 'Máté, Mirella', 22 => 'Mór, Gellért', 23 => 'Tekla, Líviusz', 24 => 'Gellért, Mercédesz', 25 => 'Eufrozina, Kende',
                26 => 'Jusztina', 27 => 'Adalbert', 28 => 'Vencel', 29 => 'Mihály', 30 => 'Jeromos'
            ],
            10 => [
                1 => 'Malvin', 2 => 'Petra', 3 => 'Helga', 4 => 'Ferenc', 5 => 'Aurél',
                6 => 'Brúnó, Renáta', 7 => 'Amália', 8 => 'Koppány', 9 => 'Dénes', 10 => 'Gedeon',
                11 => 'Brigitta', 12 => 'Miksa', 13 => 'Kálmán, Ede', 14 => 'Helén', 15 => 'Teréz',
                16 => 'Gál', 17 => 'Hedvig', 18 => 'Lukács', 19 => 'Nándor', 20 => 'Vendel',
                21 => 'Orsolya', 22 => 'Előd', 23 => 'Gyöngyi', 24 => 'Salamon', 25 => 'Blanka, Bianca',
                26 => 'Dömötör', 27 => 'Szabina', 28 => 'Simon, Szimonetta', 29 => 'Nárcisz', 30 => 'Alfonz',
                31 => 'Farkas'
            ],
            11 => [
                1 => 'Marianna', 2 => 'Achilles', 3 => 'Győző', 4 => 'Károly', 5 => 'Imre',
                6 => 'Lénárd', 7 => 'Rezső', 8 => 'Zsombor', 9 => 'Tivadar', 10 => 'Réka',
                11 => 'Márton', 12 => 'Jónás, Renátó', 13 => 'Szilvia', 14 => 'Aliz', 15 => 'Albert, Lipót',
                16 => 'Ödön', 17 => 'Hortenzia, Gergő', 18 => 'Jenő', 19 => 'Erzsébet', 20 => 'Jolán',
                21 => 'Olivér', 22 => 'Cecília', 23 => 'Klementina', 24 => 'Emma', 25 => 'Katalin',
                26 => 'Virág', 27 => 'Virgil', 28 => 'Stefánia', 29 => 'Taksony', 30 => 'András, Andor'
            ],
            12 => [
                1 => 'Elza', 2 => 'Melinda, Vivien', 3 => 'Ferenc, Olívia', 4 => 'Borbála, Barbara', 5 => 'Vilma',
                6 => 'Miklós', 7 => 'Ambrus', 8 => 'Mária', 9 => 'Natália', 10 => 'Judit',
                11 => 'Árpád', 12 => 'Gabriella', 13 => 'Luca, Otília', 14 => 'Szilárda', 15 => 'Valér',
                16 => 'Etelka, Aletta', 17 => 'Lázár, Olimpia', 18 => 'Auguszta', 19 => 'Viola', 20 => 'Teofil',
                21 => 'Tamás', 22 => 'Zénó', 23 => 'Viktória', 24 => 'Ádám, Éva', 25 => 'Eugénia',
                26 => 'István', 27 => 'János', 28 => 'Kamilla', 29 => 'Tamás, Szeréna', 30 => 'Dávid',
                31 => 'Szilveszter'
            ]
        ];
    
        public function __construct() {
            add_shortcode('nevnapok', [$this, 'render_shortcode']);
        }
    
        /**
         * Névnap lekérése adott dátumra (kezeli a szökőéveket)
         */
        private function get_nevnap_by_timestamp($timestamp) {
            $month = (int)date('n', $timestamp);
            $day = (int)date('j', $timestamp);
            $is_leap_year = (date('L', $timestamp) == 1);
    
            // Szökőév esetén február 24. után a névnapok 1 nappal eltolódnak
            if ($is_leap_year && $month == 2) {
                if ($day == 24) {
                    return 'Szökőnap (Nincs külön névnap)';
                } elseif ($day > 24) {
                    $day--; // Február 25-29. a 24-28-ai névnapokat kapja
                }
            }
    
            return isset(self::$nevnapok[$month][$day]) ? self::$nevnapok[$month][$day] : 'Ismeretlen';
        }
    
        /**
         * Shortcode megjelenítése: [nevnapok]
         */
        public function render_shortcode($atts) {
            // Alapértelmezett beállítások felülírhatósága
            $atts = shortcode_atts([
                'mai_label'     => 'Mai névnap:',
                'holnapi_label' => 'Holnapi névnap:',
                'layout'        => 'block' // 'block' vagy 'inline'
            ], $atts, 'nevnapok');
    
            // WordPress időzóna beállítása alapján kérjük le az időt
            $now = current_time('timestamp');
            $tomorrow = strtotime('+1 day', $now);
    
            $mai_nevnap = $this->get_nevnap_by_timestamp($now);
            $holnapi_nevnap = $this->get_nevnap_by_timestamp($tomorrow);
    
            // HTML kimenet előállítása
            ob_start();
            ?>
            <div class="magyar-nevnapok-container layout-<?php echo esc_attr($atts['layout']); ?>">
                <div class="nevnap-item nevnap-mai">
                    <span class="nevnap-label"><?php echo esc_html($atts['mai_label']); ?></span>
                    <span class="nevnap-name"><?php echo esc_html($mai_nevnap); ?></span>
                </div>
                <div class="nevnap-item nevnap-holnapi">
                    <span class="nevnap-label"><?php echo esc_html($atts['holnapi_label']); ?></span>
                    <span class="nevnap-name"><?php echo esc_html($holnapi_nevnap); ?></span>
                </div>
            </div>
            <style>
                .magyar-nevnapok-container.layout-inline {
                    display: flex;
                    gap: 15px;
                    align-items: center;
                }
                .magyar-nevnapok-container .nevnap-label {
                    font-weight: bold;
                    margin-right: 4px;
                }
            </style>
            <?php
            return ob_get_clean();
        }
    }
    
    // Bővítmény inicializálása
    new Magyar_Nevnapok_Plugin();
    

    Használati útmutató

    1. Telepítés:
      • Hozz létre egy magyar-nevnapok nevű mappát a weboldalad wp-content/plugins/ könyvtárában.
      • Mentsd el a fenti kódot magyar-nevnapok.php néven ebbe a mappába.
      • Lépj be a WordPress admin felületére, és kapcsold be a Magyar Névnapok (Mai és Holnapi) bővítményt.
    2. Megjelenítés Shortcode-csal:Bármelyik oldalra, bejegyzésbe, widgetsávba vagy fejlécbe beillesztheted az alábbi shortcode-ok egyikét:
      • Alapértelmezett (blokk elrendezés):[ nevnapok ]
      • Egymás melletti (egysoros) elrendezés:[ nevnapok layout="inline" ]
      • Egyedi feliratok használata:[ nevnapok mai_label="Ma:" holnapi_label="Holnap:" layout="inline" ]

    1.Fájl létrehozása:

    Hozd létre a magyar-nevnapok.php fájlt a wp-content/plugins/magyar-nevnapok/ könyvtárban.

    2.Aktiválás:

    Lépj be a WordPress Adminba -> Bővítmények, majd aktiváld a „Magyar Névnapok” nevű bővítményt.

    3.Shortcode elhelyezése:

    Szúrd be a [ nevnapok ] shortcode-ot tetszőleges oldalba, blokkba vagy widgetbe.

  • POM avagy Page Object Model

    Mire jó az AI? Hát például arra, hogy ha írni szeretnék egy témáról, de lusta vagyok, az egyébként is Google segítsével összeszedett információkat rendszerbe szedni, akkor relatív gyorsan kézhez kapok egy bejegyzést.

    A Page Object Model (POM) a szoftvertesztelés-automatizálás egyik legnépszerűbb és leghatékonyabb tervezési mintája (Design Pattern). A lényege, hogy az alkalmazás egyes weboldalait (vagy oldalrészeit) különálló objektumokként képezi le a kód szintjén.

    Miért érdemes használni?

    • Karbantarthatóság: Ha megváltozik egy gomb ID-ja vagy egy mező elhelyezkedése, elegendő azt egyetlen helyen (a Page Object osztályban) átírni, a teszteseteket nem kell bántani.
    • Kód újrafelhasználhatósága: A gyakori műveletek (pl. bejelentkezés, keresés) egyszer vannak megírva, és bármelyik tesztből meghívhatók.
    • Olvashatóság: A tesztkód tiszta, átlátható és üzleti logikát követő lesz, rejtve a techniai megvalósítást (pl. DOM-lekérdezéseket).

    Minta megvalósítás (Python + Selenium)

    Az alábbiakban egy klasszikus bejelentkezési folyamat példáján keresztül látható a POM működése.

    A Page Object osztály (login_page.py)

    Ez az osztály tartalmazza az oldal elemeinek azonosítóit (lokátorait) és a végezhető műveleteket.

    Python

    from selenium.webdriver.common.by import By
    
    class LoginPage:
        def __init__(self, driver):
            self.driver = driver
            
            # Lokátorok (különválasztva a logikától)
            self._username_input = (By.ID, "username")
            self._password_input = (By.ID, "password")
            self._login_button = (By.CSS_SELECTOR, "button[type='submit']")
            self._error_message = (By.CLASS_NAME, "flash-error")
    
        # Akciók / Műveletek
        def enter_username(self, username: str):
            self.driver.find_element(*self._username_input).send_keys(username)
    
        def enter_password(self, password: str):
            self.driver.find_element(*self._password_input).send_keys(password)
    
        def click_login(self):
            self.driver.find_element(*self._login_button).click()
    
        # Összetett folyamat (Láncolt / Magasabb szintű akció)
        def login(self, username: str, password: str):
            self.enter_username(username)
            self.enter_password(password)
            self.click_login()
    
        def get_error_message(self) -> str:
            return self.driver.find_element(*self._error_message).text
    

    A Teszteset (test_login.py)

    A tesztkód csak a Page Object által nyújtott metódusokat használja, így végtelenül egyszerű és olvasható.

    Python

    import pytest
    from selenium import webdriver
    from login_page import LoginPage
    
    @pytest.fixture
    def driver():
        driver = webdriver.Chrome()
        driver.get("https://example.com/login")
        yield driver
        driver.quit()
    
    def test_successful_login(driver):
        login_page = LoginPage(driver)
        
        # Bejelentkezés végrehajtása
        login_page.login("valid_user", "correct_password")
        
        # Ellenőrzés (Assertion)
        assert driver.current_url == "https://example.com/dashboard"
    
    def test_failed_login(driver):
        login_page = LoginPage(driver)
        
        login_page.login("invalid_user", "wrong_password")
        
        # Hibaüzenet ellenőrzése
        assert "Érvénytelen adatok" in login_page.get_error_message()
    

    Jó tanácsok a gyakorlati alkalmazáshoz

    • Ne tegyél assertion-öket a Page Object-be: A assert kifejezések a tesztfájlban maradjanak, a Page Object csak adatot adjon vissza vagy állapotot módosítson.
    • Page Factory használata: Bizonyos keretrendszerekben (pl. Java + Selenium) a @FindBy annotációkkal még tisztább szerkezet érhető el.
    • Lazy loading: Az elemeket csak akkor kérd le a DOM-ból, amikor ténylegesen szükség van rájuk (például dinamikus felületek esetén).

    A Page Object Model (POM) megvalósítása a modernebb eszközökkel (mint a Playwright vagy a Selenium Java-alapú változata) még letisztultabbá válik, mivel ezek a keretrendszerek beépített architektúrával támogatják a megbízhatóbb elemkezelést.

    Az alábbiakban mindkét megközelítésre találsz egy-egy komplett kódmintát.

    Playwright + TypeScript (Ajánlott / Modern megközelítés)

    A Playwright eleve magában foglalja az automatikus várakozást (Auto-waiting), így nincs szükség explicit wait-ek kézi kezelésére.

    A Page Object osztály (pages/LoginPage.ts)

    TypeScript

    import { Page, Locator } from '@playwright/test';
    
    export class LoginPage {
      readonly page: Page;
      readonly usernameInput: Locator;
      readonly passwordInput: Locator;
      readonly loginButton: Locator;
      readonly errorMessage: Locator;
    
      constructor(page: Page) {
        self.page = page;
        // A Playwright-ban a lokátorok deklaratívak, nem kérdezik le azonnal a DOM-ot
        this.usernameInput = page.locator('#username');
        this.passwordInput = page.locator('#password');
        this.loginButton = page.locator('button[type="submit"]');
        this.errorMessage = page.locator('.flash-error');
      }
    
      async goto() {
        await this.page.goto('https://example.com/login');
      }
    
      async login(username: string, password: string) {
        await this.usernameInput.fill(username);
        await this.passwordInput.fill(password);
        await this.loginButton.click();
      }
    
      async getErrorMessage(): Promise<string> {
        return await this.errorMessage.textContent() || '';
      }
    }
    

    A Tesztfájl (tests/login.spec.ts)

    TypeScript

    import { test, expect } from '@playwright/test';
    import { LoginPage } from '../pages/LoginPage';
    
    test.describe('Bejelentkezés tesztek', () => {
      let loginPage: LoginPage;
    
      test.beforeEach(async ({ page }) => {
        loginPage = new LoginPage(page);
        await loginPage.goto();
      });
    
      test('Sikeres bejelentkezés', async ({ page }) => {
        await loginPage.login('valid_user', 'correct_password');
        
        // Ellenőrzés Playwright assert-tel
        await expect(page).toHaveURL('https://example.com/dashboard');
      });
    
      test('Hibás bejelentkezés', async () => {
        await loginPage.login('invalid_user', 'wrong_password');
        
        await expect(loginPage.errorMessage).toBeVisible();
        await expect(loginPage.errorMessage).toContainText('Érvénytelen adatok');
      });
    });
    

    Java + Selenium (Klasszikus keretrendszer)

    Java környezetben a PageFactory és a @FindBy annotációk használata a legelterjedtebb POM minta.

    A Page Object osztály (LoginPage.java)

    Java

    package pages;
    
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.support.FindBy;
    import org.openqa.selenium.support.PageFactory;
    
    public class LoginPage {
        private WebDriver driver;
    
        // Lokátorok deklarálása annotációkkal
        @FindBy(id = "username")
        private WebElement usernameInput;
    
        @FindBy(id = "password")
        private WebElement passwordInput;
    
        @FindBy(css = "button[type='submit']")
        private WebElement loginButton;
    
        @FindBy(className = "flash-error")
        private WebElement errorMessage;
    
        // Konstruktor: inicializálja a PageFactory elemeit
        public LoginPage(WebDriver driver) {
            this.driver = driver;
            PageFactory.initElements(driver, this);
        }
    
        public void enterUsername(String username) {
            usernameInput.clear();
            usernameInput.sendKeys(username);
        }
    
        public void enterPassword(String password) {
            passwordInput.clear();
            passwordInput.sendKeys(password);
        }
    
        public void clickLogin() {
            loginButton.click();
        }
    
        public void login(String username, String password) {
            enterUsername(username);
            enterPassword(password);
            clickLogin();
        }
    
        public String getErrorMessageText() {
            return errorMessage.getText();
        }
    }
    

    A Tesztosztály (JUnit 5 használatával) (LoginTest.java)

    Java

    package tests;
    
    import org.junit.jupiter.api.AfterEach;
    import org.junit.jupiter.api.BeforeEach;
    import org.junit.jupiter.api.Test;
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.chrome.ChromeDriver;
    import pages.LoginPage;
    
    import static org.junit.jupiter.api.Assertions.assertEquals;
    import static org.junit.jupiter.api.Assertions.assertTrue;
    
    public class LoginTest {
        private WebDriver driver;
        private LoginPage loginPage;
    
        @BeforeEach
        public void setUp() {
            driver = new ChromeDriver();
            driver.manage().window().maximize();
            driver.get("https://example.com/login");
            loginPage = new LoginPage(driver);
        }
    
        @Test
        public void testSuccessfulLogin() {
            loginPage.login("valid_user", "correct_password");
            assertEquals("https://example.com/dashboard", driver.getCurrentUrl());
        }
    
        @Test
        public void testFailedLogin() {
            loginPage.login("invalid_user", "wrong_password");
            assertTrue(loginPage.getErrorMessageText().contains("Érvénytelen adatok"));
        }
    
        @AfterEach
        public void tearDown() {
            if (driver != null) {
                driver.quit();
            }
        }
    }
    

    Főbb különbségek a két megközelítés között

    SzempontPlaywright (TypeScript)Selenium (Java)
    LokátorokA Locator objektumok deklaratívak (auto-wait)A @FindBy elemek lekezeléséhez várakozás (WebDriverWait) kellhet
    PárhuzamosításAlapból támogatott a tesztfutztatóbanTestNG vagy JUnit szálkezelést igényel
    InicializálásEgyszerű objektumkonstruktorPageFactory.initElements() hívás szükséges

    A Playwright Custom Fixture funkciója segítségével a Page Object osztályok példányosítását teljesen kiszervezheted a tesztekből. Ezzel elkerülhető, hogy minden egyes tesztfájlban manuálisan kelljen megadni a const loginPage = new LoginPage(page) sorokat.

    A tesztek így közvetlenül paraméterként kérhetik el a szükséges oldalobjektumokat, az infrastruktúra pedig automatikusan példányosítja és előkészíti azokat.

    A Page Object osztályok (pages/)

    A megszokott módon hozzuk létre az oldalklasszokat.

    TypeScript

    // pages/LoginPage.ts
    import { Page, Locator } from '@playwright/test';
    
    export class LoginPage {
      readonly page: Page;
      readonly usernameInput: Locator;
    
      constructor(page: Page) {
        this.page = page;
        this.usernameInput = page.locator('#username');
      }
    
      async goto() {
        await this.page.goto('/login');
      }
    }
    
    // pages/DashboardPage.ts
    import { Page, Locator } from '@playwright/test';
    
    export class DashboardPage {
      readonly page: Page;
      readonly userMenu: Locator;
    
      constructor(page: Page) {
        this.page = page;
        this.userMenu = page.locator('#user-menu');
      }
    }
    

    A Custom Fixture létrehozása (fixtures/baseTest.ts)

    Itt terjesztjük ki a Playwright alapértelmezett test objektumát, megadva a saját Page Object fixture-jeinket.

    TypeScript

    import { test as base } from '@playwright/test';
    import { LoginPage } from '../pages/LoginPage';
    import { DashboardPage } from '../pages/DashboardPage';
    
    // 1. Típusdefiníció a fixture-ökhöz
    type MyFixtures = {
      loginPage: LoginPage;
      dashboardPage: DashboardPage;
    };
    
    // 2. A 'base.extend' használatával regisztráljuk a saját fixture-jeinket
    export const test = base.extend<MyFixtures>({
      // Automatikusan példányosítja a LoginPage-t és igény esetén megnyitja az oldalt
      loginPage: async ({ page }, use) => {
        const loginPage = new LoginPage(page);
        await loginPage.goto(); // Akár előkészítő lépéseket is tehetünk ide
        await use(loginPage);   // Átadja az objektumot a tesztnek
      },
    
      // Példányosítja a DashboardPage-t
      dashboardPage: async ({ page }, use) => {
        const dashboardPage = new DashboardPage(page);
        await use(dashboardPage);
      },
    });
    
    // Az 'expect'-et is exportáljuk a kényelmes importálásért
    export { expect } from '@playwright/test';
    

    A Fixture-ök használata a tesztekben (tests/login.spec.ts)

    A @playwright/test helyett a saját fixtures/baseTest fájlunkból importáljuk a test objektumot. A tesztfüggvény paramétereként megadott nevek alapján a Playwright automatikusan beadagolja a megfelelő példányokat.

    TypeScript

    // Figyelem: A saját kiterjesztett test objektumunkat importáljuk!
    import { test, expect } from '../fixtures/baseTest';
    
    test('Sikeres bejelentkezés custom fixture-rel', async ({ loginPage, dashboardPage }) => {
      // A loginPage.goto() már lefutott a fixture-ben!
      await loginPage.usernameInput.fill('standard_user');
      
      // A dashboardPage is azonnal használható, nem kellett 'new'-val példányosítani
      await expect(dashboardPage.userMenu).toBeVisible();
    });
    

    Miért ez a legnépszerűbb Playwright architektúra?

    • Nulla boilerplate kód: Nem kell beforeEach blokkokban példányosítani az osztályokat.
    • Lazy loading (Lusta betöltés): Csak azok a Page Object-ek példányosulnak, amiket a teszt fejlécében ({ loginPage, dashboardPage }) kifejezetten elkérsz.
    • Beépített tear-down: A fixture-ben a use() hívás utáni kódrészlet automatikusan lefut a teszt végén (takarításra, kijelentkezésre ideális).
  • Duplikációk keresése, avagy power shell script íratása AI-val

    Duplikációk keresése, avagy power shell script íratása AI-val

    Ma ilyen AI-s napom van. Egyre jobban tör be a mindennapi munkámba, és úgy tűnik, hogy a hétvégeimet is ez teszi ki. Miért? Kérdezheted joggal. Megmondom. Az IT-ban, de gyanítom a többi szakmában is jobb, ha értünk hozzá, és megtanuljuk rutinosan használni, mintsem lemaradjunk róla, és nézzük bambán, ahogy az élet felsuhan előttünk.

    Nézzük, mire is mentem elsőre.

    Az alábbi PowerShell-script végigpásztázza a megadott mappát (és annak almappáit), a fájlok SHA256 hash értéke alapján azonosítja a duplikációkat, majd az eredményt kiírja a képernyőre és elmenti egy CSV-fájlba.

    PowerShell duplikáció-kereső script

    Hozz létre egy find-duplicates.ps1 fájlt, és másold bele az alábbi kódot:

    PowerShell

    # --- BEÁLLÍTÁSOK ---
    $TargetFolder = "C:\Elérési\Út\A\Mappához"   # Cseréld ki a vizsgálandó mappa útvonalára
    $OutputFile   = "C:\Elérési\Út\duplikatumok.csv" # Ide menti az eredményt
    
    Write-Host "Fájlok indexelése és hash értékek számítása..." -ForegroundColor Cyan
    
    # 1. Összes fájl lekérése és méret szerinti csoportosítása (gyorsítási lépés)
    $fileGroups = Get-ChildItem -Path $TargetFolder -Recurse -File -ErrorAction SilentlyContinue |
        Group-Group-Object -Property Length |
        Where-Object { $_.Count -gt 1 }
    
    $duplicates = @()
    $totalGroups = $fileGroups.Count
    $counter = 0
    
    # 2. Csak az azonos méretű fájlok SHA256 hash-ének ellenőrzése
    foreach ($group in $fileGroups) {
        $counter++
        Write-Progress -Activity "Duplikátumok keresése" -Status "Fájlcsoportok feldolgozása: $counter / $totalGroups" -PercentComplete (($counter / $totalGroups) * 100)
    
        $hashTable = $group.Group | Get-FileHash -Algorithm SHA256 | Group-Object -Property Hash | Where-Object { $_.Count -gt 1 }
    
        foreach ($hashGroup in $hashTable) {
            foreach ($file in $hashGroup.Group) {
                $duplicates += [PSCustomObject]@{
                    Hash     = $hashGroup.Name
                    Path     = $file.Path
                    SizeMB   = [math]::Round((Get-Item $file.Path).Length / 1MB, 2)
                }
            }
        }
    }
    
    # 3. Eredmények megjelenítése és mentése
    if ($duplicates.Count -gt 0) {
        Write-Host "`nTalált duplikátumok:" -ForegroundColor Green
        $duplicates | Format-Table -AutoSize -Property Hash, SizeMB, Path
    
        $duplicates | Export-Csv -Path $OutputFile -NoTypeInformation -Encoding UTF8
        Write-Host "Az eredmények elmentve ide: $OutputFile" -ForegroundColor Yellow
    } else {
        Write-Host "`nNem találhatók duplikált fájlok." -ForegroundColor Green
    }
    

    Így használd:

    1. Nyisd meg a PowerShell-t (akár Rendszergazdaként, ha védett mappákat is vizsgálsz).
    2. Írd át a $TargetFolder változót a keresni kívánt mappára.
    3. Futtasd a scriptet:PowerShell.\find-duplicates.ps1

    Megjegyzés: Ha a PowerShell letiltja a scriptek futtatását, futtasd ezt a parancsot a munkamenetben:

    Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process

    Majd második próbálkozásra

    Módosítottam a scriptet, hogy a célmappát (és opcióként a kimeneti CSV-fájl helyét is) paraméterként fogadja el.

    A módosított script kódja:

    PowerShell

    <#
    .SYNOPSIS
        Fájl duplikációkat kereső és gyűjtő PowerShell script.
    
    .DESCRIPTION
        A script végigpásztázza a paraméterként megadott mappát (és annak almappáit),
        és az SHA256 hash értékek alapján azonosítja a duplikátumokat.
        Az eredményt megjeleníti a konzolon és CSV-fájlba menti.
    
    .PARAMETER TargetFolder
        A vizsgálandó mappa útvonala (kötelező paraméter).
    
    .PARAMETER OutputFile
        A kimeneti CSV fájl útvonala (opcionális, alapértelmezett: .\duplikatumok.csv).
    #>
    
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0, HelpMessage = "Kérjük, adja meg a vizsgálandó mappa útvonalát!")]
        [ValidateScript({ Test-Path $_ -PathType Container })]
        [string]$TargetFolder,
    
        [Parameter(Mandatory = $false, Position = 1)]
        [string]$OutputFile = ".\duplikatumok.csv"
    )
    
    # Abszolút útvonalak feloldása
    $TargetFolder = (Resolve-Path $TargetFolder).Path
    $OutputFile   = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutputFile)
    
    Write-Host "Mappa elemzése: $TargetFolder" -ForegroundColor Cyan
    Write-Host "Fájlok indexelése és méret szerinti csoportosítása..." -ForegroundColor Cyan
    
    # 1. Összes fájl lekérése és méret szerinti csoportosítása (gyorsítási lépés)
    $fileGroups = Get-ChildItem -Path $TargetFolder -Recurse -File -ErrorAction SilentlyContinue |
        Group-Object -Property Length |
        Where-Object { $_.Count -gt 1 }
    
    $duplicates = @()
    $totalGroups = $fileGroups.Count
    $counter = 0
    
    if ($totalGroups -eq 0) {
        Write-Host "`nNem találhatók azonos méretű fájlok, nincsenek duplikátumok." -ForegroundColor Green
        exit
    }
    
    # 2. Csak az azonos méretű fájlok SHA256 hash-ének ellenőrzése
    foreach ($group in $fileGroups) {
        $counter++
        Write-Progress -Activity "Duplikátumok keresése" -Status "Fájlcsoportok feldolgozása: $counter / $totalGroups" -PercentComplete (($counter / $totalGroups) * 100)
    
        $hashTable = $group.Group | Get-FileHash -Algorithm SHA256 | Group-Object -Property Hash | Where-Object { $_.Count -gt 1 }
    
        foreach ($hashGroup in $hashTable) {
            foreach ($file in $hashGroup.Group) {
                $duplicates += [PSCustomObject]@{
                    Hash     = $hashGroup.Name
                    Path     = $file.Path
                    SizeMB   = [math]::Round((Get-Item $file.Path).Length / 1MB, 2)
                }
            }
        }
    }
    
    # 3. Eredmények megjelenítése és mentése
    if ($duplicates.Count -gt 0) {
        Write-Host "`nTalált duplikátumok:" -ForegroundColor Green
        $duplicates | Format-Table -AutoSize -Property Hash, SizeMB, Path
    
        $duplicates | Export-Csv -Path $OutputFile -NoTypeInformation -Encoding UTF8
        Write-Host "Az eredmények elmentve ide: $OutputFile" -ForegroundColor Yellow
    } else {
        Write-Host "`nNem találhatók duplikált fájlok." -ForegroundColor Green
    }
    

    Használati példák:

    1. Alapértelmezett használat (a mappa elérést megadva, az eredmény a beállítások szerint a duplikatumok.csv-be kerül):PowerShell.\find-duplicates.ps1 -TargetFolder "C:\Users\Public\Documents"
    2. Pozicionális paraméterként (nem kell kiírni a névadó kapcsolót):PowerShell.\find-duplicates.ps1 "D:\Képek"
    3. Egyedi CSV kimeneti fájlnév megadásával:PowerShell.\find-duplicates.ps1 -TargetFolder "D:\Dokumentumok" -OutputFile "C:\Riportok\doksi_duplikatumok.csv"

    Újdonságok a scriptben:

    • Automatikus mappa-ellenőrzés (ValidateScript): Ha nem létező vagy hibás mappa-útvonalat adsz meg, a script még a futás előtt jelez és leáll.
    • Mappa-kötelezőség (Mandatory = $true): Ha paraméter nélkül indítod el a scriptet, a PowerShell automatikusan megkérdezi tőled a célmappa útvonalát.

    Nem vagyok teljes mértékben elragadtatva az eredménytől, de mára ez bőven elég volt, majd folytatjuk…

  • Playwright 1.63 újdonságok

    Playwright 1.63 újdonságok

    Újdonságok

    A Playwright 1.63 frissítése megérkezett, és számtalan olyan funkciót hoz, amelyek lényegesen egyszerűbbé teszik a komplex tesztelési munkafolyamatokat, a párhuzamosítást és a hibakeresést.

    Az alábbiakban összefoglaltuk a kiadás legfontosabb újdonságait és fejlesztéseit.

    Key Features & Újdonságok

    1. Test Locks (Tesztreteszek)

    • Megoldást nyújt a közös erőforrások (pl. központi adatbázis, globális fiókbeállítások vagy külső API-k) használata miatti ütközésekre.
    • Lehetővé teszi zárolások (locks) megadását, így a tesztek automatikusan sorba állnak a kritikus erőforrások eléréséhez, elkerülve a párhuzamos futtatásból eredő hibákat.

    2. Step paraméterek és feliratok (test.step)

    • A test.step() mostantól elfogad egy params opciót, amellyel saját metaadatokat és paramétereket adhatsz át a lépésekhez.
    • A riportokban és a Trace Viewerben tisztábban nyomon követhetők az egyedi lépésekhez tartozó változók és hívási argumentumok.

    3. Aria Traces & Bővített Tracing

    • A Trace Viewer új aria pillanatképeket (snapshots) támogat.
    • Megkönnyíti a hozzáférhetőségi (accessibility) elemek és az ARIA fa állapotának elemzését hibakeresés közben.

    4. Új Scroll Opciók az akcióknál

    • A legtöbb akciónál (pl. .click()) megjelent egy új scroll opció ("auto" | "none").
    • Lehetőséget biztosít arra, hogy kikapcsold a Playwright automatikus „scroll-into-view” viselkedését, ha egy elem pozícióját manuálisan szeretnéd kezelni.

    5. Új teszt opciók és CLI parancsok

    • Nagyobb hozzáférhetőség tesztelése: Új reducedMotion, forcedColors és contrast opciók a testOptions alatt.
    • --add-reporter parancssori opció: Bővítheted a meglévő riporterek listáját ahelyett, hogy felülírnád a playwright.config.ts-ben megadottakat.
    • omitTags opció: Letisztultabb tesztcímek a riportokban (suppress-eli az automatikusan hozzáfűzött tegeket a list, line, dot, github és junit riportereknél).

    Eltávolított (Deprecated) API-k

    A 1.63-as verzió végleg kivezeti a régen elavultnak jelölt funkciókat:

    • Locator.ariaRef() (használd helyette a locator.ariaSnapshot() funkciót).
    • handle opció a BrowserContext.exposeBinding és Page.exposeBinding esetében.
    • logger opció a BrowserType.connect és connectOverCDP metódusoknál.
    • videosPath és videoSize konfig opciók (használd a recordVideo-t).

    Frissítés

    A legújabb verzióra való átálláshoz futtasd a projekt gyökerében az alábbi parancsot:

    Bash

    npm install -D @playwright/test@latest
    npx playwright install

    Test Lock minta

    A Playwright 1.63-ban megjelent Test Locks (Tesztreteszek) opció segítségével megakadályozhatod, hogy bizonyos tesztek egyszerre futjanak és ütközzenek a közös erőforrásokon (pl. ugyanazon tesztfiók beállításai, adatbázis vagy külső API-k), miközben a többi független teszt továbbra is párhuzamosan fut.

    Az alábbiakban bemutatjuk a legfontosabb használati módokat kódpéldákkal.

    1. Egyedi teszt zárolása egy megnevezett retesszel (lock)

    Ha van két vagy több teszted, amelyek ugyanazt a megosztott erőforrást módosítják, megadhatsz nekik egy azonos lock nevet az opciók között:

    TypeScript

    import { test, expect } from '@playwright/test';
    
    // Ez a teszt lefoglalja az 'admin-account' reteszt
    test('Adminisztrátor frissíti a cég nevét', { lock: 'admin-account' }, async ({ page }) => {
      await page.goto('https://example.com/admin/settings');
      await page.getByLabel('Cégnév').fill('Új Cégnév Kft.');
      await page.getByRole('button', { name: 'Mentés' }).click();
    
      await expect(page.getByText('Sikeres mentés')).toBeVisible();
    });
    
    // Ez a teszt megvárja, amíg az előző teszt elengedi az 'admin-account' reteszt
    test('Adminisztrátor módosítja az időzónát', { lock: 'admin-account' }, async ({ page }) => {
      await page.goto('https://example.com/admin/settings');
      await page.getByLabel('Időzóna').selectOption('Europe/Budapest');
      await page.getByRole('button', { name: 'Mentés' }).click();
    
      await expect(page.getByText('Sikeres mentés')).toBeVisible();
    });
    

    Hogyan működik? Amíg a Worker 1 az első tesztet futtatja az admin-account retesszel, addig a Worker 2 nem kezdi el a második tesztet. A két teszt egymás után fog lefutni, de a csomag többi, független tesztje (pl. termékkeresés, kosárkezelés) továbbra is teljesen párhuzamosan fut a többi workeren.

    2. Egész tesztcsoport zárolása (test.describe)

    Ha egy egész blokknyi teszt egyetlen közös erőforráson dolgozik, a lock opciót közvetlenül a test.describe() csoportra is ráteheted:

    TypeScript

    import { test, expect } from '@playwright/test';
    
    test.describe('Adatbázis konfigurációs tesztek', { lock: 'database-config' }, () => {
      
      test('Alapértelmezett nyelv beállítása', async ({ page }) => {
        // ... teszt kód
      });
    
      test('Rendszerértesítések kikapcsolása', async ({ page }) => {
        // ... teszt kód
      });
    
    });
    

    3. Több retesz egyidejű használata (Array)

    Egy teszt akár több megosztott erőforrás zárolását is kérheti egyszerre. Ehhez adj meg egy tömböt a lock paraméterben:

    TypeScript

    import { test, expect } from '@playwright/test';
    
    test(
      'Fizetési átjáró és szállítási beállítások egyidejű frissítése',
      { lock: ['payment-sandbox', 'shipping-api'] },
      async ({ page }) => {
        // Ez a teszt csak akkor indul el, ha mindkét retesz szabad
        await page.goto('https://example.com/admin/integrations');
        // ... teszt kód
      }
    );
  • JAVA method overloading vs overriding

    Ez a két, vagy inkább egyként feltett kérdés szinte minden technikai interjú kapcsán előkerül. Miért? Mert nagyon hasznos tud lenni, és átláthatóbb kódot lehet így írni. Na de nézzük meg, hogy mik is ezek! Illetve inkább idézek a saját szavaim helyett, mert nem biztos, hogy ennyire pontosan meg tudnám fogalmazni a definícióját.

    Method overloading

    Javában a metódusok túlterhelése lehetővé teszi a fejlesztők számára, hogy több metódust definiáljanak ugyanazzal a névvel, de különböző paraméterekkel. Ez a rugalmasság olvashatóbbá és adaptívabbá teszi a kódot, mivel egy metódus különböző variációi különböző argumentumtípusokat vagy -mennyiségeket kezelhetnek. Azonban annak megértése, hogy a Java hogyan választja ki, hogy melyik metódust hívja meg, elengedhetetlen a hibamentes kód írásához.

    Egyszerű példa kód

    class Calculator {
        void add(int a, int b) {
            System.out.println("add(int, int) called: " + (a + b));
        }
    
        void add(double a, double b) {
            System.out.println("add(double, double) called: " + (a + b));
        }
    }

    Mi is történik a fenti példában? Két metódusnak ugyanaz a neve, de a bemenő paraméterinek a típusa különböző, így tehát az lesz használva, ahol egyeznek a paraméterek. Automatizálás során például egy jó példa lehet, ha a metódus integert vagy stringet tud fogadni, és annak megfelelően végrehajtani mondjuk egy kiválasztást. Ilyenkor ugye a bemenő paraméter típusa fogja meghatározni, hogy melyik függvény fut le, és mindkét függvény ugyanarra a névre hallgat.

    Method overriding

    A metódusok felülírása az objektumorientált programozás (OOP) alapvető koncepciója, amely lehetővé teszi egy alosztály számára, hogy egy olyan metódus specifikus implementációját biztosítsa, amely már definiálva van a szuperosztályában. Kulcsfontosságú szerepet játszik a futásidejű polimorfizmus elérésében Java-ban.

    • Öröklődés: A metódusok felülírása csak öröklődés esetén történik, ami azt jelenti, hogy egy alosztály örökli a metódusokat és tulajdonságokat egy szuperosztálytól.
    • Ugyanazon metódus szignatúra: Ahhoz, hogy a metódusok felülírása megtörténjen, az alosztályban lévő metódusnak ugyanazzal a névvel, visszatérési típussal és paraméterekkel kell rendelkeznie, mint a szuperosztályban lévő metódusnak.
    • Felülírás vs. túlterhelés: A metódusok felülírását nem szabad összekeverni a metódusok túlterhelésével. A túlterhelés azt jelenti, hogy több metódusnak ugyanaz a neve, de paramétereik különböznek, míg a felülírás az alosztályban lévő metódus újradefiniálását jelenti ugyanazzal a szignatúrával.

    Egyszerű példa kód

    class Animal {
        void sound() {
            System.out.println("This animal makes a sound");
        }
    }
    
    class Dog extends Animal {
        @Override
        void sound() {
            System.out.println("The dog barks");
        }
    }

    Miért érdemes metódusfelülírást használni?

    • Polimorfizmus: A metódusfelülírás lehetővé teszi egy alosztály számára, hogy egy adott metódushoz egy adott implementációt biztosítson, lehetővé téve a polimorfizmust. Ez azt jelenti, hogy ugyanaz a metódushívás eltérően viselkedhet attól függően, hogy melyik objektum hívja meg. Például mind az Animal, mind a Dog objektum meghívhatja a sound() metódust, de az eredmény az objektum típusától függ.
    • Dinamikus metódusküldés: Futásidőben a végrehajtandó metódust az objektum típusa határozza meg, nem a hivatkozás típusa. Ezt a folyamatot dinamikus metódusküldésnek vagy futásidejű polimorfizmusnak nevezik.
    • Újrafelhasználhatóság és bővíthetőség: A felülírás lehetővé teszi az örökölt metódusok viselkedésének kiterjesztését vagy módosítását. Ez elősegíti a kód újrafelhasználását, mivel az alosztály örökölheti a közös viselkedést, miközben további funkciókat biztosít, vagy szükség szerint felülírja a viselkedést.
    class Animal {
        void sound() {
            System.out.println("This animal makes a sound");
        }
    }
    
    class Dog extends Animal {
        @Override
        void sound() {
            super.sound();  // Calls the superclass method
            System.out.println("The dog barks");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Dog myDog = new Dog();
            myDog.sound();  // Calls the overridden method
        }
    }

    Metódus felülírásának szabályai

    • A metódusnak ugyanazzal a szignatúrával kell rendelkeznie, mint a szuperosztályban.
    • A metódusnak az eredeti metódust tartalmazó osztály alosztályában kell lennie.
    • Az alosztályban lévő metódusnak ugyanolyan vagy könnyebben hozzáférhető hozzáférési módosítóval kell rendelkeznie. Például, ha egy metódus védett a szuperosztályban, akkor nem lehet privát az alosztályban.
    • Nem írhatjuk felül a final, static vagy private metódusokat.

    Szeretem a Medium leírásait, számomra mindig is könnyen érhető, és nagyon sok témát dolgoznak fel. Csak ajánlani tudom!