Δοκιμή φορτίου ASP.NET Core Applications με k6: Πρακτική εφαρμογή (ελληνικά (Greek))

Δοκιμή φορτίου ASP.NET Core Applications με k6: Πρακτική εφαρμογή

Tuesday, 02 December 2025

//

20 minute read

Τώρα που καταλαβαίνετε τα βασικά στοιχεία του k6 και των δοκιμών απόδοσης, ήρθε η ώρα να θέσετε αυτή τη γνώση σε εφαρμογή. Αυτό το άρθρο σας βαδίζει μέσα από τη συγγραφή πραγματικών δοκιμών, την ενσωμάτωση τους σε αγωγούς CI / CD, και χρησιμοποιώντας εργαλεία προφίλ για τον εντοπισμό και τον καθορισμό των σημείων συμφόρησης των επιδόσεων.

Αυτό είναι Μέρος 2 μιας σειράς δύο μερών για τη δοκιμή φορτίου με k6:

  • Μέρος 1: Εισαγωγή: Εισαγωγή στο k6, εγκατάσταση, τύποι δοκιμών, και γιατί το k6
  • Μέρος 2 (το παρόν άρθρο): Δοκιμές γραφής, ενσωμάτωση CI/CD, προφίλ και παραδείγματα πραγματικού κόσμου

Εάν δεν έχετε διαβάσει το Μέρος 1, ξεκινήστε εκεί για να κατανοήσετε τα βασικά k6, την εγκατάσταση και τους τύπους δοκιμών πριν από την κατάδυση στην εφαρμογή.

Ρύθμιση του δοκιμίου περιβάλλοντός σας

Πριν από την εκτέλεση των δοκιμών k6, ας ρυθμίσουμε το MinimalBlog για τη δοκιμή:

1. Εκτέλεση minimalBlog τοπικά

Χρήση του έργου demo:

cd Mostlylucid.MinimalBlog.Demo
dotnet run

Ή χρησιμοποιώντας Docker:

docker run -d -p 5000:8080 \
  -v $(pwd)/Markdown:/app/Markdown \
  --name minimalblog-test \
  scottgal/minimalblog:latest

Γιατί η λειτουργία απελευθέρωσης έχει σημασία για τη δοκιμή απόδοσης

Για ακριβείς δοκιμές επιδόσεων, πάντα να χτίζεται σε λειτουργία απελευθέρωσης:

dotnet run --configuration Release

Διαφορετικές λειτουργίες αποσφαλμάτωσης εναντίον απελευθέρωσης:

Λειτουργία αποσφαλμάτωσης |--------|------------|--------------| | Βελτιστοποιήσεις Η πλήρης βελτιστοποίηση του JIT ενεργοποιήθηκε. | Είσοδος Μικρή μέθοδος επιθετικής συγκόλλησης | Κωδικός νεκρού Διατηρημένος | Σύμβολα αποσφαλμάτωσης Πλήρες PDB, επιπλέον μεταδεδομένα, minimal ή κανένα | Δέσμες | Debug.Assert() Ενεργός ~ Συγκολλημένος έξω ~ | Έλεγχος ορίων Περισσότεροι έλεγχοι ασφαλείας Βελτιστοποιημένος μακριά όπου ασφαλής | Τυπικά γενικά 2-10x πιο αργή απόδοση

Γιατί η λειτουργία αποσφαλμάτωσης δίνει παραπλανητικά αποτελέσματα:

  1. Καμία βελτιστοποίηση JIT: Ο μεταγλωττιστής διατηρεί τη δομή κώδικα για αποσφαλμάτωση και όχι τη βελτιστοποίηση της ταχύτητας
  2. Πρόσθετα όργανα: Οι κατασκευές αποσφαλμάτωσης περιλαμβάνουν κωδικό για σημεία διακοπής, μεταβλητή επιθεώρηση, και ίχνη στοίβας
  3. Χωρίς inlining: Οι μέθοδοι δεν είναι γραμμένα, προσθέτοντας call overal που δεν θα υπάρχουν στην παραγωγή
  4. Δέσμες (Assertions): Debug.Assert() δηλώσεις εκτελούνται, προσθέτοντας ελέγχους που η παραγωγή δεν θα έχει
  5. Διαφορετικά μοτίβα μνήμης: Κατανομή αποσφαλμάτωσης περιλαμβάνουν padding και παρακολούθηση που επηρεάζει τη συμπεριφορά GC

