%% fig-alt: Testing pyramid for IoT showing unit tests at the base as the foundation, integration tests in the middle layer, and system and acceptance tests at the top, with test quantity decreasing and test scope increasing as you move up the pyramid.
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#2C3E50', 'primaryTextColor': '#FFFFFF', 'primaryBorderColor': '#16A085', 'lineColor': '#7F8C8D', 'secondaryColor': '#ECF0F1', 'tertiaryColor': '#FFFFFF'}}}%%
flowchart TB
subgraph Pyramid["IoT Testing Pyramid"]
direction TB
E2E["System & Acceptance Tests<br/>End-to-end scenarios"]
INT["Integration Tests<br/>Component interactions"]
UNIT["Unit Tests<br/>Individual functions"]
end
E2E --> INT --> UNIT
style E2E fill:#E74C3C,stroke:#2C3E50,stroke-width:2px,color:#fff
style INT fill:#E67E22,stroke:#2C3E50,stroke-width:2px,color:#fff
style UNIT fill:#27AE60,stroke:#2C3E50,stroke-width:2px,color:#fff
1577 IoT Test Design Generator
Interactive test case builder for IoT systems with scenario templates, coverage analysis, and automation guidance
1577.1 Comprehensive IoT Testing
Testing IoT systems requires a multi-layered approach that covers hardware, software, connectivity, and system integration. This interactive generator helps you create structured test cases tailored to your specific IoT components and requirements.
This test design generator creates comprehensive test cases for IoT systems using the Given-When-Then format. It provides coverage analysis, priority scoring based on risk and frequency, and automation recommendations for each test scenario.
- Select a Test Type (Unit, Integration, System, Acceptance, Performance, Security)
- Choose the IoT Component under test (Sensor, Actuator, Gateway, Cloud API, Mobile App)
- Configure Test Scenario parameters including inputs, expected outputs, and edge cases
- Review generated Test Cases in Given-When-Then format
- Analyze Coverage Matrix for completeness
- Check Priority Scores based on risk and frequency
- Review Automation Suggestions for each test
- Export test cases as Markdown or JSON
Show code
{
// ===========================================================================
// IOT TEST DESIGN GENERATOR
// ===========================================================================
// Features:
// - Test type selector (Unit, Integration, System, Acceptance, Performance, Security)
// - IoT component selector (Sensor, Actuator, Gateway, Cloud API, Mobile App)
// - Test scenario builder with inputs, outputs, edge cases
// - Given-When-Then test case generator
// - Coverage matrix visualization
// - Priority scoring (risk x frequency)
// - Automation suggestions
// - Sample test suites
// - Export as Markdown/JSON
//
// IEEE Color Palette:
// Navy: #2C3E50 (primary)
// Teal: #16A085 (secondary)
// Orange: #E67E22 (highlights)
// Gray: #7F8C8D (neutral)
// LtGray: #ECF0F1 (backgrounds)
// Green: #27AE60 (good)
// Red: #E74C3C (warnings)
// ===========================================================================
// ---------------------------------------------------------------------------
// CONFIGURATION
// ---------------------------------------------------------------------------
const config = {
width: 950,
height: 2400,
colors: {
navy: "#2C3E50",
teal: "#16A085",
orange: "#E67E22",
gray: "#7F8C8D",
lightGray: "#ECF0F1",
white: "#FFFFFF",
green: "#27AE60",
red: "#E74C3C",
yellow: "#F1C40F",
purple: "#9B59B6",
blue: "#3498DB",
pink: "#E91E63"
},
testTypes: [
{ id: "unit", name: "Unit Test", icon: "U", description: "Test individual components in isolation", color: "#3498DB" },
{ id: "integration", name: "Integration Test", icon: "I", description: "Test component interactions", color: "#9B59B6" },
{ id: "system", name: "System Test", icon: "S", description: "End-to-end system behavior", color: "#27AE60" },
{ id: "acceptance", name: "Acceptance Test", icon: "A", description: "Validate business requirements", color: "#E67E22" },
{ id: "performance", name: "Performance Test", icon: "P", description: "Load, stress, and scalability", color: "#E74C3C" },
{ id: "security", name: "Security Test", icon: "X", description: "Vulnerability and penetration testing", color: "#2C3E50" }
],
components: [
{
id: "sensor",
name: "Sensor",
icon: "S",
description: "Temperature, humidity, motion, etc.",
testAreas: ["Accuracy", "Calibration", "Range", "Response time", "Noise immunity", "Power consumption"],
interfaces: ["I2C", "SPI", "Analog", "UART"],
color: "#16A085"
},
{
id: "actuator",
name: "Actuator",
icon: "A",
description: "Motors, relays, valves, displays",
testAreas: ["Response accuracy", "Timing", "Overload protection", "Feedback loop", "State transitions"],
interfaces: ["PWM", "GPIO", "I2C", "Serial"],
color: "#E67E22"
},
{
id: "gateway",
name: "Gateway",
icon: "G",
description: "Protocol translation, edge computing",
testAreas: ["Protocol translation", "Message routing", "Buffering", "Failover", "Edge processing"],
interfaces: ["MQTT", "HTTP", "CoAP", "Modbus", "BLE"],
color: "#9B59B6"
},
{
id: "cloud_api",
name: "Cloud API",
icon: "C",
description: "REST APIs, data storage, analytics",
testAreas: ["Authentication", "Rate limiting", "Data validation", "Error handling", "Latency"],
interfaces: ["REST", "GraphQL", "WebSocket", "gRPC"],
color: "#3498DB"
},
{
id: "mobile_app",
name: "Mobile App",
icon: "M",
description: "iOS/Android control applications",
testAreas: ["UI responsiveness", "Offline mode", "Push notifications", "Device pairing", "State sync"],
interfaces: ["BLE", "Wi-Fi Direct", "REST API", "WebSocket"],
color: "#E74C3C"
}
],
sampleTestSuites: {
temperature_sensor: {
name: "Temperature Sensor Validation",
component: "sensor",
testType: "unit",
tests: [
{
name: "Accuracy within specification",
given: "Sensor placed in calibrated temperature chamber at 25°C",
when: "Reading is taken after 5-minute stabilization",
then: "Reading is within ±0.5°C of reference",
priority: 9,
automatable: true
},
{
name: "Range boundary - minimum",
given: "Sensor at minimum rated temperature (-40°C)",
when: "Reading is requested",
then: "Valid reading returned without error",
priority: 8,
automatable: true
},
{
name: "Range boundary - maximum",
given: "Sensor at maximum rated temperature (85°C)",
when: "Reading is requested",
then: "Valid reading returned without error",
priority: 8,
automatable: true
},
{
name: "Out-of-range indication",
given: "Sensor exposed to temperature beyond rated range",
when: "Reading is requested",
then: "Error code or flag indicates out-of-range condition",
priority: 7,
automatable: true
}
]
},
mqtt_resilience: {
name: "MQTT Connection Resilience",
component: "gateway",
testType: "integration",
tests: [
{
name: "Reconnection after disconnect",
given: "Device connected to MQTT broker",
when: "Network connection is interrupted for 30 seconds",
then: "Device automatically reconnects within 60 seconds",
priority: 10,
automatable: true
},
{
name: "Message queue during offline",
given: "Device loses connectivity with pending messages",
when: "Messages are generated while offline",
then: "Messages are queued and delivered upon reconnection",
priority: 9,
automatable: true
},
{
name: "QoS 1 delivery guarantee",
given: "Message published with QoS 1",
when: "Broker acknowledgment delayed or lost",
then: "Message is retransmitted until acknowledged",
priority: 8,
automatable: true
},
{
name: "Broker failover",
given: "Primary broker becomes unavailable",
when: "Device attempts to publish message",
then: "Device connects to backup broker within timeout",
priority: 9,
automatable: true
}
]
},
gateway_failover: {
name: "Gateway Failover Testing",
component: "gateway",
testType: "system",
tests: [
{
name: "Primary gateway failure detection",
given: "Primary gateway handling device traffic",
when: "Primary gateway stops responding",
then: "Failure detected within 10 seconds via heartbeat",
priority: 10,
automatable: true
},
{
name: "Automatic failover to secondary",
given: "Primary gateway failure detected",
when: "Secondary gateway is available",
then: "Traffic redirected to secondary within 30 seconds",
priority: 10,
automatable: true
},
{
name: "State synchronization",
given: "Failover to secondary gateway completed",
when: "Secondary gateway becomes active",
then: "Device states match pre-failover configuration",
priority: 9,
automatable: false
},
{
name: "Failback to primary",
given: "Primary gateway recovered after failover",
when: "Recovery grace period elapsed",
then: "Traffic fails back to primary gateway",
priority: 7,
automatable: true
}
]
},
api_rate_limiting: {
name: "API Rate Limiting",
component: "cloud_api",
testType: "performance",
tests: [
{
name: "Normal rate within limits",
given: "API endpoint with 100 req/min limit",
when: "50 requests sent in one minute",
then: "All requests processed successfully (HTTP 200)",
priority: 8,
automatable: true
},
{
name: "Rate limit enforcement",
given: "API endpoint with 100 req/min limit",
when: "150 requests sent in one minute",
then: "Requests beyond limit receive HTTP 429",
priority: 9,
automatable: true
},
{
name: "Rate limit headers present",
given: "API request made to rate-limited endpoint",
when: "Response received",
then: "X-RateLimit-Remaining and X-RateLimit-Reset headers present",
priority: 6,
automatable: true
},
{
name: "Rate limit reset",
given: "Rate limit exhausted",
when: "Rate limit window resets",
then: "New requests processed successfully",
priority: 8,
automatable: true
}
]
}
},
automationTools: {
unit: ["pytest", "Unity (C)", "Google Test", "Jest", "JUnit"],
integration: ["Robot Framework", "Postman/Newman", "pytest-integration", "TestContainers"],
system: ["Robot Framework", "Selenium", "Appium", "K6", "Locust"],
acceptance: ["Cucumber", "Behave", "SpecFlow", "Robot Framework"],
performance: ["K6", "Locust", "JMeter", "Gatling", "Artillery"],
security: ["OWASP ZAP", "Burp Suite", "Nmap", "Metasploit", "Nikto"]
}
};
// ---------------------------------------------------------------------------
// STATE MANAGEMENT
// ---------------------------------------------------------------------------
let state = {
selectedTestType: "integration",
selectedComponent: "gateway",
selectedSampleSuite: null,
// Custom scenario
customScenario: {
name: "Custom Test Scenario",
given: "",
when: "",
then: "",
inputs: [],
expectedOutputs: [],
edgeCases: []
},
// Generated test cases
generatedTests: [],
// Risk and frequency for priority
risk: 7,
frequency: 5
};
// ---------------------------------------------------------------------------
// HELPER FUNCTIONS
// ---------------------------------------------------------------------------
function getTestType() {
return config.testTypes.find(t => t.id === state.selectedTestType);
}
function getComponent() {
return config.components.find(c => c.id === state.selectedComponent);
}
function calculatePriority(risk, frequency) {
return Math.round((risk * frequency) / 10);
}
function generateTestId() {
const type = state.selectedTestType.toUpperCase().substring(0, 3);
const comp = state.selectedComponent.toUpperCase().substring(0, 3);
const num = String(state.generatedTests.length + 1).padStart(3, '0');
return `TC-${type}-${comp}-${num}`;
}
function getCoverageData() {
const coverage = [];
config.testTypes.forEach(tt => {
config.components.forEach(comp => {
const matchingTests = state.generatedTests.filter(
t => t.testType === tt.id && t.component === comp.id
);
coverage.push({
testType: tt.id,
testTypeName: tt.name,
component: comp.id,
componentName: comp.name,
count: matchingTests.length,
color: tt.color
});
});
});
return coverage;
}
function getAutomationSuggestions() {
const testType = getTestType();
const component = getComponent();
const tools = config.automationTools[state.selectedTestType] || [];
const suggestions = [];
// General automation tools
suggestions.push({
category: "Recommended Frameworks",
items: tools.slice(0, 3),
description: `Best tools for ${testType.name.toLowerCase()} of IoT ${component.name.toLowerCase()}s`
});
// Component-specific suggestions
if (state.selectedComponent === "sensor") {
suggestions.push({
category: "Hardware-in-Loop",
items: ["HIL Simulator", "Signal Generator", "Calibration Chamber"],
description: "Physical testing equipment for sensor validation"
});
} else if (state.selectedComponent === "gateway") {
suggestions.push({
category: "Protocol Testing",
items: ["MQTT.fx", "Wireshark", "Protocol Analyzer"],
description: "Tools for protocol-level testing and debugging"
});
} else if (state.selectedComponent === "cloud_api") {
suggestions.push({
category: "API Testing",
items: ["Postman", "REST Assured", "Swagger/OpenAPI"],
description: "API documentation and testing tools"
});
} else if (state.selectedComponent === "mobile_app") {
suggestions.push({
category: "Mobile Testing",
items: ["Appium", "XCTest", "Espresso"],
description: "Cross-platform mobile test automation"
});
} else if (state.selectedComponent === "actuator") {
suggestions.push({
category: "Hardware Testing",
items: ["Oscilloscope", "Logic Analyzer", "Motor Test Bench"],
description: "Equipment for actuator response verification"
});
}
// CI/CD integration
suggestions.push({
category: "CI/CD Integration",
items: ["GitHub Actions", "Jenkins", "GitLab CI"],
description: "Continuous integration for automated test execution"
});
return suggestions;
}
function exportAsMarkdown() {
let md = `# IoT Test Suite\n\n`;
md += `**Test Type:** ${getTestType().name}\n`;
md += `**Component:** ${getComponent().name}\n`;
md += `**Generated:** ${new Date().toISOString().split('T')[0]}\n\n`;
md += `---\n\n`;
state.generatedTests.forEach((test, idx) => {
md += `## ${test.id}: ${test.name}\n\n`;
md += `**Priority:** ${test.priority}/10\n`;
md += `**Automatable:** ${test.automatable ? 'Yes' : 'No'}\n\n`;
md += `### Given\n${test.given}\n\n`;
md += `### When\n${test.when}\n\n`;
md += `### Then\n${test.then}\n\n`;
if (test.edgeCases && test.edgeCases.length > 0) {
md += `### Edge Cases\n`;
test.edgeCases.forEach(ec => {
md += `- ${ec}\n`;
});
md += `\n`;
}
md += `---\n\n`;
});
return md;
}
function exportAsJson() {
return JSON.stringify({
metadata: {
testType: state.selectedTestType,
component: state.selectedComponent,
generatedAt: new Date().toISOString(),
totalTests: state.generatedTests.length
},
testCases: state.generatedTests
}, null, 2);
}
// ---------------------------------------------------------------------------
// CREATE CONTAINER
// ---------------------------------------------------------------------------
const container = d3.create("div")
.style("font-family", "system-ui, -apple-system, sans-serif")
.style("max-width", `${config.width}px`)
.style("margin", "0 auto");
// ---------------------------------------------------------------------------
// TEST TYPE SELECTOR
// ---------------------------------------------------------------------------
const testTypePanel = container.append("div")
.style("background", config.colors.lightGray)
.style("border-radius", "12px")
.style("padding", "20px")
.style("margin-bottom", "15px")
.style("border", `2px solid ${config.colors.gray}`);
testTypePanel.append("h3")
.style("margin", "0 0 15px 0")
.style("color", config.colors.navy)
.style("font-size", "16px")
.text("Select Test Type");
const testTypeGrid = testTypePanel.append("div")
.style("display", "grid")
.style("grid-template-columns", "repeat(auto-fit, minmax(140px, 1fr))")
.style("gap", "10px");
config.testTypes.forEach(tt => {
const btn = testTypeGrid.append("button")
.attr("class", `test-type-btn test-type-${tt.id}`)
.style("padding", "15px 10px")
.style("border-radius", "8px")
.style("border", `2px solid ${tt.color}`)
.style("background", state.selectedTestType === tt.id ? tt.color : config.colors.white)
.style("color", state.selectedTestType === tt.id ? config.colors.white : tt.color)
.style("cursor", "pointer")
.style("font-size", "12px")
.style("font-weight", "600")
.style("transition", "all 0.2s");
btn.append("div")
.style("font-size", "20px")
.style("margin-bottom", "5px")
.text(tt.icon);
btn.append("div")
.text(tt.name);
btn.on("click", function() {
state.selectedTestType = tt.id;
updateDisplay();
});
btn.on("mouseenter", function() {
if (state.selectedTestType !== tt.id) {
d3.select(this)
.style("background", tt.color + "22")
.style("transform", "translateY(-2px)");
}
});
btn.on("mouseleave", function() {
if (state.selectedTestType !== tt.id) {
d3.select(this)
.style("background", config.colors.white)
.style("transform", "translateY(0)");
}
});
});
const testTypeDesc = testTypePanel.append("div")
.attr("class", "test-type-description")
.style("margin-top", "15px")
.style("padding", "10px")
.style("background", config.colors.white)
.style("border-radius", "6px")
.style("font-size", "13px")
.style("color", config.colors.gray);
// ---------------------------------------------------------------------------
// COMPONENT SELECTOR
// ---------------------------------------------------------------------------
const componentPanel = container.append("div")
.style("background", config.colors.lightGray)
.style("border-radius", "12px")
.style("padding", "20px")
.style("margin-bottom", "15px")
.style("border", `2px solid ${config.colors.gray}`);
componentPanel.append("h3")
.style("margin", "0 0 15px 0")
.style("color", config.colors.navy)
.style("font-size", "16px")
.text("Select IoT Component");
const componentGrid = componentPanel.append("div")
.style("display", "grid")
.style("grid-template-columns", "repeat(auto-fit, minmax(160px, 1fr))")
.style("gap", "10px");
config.components.forEach(comp => {
const btn = componentGrid.append("button")
.attr("class", `component-btn component-${comp.id}`)
.style("padding", "15px")
.style("border-radius", "8px")
.style("border", `2px solid ${comp.color}`)
.style("background", state.selectedComponent === comp.id ? comp.color : config.colors.white)
.style("color", state.selectedComponent === comp.id ? config.colors.white : comp.color)
.style("cursor", "pointer")
.style("font-size", "12px")
.style("font-weight", "600")
.style("text-align", "left")
.style("transition", "all 0.2s");
const header = btn.append("div")
.style("display", "flex")
.style("align-items", "center")
.style("gap", "8px")
.style("margin-bottom", "5px");
header.append("span")
.style("font-size", "18px")
.style("font-weight", "bold")
.text(comp.icon);
header.append("span")
.text(comp.name);
btn.append("div")
.style("font-size", "11px")
.style("opacity", "0.8")
.text(comp.description);
btn.on("click", function() {
state.selectedComponent = comp.id;
updateDisplay();
});
btn.on("mouseenter", function() {
if (state.selectedComponent !== comp.id) {
d3.select(this)
.style("background", comp.color + "22")
.style("transform", "translateY(-2px)");
}
});
btn.on("mouseleave", function() {
if (state.selectedComponent !== comp.id) {
d3.select(this)
.style("background", config.colors.white)
.style("transform", "translateY(0)");
}
});
});
const componentInfo = componentPanel.append("div")
.attr("class", "component-info")
.style("margin-top", "15px")
.style("padding", "15px")
.style("background", config.colors.white)
.style("border-radius", "8px");
// ---------------------------------------------------------------------------
// SCENARIO BUILDER
// ---------------------------------------------------------------------------
const scenarioPanel = container.append("div")
.style("background", config.colors.white)
.style("border-radius", "12px")
.style("padding", "20px")
.style("margin-bottom", "15px")
.style("border", `2px solid ${config.colors.teal}`);
scenarioPanel.append("h3")
.style("margin", "0 0 15px 0")
.style("color", config.colors.navy)
.style("font-size", "16px")
.text("Test Scenario Builder");
// Scenario name
const nameGroup = scenarioPanel.append("div")
.style("margin-bottom", "15px");
nameGroup.append("label")
.style("display", "block")
.style("font-weight", "600")
.style("color", config.colors.navy)
.style("margin-bottom", "5px")
.style("font-size", "13px")
.text("Test Name");
const nameInput = nameGroup.append("input")
.attr("type", "text")
.attr("placeholder", "e.g., Sensor calibration verification")
.style("width", "100%")
.style("padding", "10px")
.style("border-radius", "6px")
.style("border", `1px solid ${config.colors.gray}`)
.style("font-size", "13px")
.style("box-sizing", "border-box");
nameInput.on("input", function() {
state.customScenario.name = this.value;
});
// Given-When-Then inputs
const gwtGrid = scenarioPanel.append("div")
.style("display", "grid")
.style("grid-template-columns", "1fr 1fr 1fr")
.style("gap", "15px")
.style("margin-bottom", "15px");
// Given
const givenGroup = gwtGrid.append("div");
givenGroup.append("label")
.style("display", "block")
.style("font-weight", "600")
.style("color", config.colors.green)
.style("margin-bottom", "5px")
.style("font-size", "13px")
.text("GIVEN (Preconditions)");
const givenInput = givenGroup.append("textarea")
.attr("rows", 3)
.attr("placeholder", "Initial state or context...")
.style("width", "100%")
.style("padding", "10px")
.style("border-radius", "6px")
.style("border", `2px solid ${config.colors.green}`)
.style("font-size", "12px")
.style("resize", "vertical")
.style("box-sizing", "border-box");
givenInput.on("input", function() {
state.customScenario.given = this.value;
});
// When
const whenGroup = gwtGrid.append("div");
whenGroup.append("label")
.style("display", "block")
.style("font-weight", "600")
.style("color", config.colors.orange)
.style("margin-bottom", "5px")
.style("font-size", "13px")
.text("WHEN (Action/Trigger)");
const whenInput = whenGroup.append("textarea")
.attr("rows", 3)
.attr("placeholder", "Action being tested...")
.style("width", "100%")
.style("padding", "10px")
.style("border-radius", "6px")
.style("border", `2px solid ${config.colors.orange}`)
.style("font-size", "12px")
.style("resize", "vertical")
.style("box-sizing", "border-box");
whenInput.on("input", function() {
state.customScenario.when = this.value;
});
// Then
const thenGroup = gwtGrid.append("div");
thenGroup.append("label")
.style("display", "block")
.style("font-weight", "600")
.style("color", config.colors.blue)
.style("margin-bottom", "5px")
.style("font-size", "13px")
.text("THEN (Expected Result)");
const thenInput = thenGroup.append("textarea")
.attr("rows", 3)
.attr("placeholder", "Expected outcome...")
.style("width", "100%")
.style("padding", "10px")
.style("border-radius", "6px")
.style("border", `2px solid ${config.colors.blue}`)
.style("font-size", "12px")
.style("resize", "vertical")
.style("box-sizing", "border-box");
thenInput.on("input", function() {
state.customScenario.then = this.value;
});
// Priority scoring
const priorityRow = scenarioPanel.append("div")
.style("display", "grid")
.style("grid-template-columns", "1fr 1fr 1fr")
.style("gap", "20px")
.style("margin-bottom", "15px")
.style("padding", "15px")
.style("background", config.colors.lightGray)
.style("border-radius", "8px");
// Risk slider
const riskGroup = priorityRow.append("div");
const riskHeader = riskGroup.append("div")
.style("display", "flex")
.style("justify-content", "space-between")
.style("margin-bottom", "8px");
riskHeader.append("label")
.style("font-weight", "600")
.style("color", config.colors.navy)
.style("font-size", "13px")
.text("Risk Level");
const riskValue = riskHeader.append("span")
.style("font-weight", "bold")
.style("color", config.colors.red)
.text(state.risk);
const riskSlider = riskGroup.append("input")
.attr("type", "range")
.attr("min", 1)
.attr("max", 10)
.attr("value", state.risk)
.style("width", "100%")
.style("accent-color", config.colors.red);
riskSlider.on("input", function() {
state.risk = +this.value;
riskValue.text(state.risk);
updatePriorityDisplay();
});
// Frequency slider
const freqGroup = priorityRow.append("div");
const freqHeader = freqGroup.append("div")
.style("display", "flex")
.style("justify-content", "space-between")
.style("margin-bottom", "8px");
freqHeader.append("label")
.style("font-weight", "600")
.style("color", config.colors.navy)
.style("font-size", "13px")
.text("Usage Frequency");
const freqValue = freqHeader.append("span")
.style("font-weight", "bold")
.style("color", config.colors.orange)
.text(state.frequency);
const freqSlider = freqGroup.append("input")
.attr("type", "range")
.attr("min", 1)
.attr("max", 10)
.attr("value", state.frequency)
.style("width", "100%")
.style("accent-color", config.colors.orange);
freqSlider.on("input", function() {
state.frequency = +this.value;
freqValue.text(state.frequency);
updatePriorityDisplay();
});
// Calculated priority
const priorityDisplay = priorityRow.append("div")
.style("text-align", "center");
priorityDisplay.append("div")
.style("font-size", "11px")
.style("color", config.colors.gray)
.style("margin-bottom", "5px")
.text("Priority Score");
const priorityScore = priorityDisplay.append("div")
.attr("class", "priority-score")
.style("font-size", "32px")
.style("font-weight", "bold")
.style("color", config.colors.teal)
.text(calculatePriority(state.risk, state.frequency));
priorityDisplay.append("div")
.style("font-size", "10px")
.style("color", config.colors.gray)
.text("(Risk x Frequency / 10)");
function updatePriorityDisplay() {
const priority = calculatePriority(state.risk, state.frequency);
priorityScore.text(priority);
let color = config.colors.green;
if (priority >= 7) color = config.colors.red;
else if (priority >= 4) color = config.colors.orange;
priorityScore.style("color", color);
}
// Edge cases
const edgeCaseGroup = scenarioPanel.append("div")
.style("margin-bottom", "15px");
edgeCaseGroup.append("label")
.style("display", "block")
.style("font-weight", "600")
.style("color", config.colors.navy)
.style("margin-bottom", "5px")
.style("font-size", "13px")
.text("Edge Cases (one per line)");
const edgeCaseInput = edgeCaseGroup.append("textarea")
.attr("rows", 3)
.attr("placeholder", "Null input values\nNetwork timeout\nMaximum payload size...")
.style("width", "100%")
.style("padding", "10px")
.style("border-radius", "6px")
.style("border", `1px solid ${config.colors.gray}`)
.style("font-size", "12px")
.style("resize", "vertical")
.style("box-sizing", "border-box");
edgeCaseInput.on("input", function() {
state.customScenario.edgeCases = this.value.split('\n').filter(e => e.trim());
});
// Add test button
const addTestBtn = scenarioPanel.append("button")
.style("padding", "12px 24px")
.style("background", config.colors.teal)
.style("color", config.colors.white)
.style("border", "none")
.style("border-radius", "8px")
.style("font-size", "14px")
.style("font-weight", "600")
.style("cursor", "pointer")
.text("Add Test Case");
addTestBtn.on("click", function() {
if (!state.customScenario.name || !state.customScenario.given ||
!state.customScenario.when || !state.customScenario.then) {
alert("Please fill in all required fields (Name, Given, When, Then)");
return;
}
const newTest = {
id: generateTestId(),
name: state.customScenario.name,
testType: state.selectedTestType,
component: state.selectedComponent,
given: state.customScenario.given,
when: state.customScenario.when,
then: state.customScenario.then,
edgeCases: [...state.customScenario.edgeCases],
priority: calculatePriority(state.risk, state.frequency),
risk: state.risk,
frequency: state.frequency,
automatable: true,
createdAt: new Date().toISOString()
};
state.generatedTests.push(newTest);
updateTestCaseList();
updateCoverageMatrix();
// Clear inputs
nameInput.property("value", "");
givenInput.property("value", "");
whenInput.property("value", "");
thenInput.property("value", "");
edgeCaseInput.property("value", "");
state.customScenario = {
name: "",
given: "",
when: "",
then: "",
inputs: [],
expectedOutputs: [],
edgeCases: []
};
});
// ---------------------------------------------------------------------------
// SAMPLE TEST SUITES
// ---------------------------------------------------------------------------
const samplePanel = container.append("div")
.style("background", config.colors.lightGray)
.style("border-radius", "12px")
.style("padding", "20px")
.style("margin-bottom", "15px")
.style("border", `2px solid ${config.colors.purple}`);
samplePanel.append("h3")
.style("margin", "0 0 15px 0")
.style("color", config.colors.navy)
.style("font-size", "16px")
.text("Sample Test Suites");
const sampleGrid = samplePanel.append("div")
.style("display", "grid")
.style("grid-template-columns", "repeat(auto-fit, minmax(200px, 1fr))")
.style("gap", "10px");
Object.entries(config.sampleTestSuites).forEach(([key, suite]) => {
const card = sampleGrid.append("div")
.style("background", config.colors.white)
.style("padding", "15px")
.style("border-radius", "8px")
.style("border", `2px solid ${config.colors.purple}`)
.style("cursor", "pointer")
.style("transition", "all 0.2s");
card.append("div")
.style("font-weight", "600")
.style("color", config.colors.navy)
.style("font-size", "13px")
.style("margin-bottom", "5px")
.text(suite.name);
card.append("div")
.style("font-size", "11px")
.style("color", config.colors.gray)
.text(`${suite.tests.length} test cases`);
const badges = card.append("div")
.style("margin-top", "8px")
.style("display", "flex")
.style("gap", "5px");
const comp = config.components.find(c => c.id === suite.component);
const tt = config.testTypes.find(t => t.id === suite.testType);
badges.append("span")
.style("padding", "2px 6px")
.style("background", comp.color)
.style("color", config.colors.white)
.style("border-radius", "4px")
.style("font-size", "10px")
.text(comp.name);
badges.append("span")
.style("padding", "2px 6px")
.style("background", tt.color)
.style("color", config.colors.white)
.style("border-radius", "4px")
.style("font-size", "10px")
.text(tt.name);
card.on("click", function() {
// Add all tests from suite
suite.tests.forEach((test, idx) => {
const newTest = {
id: generateTestId(),
name: test.name,
testType: suite.testType,
component: suite.component,
given: test.given,
when: test.when,
then: test.then,
edgeCases: [],
priority: test.priority,
automatable: test.automatable,
createdAt: new Date().toISOString(),
fromSuite: suite.name
};
state.generatedTests.push(newTest);
});
updateTestCaseList();
updateCoverageMatrix();
});
card.on("mouseenter", function() {
d3.select(this)
.style("transform", "translateY(-2px)")
.style("box-shadow", "0 4px 8px rgba(0,0,0,0.1)");
});
card.on("mouseleave", function() {
d3.select(this)
.style("transform", "translateY(0)")
.style("box-shadow", "none");
});
});
// ---------------------------------------------------------------------------
// GENERATED TEST CASES
// ---------------------------------------------------------------------------
const testCasePanel = container.append("div")
.style("background", config.colors.white)
.style("border-radius", "12px")
.style("padding", "20px")
.style("margin-bottom", "15px")
.style("border", `2px solid ${config.colors.navy}`);
testCasePanel.append("h3")
.style("margin", "0 0 15px 0")
.style("color", config.colors.navy)
.style("font-size", "16px")
.text("Generated Test Cases");
const testCaseStats = testCasePanel.append("div")
.attr("class", "test-case-stats")
.style("display", "flex")
.style("gap", "15px")
.style("margin-bottom", "15px");
const testCaseList = testCasePanel.append("div")
.attr("class", "test-case-list")
.style("max-height", "400px")
.style("overflow-y", "auto");
function updateTestCaseList() {
testCaseList.selectAll("*").remove();
testCaseStats.selectAll("*").remove();
// Stats
testCaseStats.append("div")
.style("padding", "8px 16px")
.style("background", config.colors.navy)
.style("color", config.colors.white)
.style("border-radius", "6px")
.style("font-size", "13px")
.text(`Total: ${state.generatedTests.length}`);
const highPriority = state.generatedTests.filter(t => t.priority >= 7).length;
testCaseStats.append("div")
.style("padding", "8px 16px")
.style("background", config.colors.red)
.style("color", config.colors.white)
.style("border-radius", "6px")
.style("font-size", "13px")
.text(`High Priority: ${highPriority}`);
const automatable = state.generatedTests.filter(t => t.automatable).length;
testCaseStats.append("div")
.style("padding", "8px 16px")
.style("background", config.colors.green)
.style("color", config.colors.white)
.style("border-radius", "6px")
.style("font-size", "13px")
.text(`Automatable: ${automatable}`);
if (state.generatedTests.length === 0) {
testCaseList.append("div")
.style("padding", "40px")
.style("text-align", "center")
.style("color", config.colors.gray)
.style("font-size", "14px")
.text("No test cases yet. Use the scenario builder or load a sample suite.");
return;
}
state.generatedTests.forEach((test, idx) => {
const card = testCaseList.append("div")
.style("background", config.colors.lightGray)
.style("padding", "15px")
.style("border-radius", "8px")
.style("margin-bottom", "10px")
.style("border-left", `4px solid ${test.priority >= 7 ? config.colors.red : test.priority >= 4 ? config.colors.orange : config.colors.green}`);
// Header
const header = card.append("div")
.style("display", "flex")
.style("justify-content", "space-between")
.style("align-items", "center")
.style("margin-bottom", "10px");
const titleSection = header.append("div");
titleSection.append("span")
.style("font-weight", "bold")
.style("color", config.colors.navy)
.style("font-size", "13px")
.text(test.id);
titleSection.append("span")
.style("margin-left", "10px")
.style("color", config.colors.gray)
.style("font-size", "13px")
.text(test.name);
const badges = header.append("div")
.style("display", "flex")
.style("gap", "5px");
badges.append("span")
.style("padding", "2px 8px")
.style("background", test.priority >= 7 ? config.colors.red : test.priority >= 4 ? config.colors.orange : config.colors.green)
.style("color", config.colors.white)
.style("border-radius", "4px")
.style("font-size", "10px")
.text(`P${test.priority}`);
if (test.automatable) {
badges.append("span")
.style("padding", "2px 8px")
.style("background", config.colors.teal)
.style("color", config.colors.white)
.style("border-radius", "4px")
.style("font-size", "10px")
.text("Auto");
}
// Delete button
const deleteBtn = badges.append("button")
.style("padding", "2px 8px")
.style("background", config.colors.gray)
.style("color", config.colors.white)
.style("border", "none")
.style("border-radius", "4px")
.style("font-size", "10px")
.style("cursor", "pointer")
.text("X");
deleteBtn.on("click", function(e) {
e.stopPropagation();
state.generatedTests.splice(idx, 1);
updateTestCaseList();
updateCoverageMatrix();
});
// GWT content
const gwtContent = card.append("div")
.style("font-size", "12px");
const createGwtRow = (label, text, color) => {
const row = gwtContent.append("div")
.style("margin-bottom", "5px");
row.append("span")
.style("font-weight", "bold")
.style("color", color)
.style("margin-right", "5px")
.text(label + ":");
row.append("span")
.style("color", config.colors.navy)
.text(text);
};
createGwtRow("Given", test.given, config.colors.green);
createGwtRow("When", test.when, config.colors.orange);
createGwtRow("Then", test.then, config.colors.blue);
if (test.edgeCases && test.edgeCases.length > 0) {
const edgeRow = gwtContent.append("div")
.style("margin-top", "8px");
edgeRow.append("span")
.style("font-weight", "bold")
.style("color", config.colors.purple)
.style("margin-right", "5px")
.text("Edge Cases:");
test.edgeCases.forEach(ec => {
edgeRow.append("span")
.style("display", "inline-block")
.style("margin", "2px 4px")
.style("padding", "2px 6px")
.style("background", config.colors.purple + "22")
.style("border-radius", "4px")
.style("font-size", "11px")
.text(ec);
});
}
});
}
// ---------------------------------------------------------------------------
// COVERAGE MATRIX
// ---------------------------------------------------------------------------
const coveragePanel = container.append("div")
.style("background", config.colors.lightGray)
.style("border-radius", "12px")
.style("padding", "20px")
.style("margin-bottom", "15px")
.style("border", `2px solid ${config.colors.gray}`);
coveragePanel.append("h3")
.style("margin", "0 0 15px 0")
.style("color", config.colors.navy)
.style("font-size", "16px")
.text("Test Coverage Matrix");
const coverageSvg = coveragePanel.append("svg")
.attr("viewBox", "0 0 700 200")
.attr("width", "100%");
function updateCoverageMatrix() {
coverageSvg.selectAll("*").remove();
const cellWidth = 90;
const cellHeight = 25;
const startX = 120;
const startY = 30;
// Header row - test types
config.testTypes.forEach((tt, i) => {
coverageSvg.append("text")
.attr("x", startX + i * cellWidth + cellWidth/2)
.attr("y", startY - 10)
.attr("text-anchor", "middle")
.attr("font-size", "10px")
.attr("fill", config.colors.navy)
.attr("font-weight", "bold")
.text(tt.name.split(" ")[0]);
});
// Component rows
config.components.forEach((comp, rowIdx) => {
// Row label
coverageSvg.append("text")
.attr("x", startX - 10)
.attr("y", startY + rowIdx * cellHeight + cellHeight/2 + 4)
.attr("text-anchor", "end")
.attr("font-size", "11px")
.attr("fill", config.colors.navy)
.text(comp.name);
// Cells
config.testTypes.forEach((tt, colIdx) => {
const count = state.generatedTests.filter(
t => t.testType === tt.id && t.component === comp.id
).length;
const cell = coverageSvg.append("g")
.attr("transform", `translate(${startX + colIdx * cellWidth}, ${startY + rowIdx * cellHeight})`);
cell.append("rect")
.attr("width", cellWidth - 4)
.attr("height", cellHeight - 4)
.attr("rx", 4)
.attr("fill", count > 0 ? tt.color : config.colors.white)
.attr("stroke", tt.color)
.attr("stroke-width", 1)
.attr("opacity", count > 0 ? Math.min(0.3 + count * 0.2, 1) : 1);
cell.append("text")
.attr("x", (cellWidth - 4) / 2)
.attr("y", (cellHeight - 4) / 2 + 4)
.attr("text-anchor", "middle")
.attr("font-size", "11px")
.attr("fill", count > 0 ? config.colors.white : config.colors.gray)
.attr("font-weight", count > 0 ? "bold" : "normal")
.text(count || "-");
});
});
}
// ---------------------------------------------------------------------------
// AUTOMATION SUGGESTIONS
// ---------------------------------------------------------------------------
const automationPanel = container.append("div")
.style("background", config.colors.white)
.style("border-radius", "12px")
.style("padding", "20px")
.style("margin-bottom", "15px")
.style("border", `2px solid ${config.colors.green}`);
automationPanel.append("h3")
.style("margin", "0 0 15px 0")
.style("color", config.colors.navy)
.style("font-size", "16px")
.text("Automation Suggestions");
const automationContent = automationPanel.append("div")
.attr("class", "automation-content")
.style("display", "grid")
.style("grid-template-columns", "repeat(auto-fit, minmax(250px, 1fr))")
.style("gap", "15px");
function updateAutomationSuggestions() {
automationContent.selectAll("*").remove();
const suggestions = getAutomationSuggestions();
suggestions.forEach(sugg => {
const card = automationContent.append("div")
.style("background", config.colors.lightGray)
.style("padding", "15px")
.style("border-radius", "8px");
card.append("div")
.style("font-weight", "bold")
.style("color", config.colors.navy)
.style("margin-bottom", "8px")
.style("font-size", "13px")
.text(sugg.category);
const toolList = card.append("div")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("gap", "5px")
.style("margin-bottom", "8px");
sugg.items.forEach(item => {
toolList.append("span")
.style("padding", "4px 8px")
.style("background", config.colors.teal)
.style("color", config.colors.white)
.style("border-radius", "4px")
.style("font-size", "11px")
.text(item);
});
card.append("div")
.style("font-size", "11px")
.style("color", config.colors.gray)
.text(sugg.description);
});
}
// ---------------------------------------------------------------------------
// EXPORT PANEL
// ---------------------------------------------------------------------------
const exportPanel = container.append("div")
.style("background", config.colors.navy)
.style("border-radius", "12px")
.style("padding", "20px")
.style("margin-bottom", "15px")
.style("color", config.colors.white);
exportPanel.append("h3")
.style("margin", "0 0 15px 0")
.style("font-size", "16px")
.text("Export Test Cases");
const exportBtnRow = exportPanel.append("div")
.style("display", "flex")
.style("gap", "15px")
.style("margin-bottom", "15px");
const mdBtn = exportBtnRow.append("button")
.style("padding", "12px 24px")
.style("background", config.colors.teal)
.style("color", config.colors.white)
.style("border", "none")
.style("border-radius", "8px")
.style("font-size", "14px")
.style("cursor", "pointer")
.text("Export as Markdown");
const jsonBtn = exportBtnRow.append("button")
.style("padding", "12px 24px")
.style("background", config.colors.orange)
.style("color", config.colors.white)
.style("border", "none")
.style("border-radius", "8px")
.style("font-size", "14px")
.style("cursor", "pointer")
.text("Export as JSON");
const clearBtn = exportBtnRow.append("button")
.style("padding", "12px 24px")
.style("background", config.colors.red)
.style("color", config.colors.white)
.style("border", "none")
.style("border-radius", "8px")
.style("font-size", "14px")
.style("cursor", "pointer")
.text("Clear All");
const exportOutput = exportPanel.append("pre")
.attr("class", "export-output")
.style("background", "rgba(255,255,255,0.1)")
.style("padding", "15px")
.style("border-radius", "8px")
.style("font-size", "11px")
.style("max-height", "300px")
.style("overflow", "auto")
.style("display", "none")
.style("white-space", "pre-wrap")
.style("word-wrap", "break-word");
mdBtn.on("click", function() {
if (state.generatedTests.length === 0) {
alert("No test cases to export");
return;
}
const md = exportAsMarkdown();
exportOutput.style("display", "block").text(md);
});
jsonBtn.on("click", function() {
if (state.generatedTests.length === 0) {
alert("No test cases to export");
return;
}
const json = exportAsJson();
exportOutput.style("display", "block").text(json);
});
clearBtn.on("click", function() {
if (confirm("Clear all generated test cases?")) {
state.generatedTests = [];
updateTestCaseList();
updateCoverageMatrix();
exportOutput.style("display", "none");
}
});
// ---------------------------------------------------------------------------
// UPDATE DISPLAY FUNCTION
// ---------------------------------------------------------------------------
function updateDisplay() {
// Update test type buttons
config.testTypes.forEach(tt => {
container.select(`.test-type-${tt.id}`)
.style("background", state.selectedTestType === tt.id ? tt.color : config.colors.white)
.style("color", state.selectedTestType === tt.id ? config.colors.white : tt.color);
});
// Update test type description
const selectedType = getTestType();
testTypeDesc.text(selectedType.description);
// Update component buttons
config.components.forEach(comp => {
container.select(`.component-${comp.id}`)
.style("background", state.selectedComponent === comp.id ? comp.color : config.colors.white)
.style("color", state.selectedComponent === comp.id ? config.colors.white : comp.color);
});
// Update component info
const selectedComp = getComponent();
componentInfo.selectAll("*").remove();
componentInfo.append("div")
.style("font-weight", "bold")
.style("color", config.colors.navy)
.style("margin-bottom", "10px")
.text(`Test Areas for ${selectedComp.name}`);
const testAreaGrid = componentInfo.append("div")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("gap", "8px")
.style("margin-bottom", "10px");
selectedComp.testAreas.forEach(area => {
testAreaGrid.append("span")
.style("padding", "4px 10px")
.style("background", selectedComp.color + "22")
.style("color", selectedComp.color)
.style("border-radius", "4px")
.style("font-size", "12px")
.text(area);
});
componentInfo.append("div")
.style("font-size", "12px")
.style("color", config.colors.gray)
.html(`<strong>Common Interfaces:</strong> ${selectedComp.interfaces.join(", ")}`);
// Update automation suggestions
updateAutomationSuggestions();
}
// ---------------------------------------------------------------------------
// INITIALIZE
// ---------------------------------------------------------------------------
updateDisplay();
updateTestCaseList();
updateCoverageMatrix();
return container.node();
}1577.2 Understanding IoT Testing
1577.2.1 The Testing Pyramid for IoT
IoT systems require a comprehensive testing strategy that covers multiple layers:
%% fig-alt: Decision flowchart for selecting appropriate test type based on what aspect of the IoT system is being tested, guiding users from initial question through component scope, interaction type, and performance requirements to the recommended test type.
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#2C3E50', 'primaryTextColor': '#FFFFFF', 'primaryBorderColor': '#16A085', 'lineColor': '#7F8C8D', 'secondaryColor': '#ECF0F1', 'tertiaryColor': '#FFFFFF'}}}%%
flowchart TD
START[What are you testing?] --> Q1{Single component<br/>in isolation?}
Q1 -->|Yes| UNIT[Unit Test]
Q1 -->|No| Q2{Multiple components<br/>working together?}
Q2 -->|Yes| INT[Integration Test]
Q2 -->|No| Q3{Complete system<br/>end-to-end?}
Q3 -->|Yes| SYS[System Test]
Q3 -->|No| Q4{Business<br/>requirements?}
Q4 -->|Yes| ACC[Acceptance Test]
Q4 -->|No| Q5{Load, stress,<br/>or scalability?}
Q5 -->|Yes| PERF[Performance Test]
Q5 -->|No| SEC[Security Test]
UNIT --> R1[pytest, Unity, Google Test]
INT --> R2[Robot Framework, Postman]
SYS --> R3[End-to-end automation]
ACC --> R4[Cucumber, BDD frameworks]
PERF --> R5[K6, Locust, JMeter]
SEC --> R6[OWASP ZAP, Burp Suite]
style START fill:#2C3E50,stroke:#16A085,stroke-width:2px,color:#fff
style UNIT fill:#3498DB,stroke:#2C3E50,stroke-width:2px,color:#fff
style INT fill:#9B59B6,stroke:#2C3E50,stroke-width:2px,color:#fff
style SYS fill:#27AE60,stroke:#2C3E50,stroke-width:2px,color:#fff
style ACC fill:#E67E22,stroke:#2C3E50,stroke-width:2px,color:#fff
style PERF fill:#E74C3C,stroke:#2C3E50,stroke-width:2px,color:#fff
style SEC fill:#2C3E50,stroke:#16A085,stroke-width:2px,color:#fff
Use this decision tree to determine which test type is most appropriate for your testing scenario. Start with what you’re testing and follow the path to find the recommended approach and tools.
1577.2.2 Given-When-Then Format
The Given-When-Then format (also known as Gherkin syntax) provides a structured way to write test scenarios:
| Component | Purpose | Example |
|---|---|---|
| Given | Preconditions and context | “Given sensor is calibrated at 25°C” |
| When | Action or trigger | “When temperature drops below threshold” |
| Then | Expected outcome | “Then alert is sent within 5 seconds” |
1577.2.3 Priority Scoring
Test priority helps allocate testing resources effectively:
\[Priority = \frac{Risk \times Frequency}{10}\]
Where: - Risk (1-10): Impact if the feature fails - Frequency (1-10): How often the feature is used
| Priority Score | Classification | Testing Frequency |
|---|---|---|
| 7-10 | Critical | Every build |
| 4-6 | Important | Daily |
| 1-3 | Low | Weekly/Release |
1577.2.4 IoT-Specific Testing Challenges
%% fig-alt: Mind map showing IoT-specific testing challenges organized into categories including hardware variability, connectivity issues, environmental factors, security concerns, and scalability requirements.
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#2C3E50', 'primaryTextColor': '#FFFFFF', 'primaryBorderColor': '#16A085', 'lineColor': '#7F8C8D', 'secondaryColor': '#ECF0F1', 'tertiaryColor': '#FFFFFF'}}}%%
mindmap
root((IoT Testing<br/>Challenges))
Hardware
Sensor accuracy
Calibration drift
Component aging
Power variations
Connectivity
Intermittent networks
Protocol variety
Latency variations
Packet loss
Environment
Temperature extremes
Humidity
Interference
Physical access
Security
Authentication
Encryption
Firmware updates
Physical tampering
Scale
Device volume
Data throughput
Geographic distribution
Heterogeneity
IoT devices often operate in environments that are difficult to replicate in a lab. Consider: - Shadow testing: Run tests alongside production workloads - Canary deployments: Roll out changes to a subset of devices first - Feature flags: Enable/disable features without deployment - Chaos engineering: Intentionally introduce failures to test resilience
1577.3 What’s Next
Explore related testing and quality topics:
- Hardware Selection Optimizer - Choose the right components
- Power Budget Calculator - Validate power requirements
- Design Thinking and Planning - Development process guide
- Testing and Validation - Troubleshooting techniques
Interactive test generator created for the IoT Class Textbook - TEST-001