it

C# MAUI กับ Load Testing Strategy — วิธีพัฒนา

C# MAUI กับ Load Testing Strategy — วิธีพัฒนา

.NET MAUI Cross-platform Development

C# MAUI กับ Load Testing Strategy — วิธีพัฒนา

.NET MAUI เป็น Framework สร้าง Cross-platform Apps ด้วย C# จาก Codebase เดียว รันบน Android, iOS, macOS และ Windows ใช้ MVVM Pattern แยก UI ออกจาก Business Logic ทดสอบได้ง่าย

เนื้อหาเกี่ยวข้อง — ทำความเข้าใจ PHP Pest Testing DevSecOps Integration

Load Testing เป็นส่วนสำคัญก่อน Release App ทดสอบว่า Backend API รองรับผู้ใช้จำนวนมากได้ วัด Response Time, Throughput หา Bottleneck แก้ไขก่อน Go-live

เนื้อหาเกี่ยวข้อง — อ่านต่อ: Rpa คืออะไร — ทุกสิ่งที่ต้องรู้ในปี 2026

.NET MAUI App Development

// === .NET MAUI App ด้วย MVVM Pattern ===

// dotnet new maui -n MyApp

// cd MyApp



// === Models/User.cs ===

namespace MyApp.Models;



public class User

{

    public int Id { get; set; }

    public string Name { get; set; } = string.Empty;

    public string Email { get; set; } = string.Empty;

    public string Avatar { get; set; } = string.Empty;

    public DateTime CreatedAt { get; set; }

}



public class ApiResponse<T>

{

    public bool Success { get; set; }

    public T? Data { get; set; }

    public string Message { get; set; } = string.Empty;

    public int TotalCount { get; set; }

}



// === Services/ApiService.cs ===

namespace MyApp.Services;



public interface IApiService

{

    Task<ApiResponse<List<User>>> GetUsersAsync(int page = 1, int limit = 20);

    Task<ApiResponse<User>> GetUserAsync(int id);

    Task<ApiResponse<User>> CreateUserAsync(User user);

}



public class ApiService : IApiService

{

    private readonly HttpClient _client;

    private const string BaseUrl = "https://api.example.com/v1";



    public ApiService()

    {

        _client = new HttpClient

        {

            BaseAddress = new Uri(BaseUrl),

            Timeout = TimeSpan.FromSeconds(30),

        };

    }



    public async Task<ApiResponse<List<User>>> GetUsersAsync(int page = 1, int limit = 20)

    {

        var response = await _client.GetAsync($"/users?page={page}&limit={limit}");

        response.EnsureSuccessStatusCode();

        var json = await response.Content.ReadAsStringAsync();

        return System.Text.Json.JsonSerializer.Deserialize<ApiResponse<List<User>>>(json)!;

    }



    public async Task<ApiResponse<User>> GetUserAsync(int id)

    {

        var response = await _client.GetAsync($"/users/{id}");

        response.EnsureSuccessStatusCode();

        var json = await response.Content.ReadAsStringAsync();

        return System.Text.Json.JsonSerializer.Deserialize<ApiResponse<User>>(json)!;

    }



    public async Task<ApiResponse<User>> CreateUserAsync(User user)

    {

        var content = new StringContent(

            System.Text.Json.JsonSerializer.Serialize(user),

            System.Text.Encoding.UTF8, "application/json");

        var response = await _client.PostAsync("/users", content);

        response.EnsureSuccessStatusCode();

        var json = await response.Content.ReadAsStringAsync();

        return System.Text.Json.JsonSerializer.Deserialize<ApiResponse<User>>(json)!;

    }

}



// === ViewModels/UsersViewModel.cs ===

namespace MyApp.ViewModels;



using CommunityToolkit.Mvvm.ComponentModel;

using CommunityToolkit.Mvvm.Input;

using System.Collections.ObjectModel;



public partial class UsersViewModel : ObservableObject

{

    private readonly IApiService _api;



    [ObservableProperty] private bool _isLoading;

    [ObservableProperty] private string _searchText = "";



    public ObservableCollection<User> Users { get; } = new();



    public UsersViewModel(IApiService api)

    {

        _api = api;

    }



    [RelayCommand]

    private async Task LoadUsersAsync()

    {

        if (IsLoading) return;

        IsLoading = true;



        try

        {

            var result = await _api.GetUsersAsync();

            if (result.Success && result.Data != null)

            {

                Users.Clear();

                foreach (var user in result.Data)

                    Users.Add(user);

            }

        }

        catch (Exception ex)

        {

            await Shell.Current.DisplayAlert("Error", ex.Message, "OK");

        }

        finally

        {

            IsLoading = false;

        }

    }



    [RelayCommand]

    private async Task SearchAsync()

    {

        // Filter users by search text

        var result = await _api.GetUsersAsync();

        if (result.Success && result.Data != null)

        {

            var filtered = result.Data

                .Where(u => u.Name.Contains(SearchText, StringComparison.OrdinalIgnoreCase))

                .ToList();

            Users.Clear();

            foreach (var user in filtered)

                Users.Add(user);

        }

    }

}

Load Testing ด้วย k6

C# MAUI กับ Load Testing Strategy — วิธีพัฒนา
// === k6 Load Testing Script ===

// npm install -g k6

// หรือ brew install k6



// load-test.js — ทดสอบ API Backend ของ MAUI App

import http from 'k6/http';

import { check, sleep, group } from 'k6';

import { Rate, Trend, Counter } from 'k6/metrics';



// Custom Metrics

const errorRate = new Rate('error_rate');

const apiLatency = new Trend('api_latency');

const requestCount = new Counter('request_count');



// Test Configuration

