Renderer telah berfungsi untuk kasus ideal — linked list dengan empat elemen, binary tree lengkap, graph kecil. Pada bagian ini kita menguji kasus edge yang sering luput dari rancangan awal: struktur kosong, tree miring (skewed tree), graph dengan cycle, dan node tunggal tanpa anak. Tujuan pengujian bukan sekadar membuktikan program berjalan, melainkan menyingkap batasan renderer agar pengguna tahu kapan harus berhati-hati.

Skenario Pengujian #

Kita akan menjalankan enam skenario berikut pada program Bagian 3 (dengan satu penyesuaian: tree mungkin saja skewed):

#StrukturKasus yang Diuji
1Linked list kosongRenderer menangani values kosong
2Linked list satu elemenTidak ada -> antar elemen
3Binary tree kosongRenderer menangani root == nullptr
4Binary tree node tunggalHanya root tanpa anak
5Binary tree skewed kiriSetiap node hanya punya anak kiri
6Graph dengan cycleCycle 0→1→2→0 tetap tercetak

Penyesuaian Kode untuk Edge Case #

Pada Bagian 3, renderer linked list dan tree sudah menangani kasus kosong secara implisit (loop tidak dieksekusi untuk values kosong; renderTree mengembalikan "<empty tree>" bila root == nullptr). Satu hal yang perlu ditambahkan: linked list kosong sebaiknya mencetak null sebagai penanda, bukan string kosong.

Berikut fungsi renderList yang diperbarui:

cpp
std::vector<std::string> renderList(const std::vector<std::string>& values) {
    if (values.empty()) return { "null" };
    std::string line;
    for (size_t i = 0; i < values.size(); ++i) {
        line += "[" + values[i] + "]";
        line += (i + 1 == values.size()) ? " -> null" : " -> ";
    }
    return { line };
}

Program Pengujian Lengkap #

Berikut program yang membangun keenam kasus uji dan mencetak hasilnya. Kita pakai kembali renderer dari Bagian 3.

cpp
#include <algorithm>
#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>

struct TreeNode {
    std::string label;
    std::unique_ptr<TreeNode> left;
    std::unique_ptr<TreeNode> right;
    explicit TreeNode(std::string l) : label(std::move(l)) {}
};
using NodePtr = std::unique_ptr<TreeNode>;

NodePtr node(std::string label, NodePtr l = nullptr, NodePtr r = nullptr) {
    auto n = std::make_unique<TreeNode>(std::move(label));
    n->left  = std::move(l);
    n->right = std::move(r);
    return n;
}

int treeHeight(const TreeNode* p) {
    if (!p) return 0;
    return 1 + std::max(treeHeight(p->left.get()), treeHeight(p->right.get()));
}

void assignColumns(const TreeNode* p, int& col,
        std::vector<std::pair<const TreeNode*, int>>& positions) {
    if (!p) return;
    assignColumns(p->left.get(), col, positions);
    positions.push_back({p, col++});
    assignColumns(p->right.get(), col, positions);
}

void drawNode(const TreeNode* p, int depth, int nodeCol,
        std::vector<std::pair<const TreeNode*, int>>& positions,
        std::vector<std::string>& grid, int colUnit) {
    if (!p) return;
    int row = depth * 2;
    int col = nodeCol * colUnit;
    std::string lbl = "[" + p->label + "]";
    for (size_t i = 0; i < lbl.size(); ++i) grid[row][col + i] = lbl[i];
    if (p->left) {
        int lc = -1;
        for (auto& pr : positions) if (pr.first == p->left.get()) { lc = pr.second; break; }
        int slashCol = (col + lc * colUnit) / 2;
        if (slashCol >= 0) grid[row + 1][slashCol] = '/';
        drawNode(p->left.get(), depth + 1, lc, positions, grid, colUnit);
    }
    if (p->right) {
        int rc = -1;
        for (auto& pr : positions) if (pr.first == p->right.get()) { rc = pr.second; break; }
        int slashCol = (col + rc * colUnit) / 2;
        if (slashCol < (int)grid[row + 1].size()) grid[row + 1][slashCol] = '\\';
        drawNode(p->right.get(), depth + 1, rc, positions, grid, colUnit);
    }
}

std::vector<std::string> renderTree(const TreeNode* root) {
    if (!root) return { "<empty tree>" };
    int col = 0;
    std::vector<std::pair<const TreeNode*, int>> positions;
    assignColumns(root, col, positions);
    int nNodes = (int)positions.size();
    int colUnit = 4;
    int width = std::max(1, nNodes * colUnit);
    int h = treeHeight(root);
    int rows = std::max(1, 2 * h - 1);
    std::vector<std::string> grid(rows, std::string(width, ' '));
    int rootCol = 0;
    for (auto& pr : positions) if (pr.first == root) { rootCol = pr.second; break; }
    drawNode(root, 0, rootCol, positions, grid, colUnit);
    for (auto& line : grid) {
        while (!line.empty() && line.back() == ' ') line.pop_back();
    }
    return grid;
}

