Skip to main content

Load Testing

Load testing evaluates how an application performs under expected and peak load conditions. It helps identify performance bottlenecks, resource limitations, and scalability issues before they impact users in production.

Official Definition/Standards

Load testing is a type of performance testing that simulates realistic user loads to evaluate application behavior under normal and anticipated peak conditions. It measures response times, throughput, resource utilization, and identifies the breaking point of applications.

Setup and Usage (Tools, Packages, Test Runners)

Primary Load Testing Tools:

  • NBomber: .NET-native load testing framework
  • k6: Modern JavaScript-based load testing tool
  • Apache JMeter: GUI-based, widely-used performance testing tool
  • Artillery: Node.js-based, cloud-native testing framework
  • Azure Load Testing: Cloud-based service for load testing

Essential Packages:

# NBomber for .NET
dotnet add package NBomber
dotnet add package NBomber.Http

# For HTTP client testing
dotnet add package Microsoft.Extensions.Http

Test Infrastructure:

  • Load generators and test agents
  • Monitoring and metrics collection
  • Resource monitoring (CPU, memory, database)
  • Network and latency simulation
  • Distributed testing capabilities

Typical Test Architecture and Patterns

Common Patterns:

  • Ramp-up Testing: Gradually increase load
  • Sustained Load: Maintain constant load over time
  • Spike Testing: Sudden load increases
  • Volume Testing: Large amounts of data
  • Stress Testing: Beyond normal capacity
  • Soak Testing: Extended duration testing

Example Load Test Code

// Package references:
// <PackageReference Include="NBomber" Version="5.0.0" />
// <PackageReference Include="NBomber.Http" Version="5.0.0" />

using NBomber.CSharp;
using NBomber.Http.CSharp;

// NBomber Load Testing Example
public class HotelApiLoadTests
{
public static void RunBookingLoadTest()
{
// HTTP client configuration
using var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://localhost:7001");

// Test scenario for booking API
var bookingScenario = Scenario.Create("booking_api_test", async context =>
{
var bookingRequest = CreateBookingRequest();

var response = await httpClient.PostAsJsonAsync("/api/bookings", bookingRequest);

return response.IsSuccessStatusCode ? Response.Ok() : Response.Fail();
})
.WithLoadSimulations(
Simulation.InjectPerSec(rate: 10, during: TimeSpan.FromMinutes(5)), // Warm-up
Simulation.InjectPerSec(rate: 50, during: TimeSpan.FromMinutes(10)), // Normal load
Simulation.InjectPerSec(rate: 100, during: TimeSpan.FromMinutes(5)) // Peak load
);

// Test scenario for search API
var searchScenario = Scenario.Create("search_api_test", async context =>
{
var searchQuery = "?checkin=2024-12-01&checkout=2024-12-03&guests=2";

var response = await httpClient.GetAsync($"/api/rooms/search{searchQuery}");

return response.IsSuccessStatusCode ? Response.Ok() : Response.Fail();
})
.WithLoadSimulations(
Simulation.InjectPerSec(rate: 20, during: TimeSpan.FromMinutes(15))
);

// Run the load test
NBomberRunner
.RegisterScenarios(bookingScenario, searchScenario)
.WithReportFolder("load_test_reports")
.WithReportFormats(ReportFormat.Txt, ReportFormat.Html, ReportFormat.Csv)
.Run();
}

private static CreateBookingRequest CreateBookingRequest()
{
var random = new Random();
return new CreateBookingRequest
{
GuestId = random.Next(1, 1000),
RoomId = random.Next(101, 200),
CheckInDate = DateTime.Today.AddDays(random.Next(1, 30)),
CheckOutDate = DateTime.Today.AddDays(random.Next(31, 60)),
TotalAmount = random.Next(100, 500)
};
}
}

// Advanced NBomber scenario with data feeding
public class AdvancedLoadTests
{
public static void RunDataDrivenLoadTest()
{
// Data feed for test data
var guestData = Data.Feed(() => new
{
GuestId = Random.Shared.Next(1, 1000),
RoomType = new[] { "Standard", "Deluxe", "Suite" }[Random.Shared.Next(0, 3)],
Duration = Random.Shared.Next(1, 7)
});

var bookingWithDataFeed = Scenario.Create("booking_with_data_feed", async context =>
{
var data = context.Data;

// Search for rooms first
using var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://localhost:7001");

var searchResponse = await httpClient.GetAsync(
$"/api/rooms/search?type={data.RoomType}&duration={data.Duration}");

if (!searchResponse.IsSuccessStatusCode)
return Response.Fail("Search failed");

// Create booking
var bookingRequest = new CreateBookingRequest
{
GuestId = data.GuestId,
RoomId = Random.Shared.Next(101, 200),
CheckInDate = DateTime.Today.AddDays(1),
CheckOutDate = DateTime.Today.AddDays(1 + data.Duration),
TotalAmount = 100 * data.Duration
};

var bookingResponse = await httpClient.PostAsJsonAsync("/api/bookings", bookingRequest);

return bookingResponse.IsSuccessStatusCode ? Response.Ok() : Response.Fail("Booking failed");
})
.WithDataFeed(guestData)
.WithLoadSimulations(
Simulation.InjectPerSec(rate: 10, during: TimeSpan.FromMinutes(2)),
Simulation.InjectPerSec(rate: 25, during: TimeSpan.FromMinutes(5)),
Simulation.InjectPerSec(rate: 50, during: TimeSpan.FromMinutes(3))
);

NBomberRunner
.RegisterScenarios(bookingWithDataFeed)
.WithReportFolder("advanced_load_test_reports")
.Run();
}

// Stress testing to find breaking point
public static void RunStressTest()
{
var stressScenario = Scenario.Create("stress_test", async context =>
{
using var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://localhost:7001");
httpClient.Timeout = TimeSpan.FromSeconds(30);

var response = await httpClient.GetAsync("/api/rooms");

return response.IsSuccessStatusCode ? Response.Ok() : Response.Fail();
})
.WithLoadSimulations(
Simulation.InjectPerSec(rate: 10, during: TimeSpan.FromMinutes(1)), // Baseline
Simulation.InjectPerSec(rate: 50, during: TimeSpan.FromMinutes(2)), // Normal
Simulation.InjectPerSec(rate: 100, during: TimeSpan.FromMinutes(2)), // High
Simulation.InjectPerSec(rate: 200, during: TimeSpan.FromMinutes(2)), // Very high
Simulation.InjectPerSec(rate: 500, during: TimeSpan.FromMinutes(2)), // Extreme
Simulation.InjectPerSec(rate: 1000, during: TimeSpan.FromMinutes(1)) // Breaking point
);

NBomberRunner
.RegisterScenarios(stressScenario)
.WithReportFolder("stress_test_reports")
.Run();
}
}

