cukeup nyc ian dees on testing embedded systems with cucumber

Post on 13-May-2015

962 Views

Category:

Technology

1 Downloads

Preview:

Click to see full reader

DESCRIPTION

Testing Embedded Systems With Cucumber Cucumber isn't just for web apps. You can test just about anything with it—including embedded systems! In this talk, we'll look at several different facets of driving hardware from Cucumber, including: Connecting to an Arduino using a custom serial protocol Taking advantage of the TCP stack when it's available Building the Cucumber wire protocol into your device What to do when you can't modify the app under test Driving more fully-featured devices such as the BeagleBone or RaspberryPi By the end of the presentation, you'll have a handle on what the various options are for testing embedded devices, and which tradeoffs will apply to your system.

TRANSCRIPT

Testing Embedded Systems With Cucumber

Ian Dees • @undeesCukeUp! NYC 2013

Plenty of Ruby, but...

There will be C

There will be C++

There will be C#

The Embedded Continuum

almost acomputer

chip andsome ROM

Simple devicesNo Ruby, no Cucumber

Gray CodeA simple system to test

Feature: Gray Code Scenario Outline: Counter When I press the button Then the LEDs should read "<leds>"

Examples: | leds | | ..O | | .OO | | .O. | | OO. | | OOO | | O.O | | O.. | | ... |

Arduino

void loop() { button.update(); bool buttonPressed = button.risingEdge();

if (buttonPressed) { counter = (counter + 1) % ENTRIES; updateLeds(); }

delay(50);}

Drive code directly

void loop() { button.update(); bool buttonPressed = button.risingEdge();

if (buttonPressed) { counter = (counter + 1) % ENTRIES; updateLeds(); }

delay(50);}

void loop() { button.update(); bool buttonPressed = button.risingEdge();

if (buttonPressed) { counter = (counter + 1) % ENTRIES; updateLeds(); }

delay(50);}

static bool isFakeButtonPressed;

class Bounce {public: // ...

bool risingEdge() { bool result = isFakeButtonPressed; isFakeButtonPressed = false; return result; }};

extern "C" void press() { isFakeButtonPressed = true;}

const char* leds() { static char buf[LEDS + 1] = {0};

for (int i = 0; i < LEDS; ++i) { buf[i] = STATES[counter][i] == HIGH ? 'O' : '.'; }

return buf;}

https://github.com/ffi/ffi

FFI

require 'ffi'

module Arduino extend FFI::Library ffi_lib 'graycode'

attach_function :press, [], :void attach_function :leds, [], :string

attach_function :setup, [], :void attach_function :loop, [], :voidend

When /^I press the button$/ do Arduino.press Arduino.loopend

Then /^the LEDs should read "(.*?)"$/ do |leds|

expect(Arduino.leds).to eq(leds)end

Cucumber Wire Protocol

Cucumber-CPPhttps://github.com/cucumber/cucumber-cpp

host: localhostport: 3902

features/step_definitions/cucumber.wire

When /^I press the button$/ do Arduino.press Arduino.loopend

Then /^the LEDs should read "(.*?)"$/ do |expected| expect(Arduino.leds).to eq(expected)end

WHEN("^I press the button$") { press(); loop();}

THEN("^the LEDs should read \"(.*?)\"$") { REGEX_PARAM(string, expected); BOOST_CHECK_EQUAL(leds(), expected);}

Bespoke wire server

http://www.2600.com/code/212/listener.c

listen/accept/read/write

while(fgets(buf,sizeof buf,rStream)) { respond_to_cucumber(wStream, buf);}

step_matchesinvoke

Two messages

Then the LEDs should read "..O"

⬇["step_matches", {"name_to_match": "the LEDs should read"}]

⬇["success", [{"id":"1", "args":[{"val":"..O", "pos":"22"}]}]]

extern "C" void respond_to_cucumber( FILE* stream, const char* message) { string s = message; Value v; read(s, v);

Array& a = v.get_array(); string type = a[0].get_str();

// handle Cucumber message types

report_success(stream);}