Πότε να χρησιμοποιήσετε τη λειτουργία αποσφαλμάτωσης για τη δοκιμή:

  • Αποτυχίες αντιμετώπισης προβλημάτων: Όταν οι δοκιμές αποτυγχάνουν και χρειάζεστε λεπτομερή ίχνη στοίβας
  • Ερευνώντας συγκεκριμένα αιτήματα: Προσάρτησε έναν αποσφαλματωτή για να εντοπίσει ένα μόνο προβληματικό αίτημα
  • Επιβεβαίωση καταγραφής: Βεβαιωθείτε ότι η καταγραφή σας αιχμαλωτίζει τις σωστές πληροφορίες υπό φορτίο
  • Αρχική ανάπτυξη: Όταν γράφετε νέα σενάρια δοκιμής και επαληθεύετε ότι χτυπούν τα σωστά τελικά σημεία

Συνιστώμενη ροή εργασίας:

# 1. Develop and debug with Debug mode
dotnet run                           # Debug mode (default)

# 2. Validate functionality works
k6 run --vus 1 --duration 10s smoke-test.js

# 3. Switch to Release for actual performance testing
dotnet run -c Release

# 4. Run full load tests
k6 run load-test.js

Κανόνας του αντίχειρα: Εάν μετράτε την απόδοση, χρησιμοποιήστε το Release. Αν φτιάχνετε σφάλματα, χρησιμοποιήστε το Debug.

2. Προετοιμάστε τα δεδομένα δοκιμής

Δημιουργήστε αρκετές θέσεις markdown για να δοκιμάσετε με:

# Create test posts directory
mkdir -p TestMarkdown

# Create sample posts
for i in {1..10}; do
  cat > TestMarkdown/test-post-$i.md <<EOF
# Test Post $i




This is test post number $i with some **bold** text and *italic* text.

## Sample Content

- Item 1
- Item 2
- Item 3

