it
C# MAUI กับ Load Testing Strategy — วิธีพัฒนา
.NET MAUI Cross-platform Development

.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

// === 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