if (type == "step_matches") { string name = step_name(v);

if (name == "I press the button") { report_success(stream); return; } else if (...)

// ...

}}

if (type == "step_matches") { string name = step_name(v);

if (name == "I press the button") { report_success(stream); return; } else if (...)

// ...

}}

if (type == "step_matches") { string name = step_name(v);

if (...) { // ...

} else if (name.find("the LEDs") == 0) const int START = 22; string leds = name.substr(START, 3); report_match(leds, START, stream); return; }}

Then the LEDs should read "..O"

⬇["invoke", {"id":"1", "args":["..O"]}]

⬇["success", []]

if (type == "invoke") { string id = step_id(v);

if (id == "0") { press(); loop(); } else if (id == "1") {

// ...

} } }

if (type == "invoke") { string id = step_id(v);

if (id == "0") { press(); loop(); } else if (id == "1") {

// ...

} } }

if (type == "invoke") { string id = step_id(v);

if (id == "0") { // ...

} else if (id == "1") { string expected = step_leds(v); if (expected != leds()) { report_failure("LEDs", stream); return; } } }

https://github.com/hparra/ruby-serialport

Serial

void loop() { button.update(); bool buttonPressed = button.risingEdge();

if (buttonPressed) { counter = (counter + 1) % ENTRIES; updateLeds(); }

delay(50);}

