Embedded software development using BDD

Preview:

DESCRIPTION

This presentation makes the case for BDD in general and focuses on its use within embedded software development. Using the Cucumber gem, I will demonstrate how to use feature files in the context of working with embedded hardware projects, and explain how to extend the framework using the Wire protocol to allow integration tests to run in-situ, which greatly enhances testing coverage compared to PC-based testing using emulators. I also cover the notion of a SpecFlow gateway, with which one can achieve end-to-end testing with a variety of devices as an orchestration mechanism for broader tests.

Citation preview

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

THANK YOU!@itababy

www.in-context.com

Recommended