std::vector<std::string> renderList(const std::vector<std::string>& values) {
    if (values.empty()) return { "null" };
    std::string line;
    for (size_t i = 0; i < values.size(); ++i) {
        line += "[" + values[i] + "]";
        line += (i + 1 == values.size()) ? " -> null" : " -> ";
    }
    return { line };
}

std::vector<std::string> renderGraph(
        const std::unordered_map<int, std::vector<int>>& adj, int n) {
    std::vector<std::string> lines;
    for (int u = 0; u < n; ++u) {
        std::string line = std::to_string(u) + " -> ";
        const auto& nbrs = adj.at(u);
        for (size_t i = 0; i < nbrs.size(); ++i) {
            line += std::to_string(nbrs[i]);
            line += (i + 1 == nbrs.size()) ? "" : ", ";
        }
        lines.push_back(line);
    }
    return lines;
}

void printLines(const std::vector<std::string>& lines) {
    for (const auto& l : lines) std::cout << l << "\n";
}

int main() {
    std::cout << "=== Test 1: Linked list kosong ===\n";
    printLines(renderList({}));

    std::cout << "\n=== Test 2: Linked list satu elemen ===\n";
    printLines(renderList({"a"}));

    std::cout << "\n=== Test 3: Binary tree kosong ===\n";
    printLines(renderTree(nullptr));

    std::cout << "\n=== Test 4: Binary tree node tunggal ===\n";
    auto single = node("X");
    printLines(renderTree(single.get()));

    std::cout << "\n=== Test 5: Binary tree skewed kiri ===\n";
    auto skewed = node("1", node("2", node("3", node("4"))), nullptr);
    printLines(renderTree(skewed.get()));

    std::cout << "\n=== Test 6: Graph dengan cycle ===\n";
    std::unordered_map<int, std::vector<int>> adj;
    adj[0] = {1};
    adj[1] = {2};
    adj[2] = {0};
    printLines(renderGraph(adj, 3));
    return 0;
}

Output yang Dihasilkan:

text
=== Test 1: Linked list kosong ===
null

=== Test 2: Linked list satu elemen ===
[a] -> null

=== Test 3: Binary tree kosong ===
<empty tree>

=== Test 4: Binary tree node tunggal ===
[X]

=== Test 5: Binary tree skewed kiri ===
            [1]
          /
        [2]
      /
    [3]
  /
[4]

=== Test 6: Graph dengan cycle ===
0 -> 1
1 -> 2
2 -> 0

Bedah Hasil Pengujian #

  1. Test 1 — Linked list kosong: renderer kini mengembalikan "null" alih-alih string kosong. Tanpa penanganan ini, output berupa baris kosong yang membingungkan pembaca.
  2. Test 2 — Satu elemen: loop berjalan satu kali, suffix -> null diappend dengan benar. Loop dengan i + 1 == values.size() menjadi true sehingga tidak ada -> di tengah.
  3. Test 3 — Tree kosong: guard if (!root) return { "<empty tree>" } aktif. Tanpa guard, assignColumns menerima pointer null dan dereferensi p->left menyebabkan segfault.
  4. Test 4 — Node tunggal: treeHeight mengembalikan 1, rows = 1, hanya root yang ditulis. Edge tidak ada karena tidak ada anak. Output berupa [X] saja.
  5. Test 5 — Skewed kiri: setiap node hanya punya anak kiri. In-order memberi posisi 3, 2, 1, 0 (anak terdalam dapat kolom 0, root dapat kolom 3). Hasilnya tree vertikal yang miring ke kiri — visualisasi tetap terbaca meskipun tidak ideal.
  6. Test 6 — Graph cycle: cycle 0→1→2→0 tercetak sebagai tiga baris adjacency list. Renderer graph tidak terpengaruh oleh cycle karena hanya iterasi adj[u] tanpa traversal DFS.

Ringkasan Proyek #

Kita telah membangun tool visualisasi struktur data lengkap dengan empat tahap: rancangan, model, renderer, dan pengujian. Arsitektur model-view yang dipisahkan sejak Bagian 1 memudahkan penambahan struktur baru di masa depan — misalnya AVL tree atau heap — tanpa mengubah renderer secara besar. Batasan utama yang kita akui: label node dibatasi 1 karakter, tree skewed menghasilkan output vertikal yang panjang, dan graph dirender sebagai adjacency list alih-alih diagram 2D.

Kompilasi dan Pengujian #

bash
g++ -std=c++17 -O2 -Wall -Wextra -o test.bin test.cpp
./test.bin

Pastikan keenam test sesuai dengan output di atas. Bila ada perbedaan, periksa kembali penanganan edge case pada renderList dan renderTree.