EMBEDDED SOFTWARE DEVELOPMENT USING BDD
Itamar Hassin September 2014
THIS TALK COVERS
•Embedded development challenges !
•The case for BDD !
•Simulating the target !
•Accessing the target remotely/in-situ !
•Orchestrating access to multiple targets
EMBEDDED DEVELOPMENT CHALLENGES
•A simulator rarely available or not fully functional !
•Remote device verification !
•Complex state-machines !
•Test coverage often limited to unit testing
THE CASE FOR CUCUMBER
•Direct mapping from user story acceptance criteria
!
•Living documentation, unified view of the product !
•Helps defines ‘done’: Code is tested and validated !
•BDD promotes lean code & emergent design !
•Authored by the team: BAs/QA/Devs
CLOSER TO THE SOURCE
PREMISE FOR AUTOMATIONProgramatically validate that code solves the problem by articulating behaviour in machine-readable form.
CUCUMBER FOR EMBEDDED!
WRITE ONCE, USE THRICE
Feature: Alarm assured to appear in quiet mode !Scenario: Pressure alarm Given device is in quiet mode When pressure sensor is disconnected Then a silent alarm will appear
IMPLEMENT STEPS
Given(/Given device is in quiet mode $/) do @device.set_quiet_mode(1) end
IMPLEMENT A SIMULATOR
class Device def set_quiet_mode(flag) if (flag) mode |= QUIET_MODE else mode &= ~QUIET_MODE end update_driver(mode) end end
VALIDATE UNDER SIMULATOR
Scenario: Pressure alarm Given device is in quiet mode When pressure sensor is disconnected Then a silent alarm will appear !
5 scenarios (5 passed) 26 steps (26 passed) 0m0.052s
WHEN SIMULATION IS NOT ENOUGH
THE “WIRE”
•When your system does not have native support
•When you want a lean, portable implementation
SIMPLIFIED WIRE PROTOCOL
WIRE IMPLEMENTATION BLUEPRINT
•TCP/IP loop managing Cucumber protocol
•Function table for API invocation
•API implementation returning status to Cucumber
HOOKING CUCUMBER TO LISTENER
features/step_definitions/cucumber.wire host: deviceport: 3901
LISTENER TCP/IP LOOP
while(fgets(buff, sizeof(buff), rStream)) { respond_to_cucumber(wStream, buff); }
WIRE HANDLERvoid respond_to_cucumber(FILE* stream, char* msg) { if (MSG_TYPE_IS(msg, "step_matches")) respond_to_step_matches(stream, msg); else if (MSG_TYPE_IS(msg, "invoke")) respond_to_invoke(stream, msg); else if (MSG_TYPE_IS(msg, "begin_scenario")) respond_to_begin(stream, msg); else if (MSG_TYPE_IS(msg, "end_scenario")) respond_to_end(stream, msg); else respond_success(stream); }
FUNCTION TABLE
stepdef_t stepdefs[] = { { "device is in quiet mode”, set_quiet_mode } };
INVOKING API FUNCTIONS
void respond_to_invoke(…) { int id = get_function_id(msg); !
stepdefs[id].callback(arg_val) ? failure(stream) : success(stream); }
TARGET API
int set_quiet_mode(const char *arg) { context->requested_mode = atoi(arg); context->quiet_mode |= context->requested_mode; return(update_driver(context)); }
IMPLEMENTATION STACK
WORKING WITH CUCUMBER•Decide on a strategy (off-board, on-board)
•Get appropriate toolchain (cross compiler, linker)
•Implement and port Wire to target
•Run the feature files
•fail/implement/pass/refactor/repeat
USAGE PATTERNS Off-Board
• Framework on PC • Listener on PC • Proxy API on PC
• Network calls to Target API !
In-Situ • Framework on PC • Listener on Target • API calls on Target
Prog
ress
ion
WORKING AT A SAFE DISTANCE
OFF-BOARDCucumber running on
PC
Wire running on PC
Target API running on
PC
Target
NetworkC-implementation
• + Target untouched
• - All API’s must be exposed; low-fidelity; many moving parts;
UP CLOSE AND PERSONAL
IN-SITUFramework running on
PC
Cucumber-Wire running
on Target
Network C-implementation
Target API
• + high-fidelity, API’s not exposed
• - Server part of codebase
COLLABORATIVE ENVIRONMENT
GATEWAY
!
•Acts as an end-to-end test orchestrator !
•Switchboard events across heterogeneous devices
COLLABORATIVE END-TO-END TESTING
Framework running on
PC
C-implementation
Cucumber-Wire running
on Target
Targets
NativeWire
Collaboration
GATEWAY ARCHITECTURE
SpecFlow
Target B
Proxies
A1
B1
Hardware
Serial
Wire
Target ACucumber
Behave
END-TO-END FEATURES
Feature: Alarm assured to appear in quiet mode !
Scenario: Pressure alarm Given device is in quiet mode When pressure sensor is disconnected Then a silent alarm will appear
GATEWAY STEPS public class QuietModeSteps { SignalSimulator signalSimulator = new SignalSimulator(); MedicalDevice medicalDevice = new MedicalDevice(“192.168.1.1”, 3901); ! [Given(@"device is quiet mode")] public void GivenDeviceIsQuietMode() { NUnit.Framework.Assert.IsTrue(medicalDevice.SetQuietMode()); } ! [When(@“pressure sensor is disconnected")] public void GivenPressureSensorIsDisconnected() { NUnit.Framework.Assert.IsTrue(signalSimulator.SetPressure(off)); } }
GATEWAY PROXIES class MedicalDevice { protected Wire wire; ! public MedicalDevice(string ipAddress, int port) { myAddress = ipAddress; wire = new Wire(myAddress, port); wire.Open(); } ! public bool SetQuietMode() { wire.Send("[\"step_matches\",{\"name_to_match\":\"set quiet mode on\"}]\n"); wire.Send("[\"invoke\",{\"id\":\"7\",\"args\":[\"on\"]}]\n"); return(wire.Ack()); } }
EMULATING WIRE public class Wire { public int Open() { client = new TcpClient(myAddress, myPort); stream = client.GetStream(); return(Send(“[\”begin_scenario\"]\n")); } ! public int Close() { stream = client.GetStream(); Send("[\"end_scenario\"]\n"); return(client.Close()); } }
SpecFlow
Wire
Proxies
A1
Target
TCP
Given … quiet mode
Wire
int SetQuietMode(“on”) {}
Match: “set quiet\’(on|off)’\”
Invoke: idx:0, params: “on”
SPECFLOW TO WIRE
int set_quiet(char* state){}A
Exists in proxy? Write wrappers
Exists in Wire?
No
NoYes
Function table entry
Yes
Use in feature/step files
API Exists? No Implement API
Yes
MAINTENANCE CONSIDERATIONS
•Security - Anyone can connect to Wire!
•Regulation may not allow non-application code on a production system
Shut down the wire thread in production
COMPLIANCE CONSIDERATIONS
LESSONS LEARNED
Threading
Dual VocabularyThreads & Target Architecture
REFERENCES•Specification by example
•The Cucumber Book
•Cucumber Recipes
@history_pics/@historyinpicsJim Reese#Wikipedia
National Library of Australia
Photo Credits:
•SpecFlow
Top Related