\`\`\`csharp
public class Test {
    public int Value { get; set; }
}
\`\`\`
EOF
done

3. Δημιουργία καταλόγου Script δοκιμής

mkdir k6-tests
cd k6-tests

Τώρα είμαστε έτοιμοι να γράψουμε τις εξετάσεις μας!

Δοκιμή 1: Δοκιμή καπνού

Ας ξεκινήσουμε με ένα απλό τεστ καπνού για να επιβεβαιώσουμε τη βασική λειτουργικότητα:

Αρχείο: smoke-test.js

import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  vus: 1,
  duration: '30s',
  thresholds: {
    http_req_duration: ['p(95)<500'], // 95% of requests should be below 500ms
    http_req_failed: ['rate<0.01'],    // Less than 1% errors
  },
};

const BASE_URL = __ENV.BASE_URL || 'http://localhost:5000';

export default function() {
  // Test homepage
  let response = http.get(`${BASE_URL}/`);
  check(response, {
    'homepage status is 200': (r) => r.status === 200,
    'homepage has content': (r) => r.body.length > 0,
    'homepage response time OK': (r) => r.timings.duration < 500,
  });

  sleep(1);

  // Test a single post
  response = http.get(`${BASE_URL}/post/test-post-1`);
  check(response, {
    'post status is 200': (r) => r.status === 200,
    'post has content': (r) => r.body.length > 0,
    'post contains title': (r) => r.body.includes('Test Post 1'),
  });

  sleep(1);

  // Test categories page
  response = http.get(`${BASE_URL}/categories`);
  check(response, {
    'categories status is 200': (r) => r.status === 200,
    'categories has content': (r) => r.body.length > 0,
  });

  sleep(1);

  // Test category filter
  response = http.get(`${BASE_URL}/category/Testing`);
  check(response, {
    'category filter status is 200': (r) => r.status === 200,
    'category has posts': (r) => r.body.includes('Test Post'),
  });
}

Εκτέλεση της δοκιμής:

k6 run smoke-test.js

Αναμενόμενη έξοδος:

     ✓ homepage status is 200
     ✓ homepage has content
     ✓ homepage response time OK
     ✓ post status is 200
     ✓ post has content
     ✓ post contains title
     ✓ categories status is 200
     ✓ categories has content
     ✓ category filter status is 200
     ✓ category has posts

     checks.........................: 100.00% ✓ 300  ✗ 0
     data_received..................: 1.2 MB  40 kB/s
     data_sent......................: 15 kB   500 B/s
     http_req_duration..............: avg=45ms min=12ms med=38ms max=156ms p(95)=98ms
     http_reqs......................: 120     4/s

Δοκιμή 2: Δοκιμή επικύρωσης Cache

Η απόδοση του MinimalBlog εξαρτάται σε μεγάλο βαθμό από το caching. Ας επιβεβαιώσουμε ότι λειτουργεί:

Αρχείο: cache-test.js

import http from 'k6/http';
import { check, group } from 'k6';

export const options = {
  vus: 1,
  iterations: 10,
};

const BASE_URL = __ENV.BASE_URL || 'http://localhost:5000';

export default function() {
  group('Memory Cache Test - First Request', () => {
    const start = Date.now();
    const response = http.get(`${BASE_URL}/post/test-post-1`);
    const duration = Date.now() - start;

    check(response, {
      'first request successful': (r) => r.status === 200,
    });

    console.log(`First request: ${duration}ms`);
  });

  group('Memory Cache Test - Cached Request', () => {
    const start = Date.now();
    const response = http.get(`${BASE_URL}/post/test-post-1`);
    const duration = Date.now() - start;

    check(response, {
      'cached request successful': (r) => r.status === 200,
      'cached request is faster': (r) => r.timings.duration < 100,
    });

    console.log(`Cached request: ${duration}ms`);
  });

  group('Output Cache Headers', () => {
    const response = http.get(`${BASE_URL}/post/test-post-1`);

    check(response, {
      'has cache headers': (r) => r.headers['Cache-Control'] !== undefined,
      'output cache working': (r) => {
        const age = r.headers['Age'];
        return age !== undefined && parseInt(age) >= 0;
      },
    });

    console.log(`Cache-Control: ${response.headers['Cache-Control']}`);
    console.log(`Age: ${response.headers['Age']}`);
  });
}

Εκτέλεση της δοκιμής:

k6 run cache-test.js

Αυτή η δοκιμή θα σας δείξει:

  1. Ώρα πρώτης αίτησης (όταν η κρύα μνήμη είναι κρύα)
  2. Επακόλουθοι χρόνοι υποβολής αιτήσεων (τσάκα ζεστό)
  3. Επιβεβαίωση κεφαλίδων Cache

Δοκιμή 3: Δοκιμή φόρτωσης

Τώρα ας δοκιμάσουμε με ρεαλιστικό φορτίο:

Αρχείο: load-test.js

import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate } from 'k6/metrics';

// Custom metrics
const errorRate = new Rate('errors');

export const options = {
  stages: [
    { duration: '2m', target: 10 },   // Ramp up to 10 users
    { duration: '5m', target: 10 },   // Stay at 10 users
    { duration: '2m', target: 0 },    // Ramp down to 0
  ],
  thresholds: {
    http_req_duration: ['p(95)<300'],  // 95% under 300ms
    http_req_failed: ['rate<0.01'],     // Less than 1% errors
    errors: ['rate<0.1'],               // Less than 10% errors
  },
};

const BASE_URL = __ENV.BASE_URL || 'http://localhost:5000';

const scenarios = [
  { weight: 50, path: '/' },                    // 50% homepage
  { weight: 30, path: '/post/test-post-1' },    // 30% specific post
  { weight: 10, path: '/categories' },          // 10% categories
  { weight: 10, path: '/category/Testing' },    // 10% category filter
];

function weightedChoice(scenarios) {
  const total = scenarios.reduce((sum, s) => sum + s.weight, 0);
  let random = Math.random() * total;

  for (const scenario of scenarios) {
    random -= scenario.weight;
    if (random <= 0) return scenario;
  }

  return scenarios[0];
}

export default function() {
  const scenario = weightedChoice(scenarios);
  const response = http.get(`${BASE_URL}${scenario.path}`);

  const success = check(response, {
    'status is 200': (r) => r.status === 200,
    'response time OK': (r) => r.timings.duration < 500,
    'has content': (r) => r.body.length > 0,
  });

  errorRate.add(!success);

  sleep(Math.random() * 2 + 1); // Random sleep 1-3 seconds
}

Εκτέλεση της δοκιμής:

k6 run load-test.js

Ερμηνευτικά αποτελέσματα:

scenarios: (100.00%) 1 scenario, 10 max VUs, 9m30s max duration
  default: 2m00s ramp-up, 5m00s plateau, 2m00s ramp-down

     ✓ status is 200
     ✓ response time OK
     ✓ has content

     checks.........................: 100.00% ✓ 3450  ✗ 0
     data_received..................: 12 MB   22 kB/s
     data_sent......................: 132 kB  244 B/s
     errors.........................: 0.00%   ✓ 0     ✗ 1150
     http_req_duration..............: avg=42ms min=8ms med=35ms max=245ms p(95)=86ms p(99)=156ms
     http_reqs......................: 1150    2.12/s
     iteration_duration.............: avg=2.1s min=1.0s med=2.0s max=3.4s
     vus............................: 10      min=0   max=10

Μετρητές κλειδιών για παρακολούθηση:

  • http_req_ordination p(95): 95ο εκατοστημόριο χρόνος απόκρισης (θα πρέπει να είναι κάτω από το κατώφλι)
  • http_req_ failed: Ποσοστό σφάλματος (θα πρέπει να είναι κοντά 0%)
  • http_reqs: Σύνολο

Δοκιμή 4: Δοκιμή άγχους

Ας βρούμε το σημείο διακοπής του MinimalBlog:

Αρχείο: stress-test.js

import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '2m', target: 10 },    // Normal load
    { duration: '2m', target: 50 },    // Increase to 50
    { duration: '2m', target: 100 },   // Stress at 100
    { duration: '2m', target: 200 },   // High stress at 200
    { duration: '3m', target: 0 },     // Recovery
  ],
  thresholds: {
    http_req_duration: ['p(99)<3000'], // 99% under 3s even under stress
  },
};

const BASE_URL = __ENV.BASE_URL || 'http://localhost:5000';

export default function() {
  const responses = http.batch([
    ['GET', `${BASE_URL}/`],
    ['GET', `${BASE_URL}/post/test-post-1`],
    ['GET', `${BASE_URL}/post/test-post-2`],
    ['GET', `${BASE_URL}/categories`],
  ]);

  responses.forEach((response, index) => {
    check(response, {
      [`request ${index} status is 200`]: (r) => r.status === 200,
    });
  });

  sleep(0.5);
}

export function handleSummary(data) {
  return {
    'stress-test-summary.json': JSON.stringify(data),
    stdout: textSummary(data, { indent: ' ', enableColors: true }),
  };
}

Εκτέλεση της δοκιμής:

k6 run stress-test.js

Πρόσεχε:

  • Πότε αρχίζει ο χρόνος αντίδρασης να εξευτελίζει;
  • Σε ποια κατηγορία VU εμφανίζονται τα λάθη;
  • Αναρρώνει το σύστημα μετά τη μείωση του φορτίου;

Δοκιμή 5: Δοκιμή Spike

Δοκιμάστε ξαφνικά καρφιά κυκλοφορίας (όπως εμφανίζονται σε Reddit ή Hacker News):

Αρχείο: spike-test.js

import http from 'k6/http';
import { check } from 'k6';

export const options = {
  stages: [
    { duration: '1m', target: 10 },    // Normal traffic
    { duration: '30s', target: 200 },  // Sudden spike!
    { duration: '3m', target: 200 },   // Sustained spike
    { duration: '1m', target: 10 },    // Back to normal
    { duration: '1m', target: 0 },     // Ramp down
  ],
  thresholds: {
    http_req_duration: ['p(95)<1000'], // Allow higher latency during spike
    http_req_failed: ['rate<0.05'],     // Allow 5% errors during spike
  },
};

const BASE_URL = __ENV.BASE_URL || 'http://localhost:5000';

export default function() {
  const response = http.get(`${BASE_URL}/`);

  check(response, {
    'status is 200 or 503': (r) => r.status === 200 || r.status === 503,
  });
}

Εκτέλεση της δοκιμής:

k6 run spike-test.js

Η παρούσα δοκιμή επαληθεύει:

  • Το σύστημα χειρίζεται ξαφνικό φορτίο;
  • Υπάρχουν κατάλληλοι μηχανισμοί περιορισμού των ποσοστών;
  • Αναρρώνει με χάρη;

Δοκιμή 6: Δοκιμή απολέπισης

Δοκιμή μακράς λειτουργίας για την ανίχνευση διαρροών μνήμης ή εξάντληση πόρων:

Αρχείο: soak-test.js

import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '2m', target: 20 },    // Ramp up
    { duration: '3h', target: 20 },    // Stay at 20 for 3 hours
    { duration: '2m', target: 0 },     // Ramp down
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'],
    http_req_failed: ['rate<0.01'],
  },
};

const BASE_URL = __ENV.BASE_URL || 'http://localhost:5000';

export default function() {
  const response = http.get(`${BASE_URL}/post/test-post-${Math.floor(Math.random() * 10) + 1}`);

  check(response, {
    'status is 200': (r) => r.status === 200,
    'response time stable': (r) => r.timings.duration < 500,
  });

  sleep(2);
}

Εκτέλεση της δοκιμής:

k6 run soak-test.js

Σημαντικό: Παρακολουθήστε τους πόρους του συστήματος κατά τις δοκιμές εμποτισμού:

# In another terminal
watch -n 5 'dotnet-counters ps | grep -i minimal'

# Or use top/htop
htop -p $(pgrep -f MinimalBlog)

Πρόσεχε:

  • Σταδιακή αύξηση μνήμης (διαρροή μνήμης)
  • Χρήση CPU ανατριχιάζοντας
  • Απάντηση χρονική υποβάθμιση με την πάροδο του χρόνου

Δοκιμή 7: Ρεαλιστικό ταξίδι χρήστη

Προσομοίωση πραγματικής συμπεριφοράς χρήστη:

Αρχείο: user-journey-test.js

import http from 'k6/http';
import { check, group, sleep } from 'k6';
import { htmlReport } from 'https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js';

export const options = {
  vus: 10,
  duration: '5m',
  thresholds: {
    'group_duration{group:::01_homepage}': ['avg<500'],
    'group_duration{group:::02_browse_posts}': ['avg<500'],
    'group_duration{group:::03_categories}': ['avg<500'],
  },
};

const BASE_URL = __ENV.BASE_URL || 'http://localhost:5000';

export default function() {
  // Simulate a real user journey

  group('01_homepage', () => {
    const response = http.get(`${BASE_URL}/`);
    check(response, {
      'homepage loaded': (r) => r.status === 200,
      'homepage has posts': (r) => r.body.includes('Test Post'),
    });
    sleep(Math.random() * 3 + 2); // Read homepage 2-5 seconds
  });

  group('02_browse_posts', () => {
    // User clicks on a post
    let response = http.get(`${BASE_URL}/post/test-post-1`);
    check(response, {
      'post loaded': (r) => r.status === 200,
      'post has content': (r) => r.body.length > 500,
    });
    sleep(Math.random() * 20 + 10); // Read post 10-30 seconds

    // User clicks another post
    response = http.get(`${BASE_URL}/post/test-post-2`);
    check(response, {
      'second post loaded': (r) => r.status === 200,
    });
    sleep(Math.random() * 15 + 5); // Read second post 5-20 seconds
  });

  group('03_categories', () => {
    // User explores categories
    const response = http.get(`${BASE_URL}/categories`);
    check(response, {
      'categories loaded': (r) => r.status === 200,
    });
    sleep(2);

    // User clicks a category
    const categoryResponse = http.get(`${BASE_URL}/category/Testing`);
    check(categoryResponse, {
      'category posts loaded': (r) => r.status === 200,
      'category has posts': (r) => r.body.includes('Test Post'),
    });
    sleep(Math.random() * 10 + 5); // Browse category 5-15 seconds
  });
}

export function handleSummary(data) {
  return {
    'user-journey-report.html': htmlReport(data),
  };
}

Εκτέλεση της δοκιμής:

k6 run user-journey-test.js

Αυτό παράγει μια έκθεση HTML: user-journey-report.html

Advanced: Δοκιμή MetaWeblog API

Εάν έχετε ενεργοποιήσει το MetaWeblog, δοκιμάστε το επίσης:

Αρχείο: metaweblog-test.js

import http from 'k6/http';
import { check } from 'k6';
import encoding from 'k6/encoding';

export const options = {
  vus: 1,
  iterations: 10,
};

const BASE_URL = __ENV.BASE_URL || 'http://localhost:5000';
const USERNAME = __ENV.USERNAME || 'admin';
const PASSWORD = __ENV.PASSWORD || 'changeme';

function createXmlRpcRequest(methodName, params) {
  return `<?xml version="1.0"?>