void loop() { button.update(); bool buttonPressed = button.risingEdge();

if ( buttonPressed ) { counter = (counter + 1) % ENTRIES; updateLeds();

delay(50);}

void loop() { button.update(); bool buttonPressed = button.risingEdge();

int command = (Serial.available() > 0 ? Serial.read() : -1);

if (isIncrement(buttonPressed, command)) { counter = (counter + 1) % ENTRIES; updateLeds(); } else if (isQuery(command)) { Serial.write(leds()); Serial.write('\n'); }

delay(50);}

void loop() { button.update(); bool buttonPressed = button.risingEdge();

int command = (Serial.available() > 0 ? Serial.read() : -1);

if (isIncrement(buttonPressed, command)) { counter = (counter + 1) % ENTRIES; updateLeds(); } else if (isQuery(command)) { Serial.write(leds()); Serial.write('\n'); }

delay(50);}

void loop() { button.update(); bool buttonPressed = button.risingEdge();

int command = (Serial.available() > 0 ? Serial.read() : -1);

if (isIncrement(buttonPressed, command)) { counter = (counter + 1) % ENTRIES; updateLeds(); } else if (isQuery(command)) { Serial.write(leds()); Serial.write('\n'); }

delay(50);}

void loop() { button.update(); bool buttonPressed = button.risingEdge();

int command = (Serial.available() > 0 ? Serial.read() : -1);

if (isIncrement(buttonPressed, command)) { counter = (counter + 1) % ENTRIES; updateLeds(); } else if (isQuery(command)) { Serial.write(leds()); Serial.write('\n'); }

delay(50);}

require 'serialport'

module Arduino @@port = SerialPort.open 2, 9600 at_exit { @@port.close }

def self.press @@port.write '+' end

def self.leds @@port.write '?' @@port.read.strip endend

Ruby, C#, SpecFlow, Cucumber

Almost a computer

Feature: Calculator

Scenario: Add two numbers When I multiply 2 and 3 Then I should get 6

Run Cucumber directly

rsync -av --delete . remote1:test_path

ssh remote1 'cd test_path && cucumber'

Drive the GUI

TestStack Whitehttps://github.com/TestStack/White

namespace Calc.Spec{ [Binding] public class CalculatorSteps { private Window window;

[Before] public void Before() { Application application = Application.Launch("calc.exe"); window = application.GetWindow( "Calculator", InitializeOption.NoCache); } // ... }}

namespace Calc.Spec{ [Binding] public class CalculatorSteps { private Window window;

[Before] public void Before() { Application application = Application.Launch("calc.exe"); window = application.GetWindow( "Calculator", InitializeOption.NoCache); } // ... }}

namespace Calc.Spec{ [Binding] public class CalculatorSteps { private Window window;

[Before] public void Before() { Application application = Application.Launch("calc.exe"); window = application.GetWindow( "Calculator", InitializeOption.NoCache); } // ... }}

[When(@"I multiply (.*) and (.*)")]public void WhenIMultiply(string a, string b){ window.Keyboard.Enter(a + "*" + b + "=");}

[Then(@"I should get (.*)")]public void ThenIShouldGet(string expected){ window.Get<Label>(expected);}

Give your app an API

Mongoose web server

int main(){ struct mg_context *ctx = mg_start(); mg_set_option(ctx, "ports", "33333");

mg_set_uri_callback(ctx, "/", &show_index, 0); mg_set_uri_callback(ctx, "/multiply", &multiply, 0); mg_set_uri_callback(ctx, "/result", &get_result, 0);

getchar(); mg_stop(ctx);

return 0;}

int main(){ struct mg_context *ctx = mg_start(); mg_set_option(ctx, "ports", "33333");

mg_set_uri_callback(ctx, "/", &show_index, 0); mg_set_uri_callback(ctx, "/multiply", &multiply, 0); mg_set_uri_callback(ctx, "/result", &get_result, 0);

getchar(); mg_stop(ctx);

return 0;}

static void multiply( struct mg_connection *conn, const struct mg_request_info *request_info, void *user_data){ char *ap = mg_get_var(conn, "multiplier"); char *bp = mg_get_var(conn, "multiplicand");

int a = atol(ap); int b = atol(bp);

result = a * b;

mg_printf(conn, "HTTP/1.1 200 OK\r\n\Content-Type: text/plain\r\n\r\n");

mg_free(multiplier_s); mg_free(multiplicand_s);}

static void multiply( struct mg_connection *conn, const struct mg_request_info *request_info, void *user_data){ char *ap = mg_get_var(conn, "multiplier"); char *bp = mg_get_var(conn, "multiplicand");

int a = atol(ap); int b = atol(bp);

result = a * b;

mg_printf(conn, "HTTP/1.1 200 OK\r\n\Content-Type: text/plain\r\n\r\n");

mg_free(multiplier_s); mg_free(multiplicand_s);}

static void multiply( struct mg_connection *conn, const struct mg_request_info *request_info, void *user_data){ char *ap = mg_get_var(conn, "multiplier"); char *bp = mg_get_var(conn, "multiplicand");

int a = atol(ap); int b = atol(bp);

result = a * b;

mg_printf(conn, "HTTP/1.1 200 OK\r\n\Content-Type: text/plain\r\n\r\n");

mg_free(multiplier_s); mg_free(multiplicand_s);}

static void multiply( struct mg_connection *conn, const struct mg_request_info *request_info, void *user_data){ char *ap = mg_get_var(conn, "multiplier"); char *bp = mg_get_var(conn, "multiplicand");

int a = atol(ap); int b = atol(bp);

result = a * b;

mg_printf(conn, "HTTP/1.1 200 OK\r\n\Content-Type: text/plain\r\n\r\n");

mg_free(multiplier_s); mg_free(multiplicand_s);}

static void get_result( struct mg_connection *conn, const struct mg_request_info *request_info, void *user_data){ mg_printf(conn, "HTTP/1.1 200 OK\r\n\Content-Type: text/plain\r\n\r\n%d", result);}

https://github.com/jnunemaker/httparty

HTTParty

When(/^I multiply (\d+) and (\d+)$/) do |a, b| Calculator.multiply a, bend

Then(/^I should get (\d+)$/) do |n| expect(Calculator.result).to eq(n.to_i)end

require 'httparty'

class Calculator include HTTParty base_uri 'localhost:33333'

def self.multiply(a, b) get "/multiply?multiplier=#{a}\&multiplicand=#{b}" end

def self.result get("/result").to_i endend

(because tl;dr is passé)

In summary:

Have source? Device Technique

yes simple Direct

yes simple Serial

yes mediumCucumber Wire

Protocol

yes powerful Custom TCP

no powerful GUI

Thank youhttps://github.com/undees/cukeup

top related