// Integration with ASP.NET Core TestServer for isolated testing
public class InProcessLoadTests
{
[Test]
public void LoadTest_BookingAPI_WithTestServer()
{
var webAppFactory = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
// Configure in-memory database for testing
services.AddDbContext<HotelDbContext>(options =>
options.UseInMemoryDatabase("LoadTestDb"));
});
});

var httpClient = webAppFactory.CreateClient();

var scenario = Scenario.Create("in_process_test", async context =>
{
var bookingRequest = new CreateBookingRequest
{
GuestId = Random.Shared.Next(1, 100),
RoomId = Random.Shared.Next(101, 110),
CheckInDate = DateTime.Today.AddDays(1),
CheckOutDate = DateTime.Today.AddDays(3),
TotalAmount = 299.99m
};

var response = await httpClient.PostAsJsonAsync("/api/bookings", bookingRequest);

return response.IsSuccessStatusCode ? Response.Ok() : Response.Fail();
})
.WithLoadSimulations(
Simulation.InjectPerSec(rate: 100, during: TimeSpan.FromMinutes(2))
);

NBomberRunner
.RegisterScenarios(scenario)
.WithReportFolder("in_process_load_test_reports")
.Run();

webAppFactory.Dispose();
}
}

// k6 JavaScript alternative example
/*
// k6-load-test.js
import http from 'k6/http';
import { check, group, sleep } from 'k6';

export let options = {
stages: [
{ duration: '2m', target: 10 }, // Ramp up
{ duration: '5m', target: 50 }, // Stay at 50 users
{ duration: '2m', target: 100 }, // Ramp up to 100 users
{ duration: '5m', target: 100 }, // Stay at 100 users
{ duration: '2m', target: 0 }, // Ramp down
],
thresholds: {
http_req_duration: ['p(95)<500'], // 95% of requests should be below 500ms
http_req_failed: ['rate<0.02'], // Error rate should be less than 2%
},
};

const BASE_URL = 'https://localhost:7001';

export default function () {
group('Hotel Booking Flow', function () {
// Search for rooms
let searchResponse = http.get(`${BASE_URL}/api/rooms/search?checkin=2024-12-01&checkout=2024-12-03`);
check(searchResponse, {
'search status is 200': (r) => r.status === 200,
'search response time < 200ms': (r) => r.timings.duration < 200,
});

sleep(1);

// Create booking
let bookingPayload = JSON.stringify({
guestId: Math.floor(Math.random() * 1000) + 1,
roomId: Math.floor(Math.random() * 100) + 101,
checkInDate: '2024-12-01',
checkOutDate: '2024-12-03',
totalAmount: 299.99
});

let bookingResponse = http.post(`${BASE_URL}/api/bookings`, bookingPayload, {
headers: { 'Content-Type': 'application/json' },
});

check(bookingResponse, {
'booking status is 201': (r) => r.status === 201,
'booking response time < 500ms': (r) => r.timings.duration < 500,
});

sleep(1);
});
}

// Run with: k6 run k6-load-test.js
*/

When to Use and When Not to Use

Use Load Testing when:

  • Preparing for production deployment
  • Validating performance requirements
  • Identifying system bottlenecks
  • Planning capacity and scaling
  • Testing under realistic conditions
  • Verifying SLA compliance

Don't use Load Testing when:

  • Testing individual components (use unit tests)
  • Early development stages
  • Testing business logic correctness
  • Limited testing environment resources
  • Functional issues still exist
  • Testing non-performance requirements

Pros and Cons and Alternatives

Pros:

  • Identifies performance bottlenecks
  • Validates scalability limits
  • Provides capacity planning data
  • Tests realistic user scenarios
  • Prevents production performance issues
  • Helps optimize resource usage

Cons:

  • Requires significant infrastructure
  • Time-consuming to setup and run
  • Expensive to maintain
  • May not represent real user behavior
  • Can be complex to analyze results
  • Environment differences affect results

Alternatives:

  • Application Performance Monitoring (APM)
  • Synthetic monitoring
  • Profiling and benchmarking tools
  • Cloud-based load testing services
  • Manual performance testing
  • Chaos engineering practices