<methodCall>
  <methodName>${methodName}</methodName>
  <params>
    ${params}
  </params>
</methodCall>`;
}

export default function() {
  // Test getRecentPosts
  const recentPostsXml = createXmlRpcRequest('blogger.getRecentPosts', `
    <param><value><string>0</string></value></param>
    <param><value><string>${USERNAME}</string></value></param>
    <param><value><string>${PASSWORD}</string></value></param>
    <param><value><int>10</int></value></param>
  `);

  const response = http.post(`${BASE_URL}/metaweblog`, recentPostsXml, {
    headers: { 'Content-Type': 'text/xml' },
  });

  check(response, {
    'MetaWeblog API responds': (r) => r.status === 200,
    'MetaWeblog returns XML': (r) => r.body.includes('<?xml'),
    'MetaWeblog has methodResponse': (r) => r.body.includes('methodResponse'),
  });
}

Εκτέλεση της δοκιμής:

k6 run -e USERNAME=admin -e PASSWORD=yourpassword metaweblog-test.js

Διεξαγωγή δοκιμών σε CI/CD

Χρήση του k6 ως Πύλη Ποιότητας

Η πιο ισχυρή χρήση του k6 σε CI/ CD είναι ως ένα Πύλη ποιότητας Εδώ είναι ο τρόπος εφαρμογής ολοκληρωμένων δοκιμών k6 σε δράσεις GitHub.

Δράσεις GitHub: Βασική επαλήθευση

Ας ξεκινήσουμε με μια βασική ροή εργασίας που τρέχει σε κάθε αίτηση έλξης και μπλοκ συγχώνευσης αν οι δοκιμές αποτύχουν.

Δημιουργία .github/workflows/k6-pr-check.yml:

name: k6 Performance Check

on:
  pull_request:
    branches: [ main ]

jobs:
  performance-check:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '9.0.x'

      - name: Setup k6
        run: |
          sudo gpg -k
          sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg \
            --keyserver hkp://keyserver.ubuntu.com:80 \
            --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
          echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | \
            sudo tee /etc/apt/sources.list.d/k6.list
          sudo apt-get update
          sudo apt-get install k6

      - name: Create test data
        run: |
          mkdir -p TestMarkdown
          for i in {1..10}; do
            cat > TestMarkdown/test-post-$i.md <<EOF
          # Test Post $i
          
          

          This is test post $i with **bold** and *italic* text.

          \`\`\`csharp
          public class Test { public int Value { get; set; } }
          \`\`\`
          EOF
          done

      - name: Build MinimalBlog
        run: |
          cd Mostlylucid.MinimalBlog.Demo
          dotnet build --configuration Release

      - name: Start MinimalBlog
        run: |
          cd Mostlylucid.MinimalBlog.Demo
          dotnet run --configuration Release &
          echo $! > app.pid

          # Wait for app to be ready
          timeout 60 bash -c 'until curl -sf http://localhost:5000 > /dev/null; do
            echo "Waiting for app..."
            sleep 2
          done'

          echo "- App is ready!"

      - name: Run Smoke Test
        id: smoke-test
        run: |
          k6 run --out json=smoke-results.json k6-tests/smoke-test.js
          echo "smoke-test-passed=true" >> $GITHUB_OUTPUT

      - name: Run Load Test
        id: load-test
        run: |
          k6 run --out json=load-results.json k6-tests/load-test.js
          echo "load-test-passed=true" >> $GITHUB_OUTPUT

      - name: Run Cache Test
        id: cache-test
        run: |
          k6 run --out json=cache-results.json k6-tests/cache-test.js
          echo "cache-test-passed=true" >> $GITHUB_OUTPUT

      - name: Stop MinimalBlog
        if: always()
        run: |
          if [ -f Mostlylucid.MinimalBlog.Demo/app.pid ]; then
            kill $(cat Mostlylucid.MinimalBlog.Demo/app.pid) || true
          fi

      - name: Upload Test Results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: k6-test-results
          path: |
            *-results.json
            *.html
          retention-days: 30

      - name: Check Test Results
        if: always()
        run: |
          echo "## k6 Performance Test Results" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY

          if [ "${{ steps.smoke-test.outputs.smoke-test-passed }}" == "true" ]; then
            echo "- Smoke Test: PASSED" >> $GITHUB_STEP_SUMMARY
          else
            echo "- Smoke Test: FAILED" >> $GITHUB_STEP_SUMMARY
          fi

          if [ "${{ steps.load-test.outputs.load-test-passed }}" == "true" ]; then
            echo "- Load Test: PASSED" >> $GITHUB_STEP_SUMMARY
          else
            echo "- Load Test: FAILED" >> $GITHUB_STEP_SUMMARY
          fi

          if [ "${{ steps.cache-test.outputs.cache-test-passed }}" == "true" ]; then
            echo "- Cache Test: PASSED" >> $GITHUB_STEP_SUMMARY
          else
            echo "- Cache Test: FAILED" >> $GITHUB_STEP_SUMMARY
          fi

Βασικά χαρακτηριστικά:

  • Λειτουργεί σε όλες τις δημόσιες σχέσεις.
  • Δημιουργεί αυτόματα δεδομένα δοκιμών
  • Περιμένει η εφαρμογή να είναι έτοιμη πριν από τη δοκιμή
  • Εκτελεί πολλαπλούς τύπους δοκιμών
  • Αποτυχία κατασκευής εάν δεν πληρούνται τα κατώτατα όρια
  • Μεταφορτώνει τα αποτελέσματα ως αντικείμενα
  • Εμφάνιση περίληψης στο GitHub UI
sequenceDiagram
    participant PR as Pull Request
    participant GHA as GitHub Actions
    participant App as MinimalBlog
    participant k6 as k6 Tests

    PR->>GHA: Trigger workflow
    GHA->>GHA: Setup .NET & k6
    GHA->>GHA: Create test data
    GHA->>App: Build & Start
    App-->>GHA: App ready
    GHA->>k6: Run smoke test
    k6-->>GHA: Results
    GHA->>k6: Run load test
    k6-->>GHA: Results
    GHA->>k6: Run cache test
    k6-->>GHA: Results
    GHA->>App: Stop
    alt Tests Pass
        GHA->>PR: Mark as success
    else Tests Fail
        GHA->>PR: Block merge
    end

Δράσεις GitHub: Προχωρημένα με σχόλια PR

Δημιουργία .github/workflows/k6-pr-comment.yml για να δημοσιεύσετε τα αποτελέσματα ως σχόλια PR:

name: k6 Performance with PR Comment

on:
  pull_request:
    branches: [ main ]

permissions:
  pull-requests: write
  contents: read

jobs:
  performance-test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '9.0.x'

      - name: Setup k6
        uses: grafana/setup-k6-action@v1

      - name: Create test data
        run: |
          mkdir -p TestMarkdown
          for i in {1..10}; do
            cat > TestMarkdown/test-post-$i.md <<EOF
          # Test Post $i
          
          
          Test content for post $i.
          EOF
          done

      - name: Build and Start App
        run: |
          cd Mostlylucid.MinimalBlog.Demo
          dotnet build -c Release
          dotnet run -c Release > app.log 2>&1 &
          APP_PID=$!
          echo $APP_PID > app.pid

          timeout 60 bash -c 'until curl -sf http://localhost:5000; do sleep 2; done'

      - name: Run k6 Tests
        id: k6-test
        run: |
          # Run tests and capture output
          k6 run --out json=results.json k6-tests/load-test.js > k6-output.txt 2>&1 || true

          # Parse results
          if grep -q "✓" k6-output.txt; then
            echo "tests-passed=true" >> $GITHUB_OUTPUT
          else
            echo "tests-passed=false" >> $GITHUB_OUTPUT
          fi

          # Extract key metrics
          P95=$(grep "http_req_duration" k6-output.txt | grep -oP 'p\(95\)=\K[0-9.]+' || echo "N/A")
          RPS=$(grep "http_reqs" k6-output.txt | grep -oP '\d+\.\d+/s' || echo "N/A")
          ERRORS=$(grep "http_req_failed" k6-output.txt | grep -oP '\d+\.\d+%' || echo "0%")

          echo "p95=${P95}" >> $GITHUB_OUTPUT
          echo "rps=${RPS}" >> $GITHUB_OUTPUT
          echo "errors=${ERRORS}" >> $GITHUB_OUTPUT

      - name: Comment PR
        uses: actions/github-script@v7
        if: always()
        with:
          script: |
            const fs = require('fs');
            const output = fs.readFileSync('k6-output.txt', 'utf8');

            const testsPassed = '${{ steps.k6-test.outputs.tests-passed }}' === 'true';
            const icon = testsPassed ? 'PASS' : 'FAIL';
            const status = testsPassed ? 'PASSED' : 'FAILED';

            const comment = `## ${icon} k6 Performance Test Results

            **Status:** ${status}

            ### Key Metrics
            | Metric | Value |
            |--------|-------|
            | P95 Response Time | ${{ steps.k6-test.outputs.p95 }}ms |
            | Requests/sec | ${{ steps.k6-test.outputs.rps }} |
            | Error Rate | ${{ steps.k6-test.outputs.errors }} |

            ### Thresholds
            - P95 < 300ms
            - Error rate < 1%

            <details>
            <summary>Full k6 Output</summary>


            \`\`\`
            ${output}
            \`\`\`

            </details>

            [View full test results](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
            `;

            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: comment
            });

      - name: Stop App
        if: always()
        run: |
          [ -f Mostlylucid.MinimalBlog.Demo/app.pid ] && \
            kill $(cat Mostlylucid.MinimalBlog.Demo/app.pid) || true

      - name: Upload Artifacts
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: k6-results
          path: |
            results.json
            k6-output.txt
            Mostlylucid.MinimalBlog.Demo/app.log