export const options = {

  scenarios: {

    // Scenario 1: Smoke Test (5 users, 1 min)

    smoke: {

      executor: 'constant-vus',

      vus: 5,

      duration: '1m',

      startTime: '0s',

    },

    // Scenario 2: Load Test (100 users, ramp up)

    load: {

      executor: 'ramping-vus',

      startVUs: 0,

      stages: [

        { duration: '2m', target: 50 },   // Ramp up to 50

        { duration: '5m', target: 100 },  // Stay at 100

        { duration: '2m', target: 0 },    // Ramp down

      ],

      startTime: '1m',

    },

    // Scenario 3: Stress Test (500 users)

    stress: {

      executor: 'ramping-vus',

      startVUs: 0,

      stages: [

        { duration: '2m', target: 200 },

        { duration: '3m', target: 500 },

        { duration: '2m', target: 0 },

      ],

      startTime: '10m',

    },

  },

  thresholds: {

    http_req_duration: ['p(95)<500', 'p(99)<1000'],

    error_rate: ['rate<0.05'],         // Error rate < 5%

    http_req_failed: ['rate<0.05'],

  },

};



const BASE_URL = __ENV.API_URL || 'https://api.example.com/v1';



export default function () {

  group('GET /users', () => {

    const res = http.get(`/users?page=1&limit=20`, {

      headers: { 'Content-Type': 'application/json' },

    });



    check(res, {

      'status is 200': (r) => r.status === 200,

      'response time < 500ms': (r) => r.timings.duration < 500,

      'has data': (r) => JSON.parse(r.body).data.length > 0,

    });



    errorRate.add(res.status !== 200);

    apiLatency.add(res.timings.duration);

    requestCount.add(1);

  });



  group('GET /users/:id', () => {

    const userId = Math.floor(Math.random() * 100) + 1;

    const res = http.get(`/users/`);



    check(res, {

      'status is 200': (r) => r.status === 200,

      'response time < 300ms': (r) => r.timings.duration < 300,

    });



    errorRate.add(res.status !== 200);

    apiLatency.add(res.timings.duration);

  });



  group('POST /users', () => {

    const payload = JSON.stringify({

      name: `User_`,

      email: `user@test.com`,

    });



    const res = http.post(`/users`, payload, {

      headers: { 'Content-Type': 'application/json' },

    });



    check(res, {

      'status is 201': (r) => r.status === 201,

      'response time < 1000ms': (r) => r.timings.duration < 1000,

    });



    errorRate.add(res.status !== 201);

  });



  sleep(1);

}



// รัน: k6 run load-test.js

// รันกับ Cloud: k6 cloud load-test.js

// รันกับ ENV: k6 run -e API_URL=https://staging.example.com load-test.js

CI/CD Pipeline สำหรับ MAUI + Load Testing

# === GitHub Actions — MAUI Build + Load Test ===

# .github/workflows/maui-loadtest.yml



name: MAUI Build & Load Test

on:

  push:

    branches: [main]

  pull_request:

    branches: [main]



jobs:

  build-android:

    runs-on: windows-latest

    steps:

      - uses: actions/checkout@v4

      - uses: actions/setup-dotnet@v4

        with:

          dotnet-version: 8.0.x



      - name: Install MAUI Workload

        run: dotnet workload install maui-android



      - name: Restore

        run: dotnet restore



      - name: Build Android

        run: |

          dotnet build -c Release -f net8.0-android \

            /p:AndroidKeyStore=true



      - name: Run Unit Tests

        run: dotnet test tests/ --no-build -c Release



  load-test:

    runs-on: ubuntu-latest

    needs: build-android

    steps:

      - uses: actions/checkout@v4



      - name: Start API Server

        run: |

          docker compose -f docker-compose.test.yml up -d

          sleep 10



      - name: Install 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 C5AD17C747E3415A3642D57D77C6C491D6AC1D68

          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: Smoke Test

        run: k6 run --vus 5 --duration 30s tests/load/smoke.js



      - name: Load Test

        run: k6 run tests/load/load-test.js



      - name: Upload Results

        uses: actions/upload-artifact@v4

        with:

          name: load-test-results

          path: load-test-results.json



      - name: Stop API Server

        run: docker compose -f docker-compose.test.yml down

Best Practices

  • MVVM Pattern: ใช้ MVVM แยก UI ออกจาก Logic ทดสอบ ViewModel ด้วย Unit Tests
  • CommunityToolkit.Mvvm: ใช้ Source Generators ลด Boilerplate Code
  • HttpClient Singleton: ใช้ HttpClient เป็น Singleton ผ่าน DI ไม่สร้างใหม่ทุกครั้ง
  • Load Test Scenarios: ทำ Smoke, Load, Stress, Spike Tests ครบทุกแบบ
  • Thresholds: ตั้ง Thresholds ให้ P95 < 500ms, Error Rate < 5%
  • CI/CD Integration: รัน Load Test ใน CI/CD Pipeline ทุก Release

.NET MAUI คืออะไร

Framework จาก Microsoft สร้าง Cross-platform Apps ด้วย C# XAML รัน Android iOS macOS Windows จาก Codebase เดียว ต่อจาก Xamarin.Forms Performance ดีขึ้น Hot Reload .NET 8+

แนะนำเพิ่มเติม — ติดตาม XM Signal

เนื้อหาเกี่ยวข้อง — ดูเพิ่มเติมเรื่อง Kubernetes CRD Progressive Delivery

XM Legend · เทรดเดอร์ & ผู้สอน Forex 13 ปี

ผู้ก่อตั้ง SiamCafe ตั้งแต่ปี 1997 · เทรดเดอร์สาย Forex มากกว่า 13 ปี ได้รับการยกย่องเป็น XM Legend · แบ่งปันความรู้ Forex, ไอที, AI และการเทรด จากประสบการณ์จริงในตลาดจริง