Αυτή η ροή εργασίας:

  • Δημοσιεύσεις αποτελέσματα απευθείας σε PR
  • Εμφανίζει τις βασικές μετρήσεις σε έναν πίνακα
  • Παρέχει πλήρη έξοδο σε επεκτεινόμενο τμήμα
  • Σύνδεσμοι σε πλήρη δοκιμή
  • Δείκτες οπτικής κατάστασης

Ενέργειες GitHub: Ανίχνευση της Επιδόσεως της Παλινδρόμησης

Δημιουργία .github/workflows/k6-baseline.yml για την ανίχνευση παλινδρόμησης των επιδόσεων:

name: Performance Regression Check

on:
  pull_request:
    branches: [ main ]

jobs:
  regression-check:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout PR code
        uses: actions/checkout@v4

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '9.0.x'

      - name: Setup k6
        uses: grafana/setup-k6-action@v1

      - name: Create test data
        run: |
          mkdir -p TestMarkdown
          for i in {1..20}; do
            echo "# Test Post $i" > TestMarkdown/test-$i.md
            echo "" >> TestMarkdown/test-$i.md
            echo '' >> TestMarkdown/test-$i.md
            echo "Test content $i" >> TestMarkdown/test-$i.md
          done

      - name: Test PR Branch
        run: |
          cd Mostlylucid.MinimalBlog.Demo
          dotnet build -c Release
          dotnet run -c Release &
          APP_PID=$!

          timeout 60 bash -c 'until curl -sf http://localhost:5000; do sleep 2; done'

          # Run test and save results
          k6 run --summary-export=pr-results.json k6-tests/load-test.js

          kill $APP_PID
          sleep 5

      - name: Checkout main branch
        uses: actions/checkout@v4
        with:
          ref: main
          path: baseline

      - name: Test Baseline (main branch)
        run: |
          cd baseline/Mostlylucid.MinimalBlog.Demo
          dotnet build -c Release
          dotnet run -c Release &
          APP_PID=$!

          timeout 60 bash -c 'until curl -sf http://localhost:5000; do sleep 2; done'

          # Run test and save results
          k6 run --summary-export=baseline-results.json ../k6-tests/load-test.js

          kill $APP_PID

      - name: Compare Results
        run: |
          # Extract P95 from both runs
          PR_P95=$(jq '.metrics.http_req_duration.values["p(95)"]' pr-results.json)
          BASE_P95=$(jq '.metrics.http_req_duration.values["p(95)"]' baseline/baseline-results.json)

          # Calculate percentage change
          CHANGE=$(echo "scale=2; (($PR_P95 - $BASE_P95) / $BASE_P95) * 100" | bc)

          echo "## Performance Comparison" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY
          echo "| Branch | P95 Response Time |" >> $GITHUB_STEP_SUMMARY
          echo "|--------|-------------------|" >> $GITHUB_STEP_SUMMARY
          echo "| main (baseline) | ${BASE_P95}ms |" >> $GITHUB_STEP_SUMMARY
          echo "| PR | ${PR_P95}ms |" >> $GITHUB_STEP_SUMMARY
          echo "| Change | ${CHANGE}% |" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY

          # Fail if performance degrades by more than 20%
          if (( $(echo "$CHANGE > 20" | bc -l) )); then
            echo "- Performance degraded by ${CHANGE}%" >> $GITHUB_STEP_SUMMARY
            echo "::error::Performance regression detected: ${CHANGE}% slower than baseline"
            exit 1
          else
            echo "- Performance acceptable" >> $GITHUB_STEP_SUMMARY
          fi

Αυτή η ροή εργασίας:

  • Δοκιμές τόσο PR όσο και κύριος κλάδος
  • Συγκρίνει τις μετρήσεις απόδοσης
  • Αποτυχία εάν οι επιδόσεις υποβαθμίζονται >20%
  • Αποτρέπει τη συγχώνευση αργού κώδικα
graph TD
    A[PR Submitted] --> B[Checkout PR Code]
    B --> C[Test PR Branch]
    C --> D[Checkout main Branch]
    D --> E[Test Baseline]
    E --> F{Calculate<br/>Difference}
    F -->|>20% Slower| G[FAIL Build]
    F -->|<20% Change| H[PASS Build]
    G --> I[Block Merge]
    H --> J[Allow Merge]

    style G stroke:#ff6b6b
    style H stroke:#51cf66

Βέλτιστες πρακτικές για τη δοκιμή CI/CD k6

  1. Ξεκινήστε γρήγορα, Πύλη Νωρίς

    • Εκτελέστε πρώτα τις δοκιμές καπνού (30s)
    • Εκτελέστε πλήρεις δοκιμές μόνο εάν περάσει καπνός
    • Αποτυχία γρήγορης αποθήκευσης λεπτών CI
  2. Χρήση κατάλληλων ορίων

    javascript // Too strict for CI thresholds: { http_req_duration: ['p(95)<100'] }

Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.