Testing Embedded Systems
With Cucumber
Ian Dees • @undees
CukeUp! NYC 2013
Plenty of Ruby, but...
There will be C
There will be C++
There will be C#
http://pragprog.com/
titles/dhwcr
discount code:
CucumberIanDees
The Embedded
Continuum
almost a
computer
chip and
some ROM
Simple devices
No Ruby, no Cucumber
Gray Code
A simple system to test
Feature: Gray Code
Scenario Outline: Counter
When I press the button
Then the LEDs should read "<leds>"
Examples:
| leds |...
Arduino
void loop() {
button.update();
bool buttonPressed = button.risingEdge();
if (buttonPressed) {
counter = (counter + 1) % EN...
Drive code directly
void loop() {
button.update();
bool buttonPressed = button.risingEdge();
if (buttonPressed) {
counter = (counter + 1) % EN...
void loop() {
button.update();
bool buttonPressed = button.risingEdge();
if (buttonPressed) {
counter = (counter + 1) % EN...
static bool isFakeButtonPressed;
class Bounce {
public:
// ...
bool risingEdge() {
bool result = isFakeButtonPressed;
isFa...
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...
https://github.com/ffi/ffi
FFI
require 'ffi'
module Arduino
extend FFI::Library
ffi_lib 'graycode'
attach_function :press, [], :void
attach_function :led...
When /^I press the button$/ do
Arduino.press
Arduino.loop
end
Then /^the LEDs should read "(.*?)"$/ do
|leds|
expect(Ardui...
Cucumber Wire Protocol
Cucumber-CPP
https://github.com/cucumber/cucumber-cpp
host: localhost
port: 3902
features/step_definitions/cucumber.wire
When /^I press the button$/ do
Arduino.press
Arduino.loop
end
Then /^the LEDs should read "(.*?)"$/ do
|expected|
expect(A...
WHEN("^I press the button$") {
press();
loop();
}
THEN("^the LEDs should read "(.*?)"$") {
REGEX_PARAM(string, 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_matches
invoke
Two messages
Then the LEDs should read "..O"
➡
["step_matches",
{"name_to_match":
"the LEDs should read"}]
➡
["success",
[{"id":"1",
"a...
json spirit
http://www.codeproject.com/KB/recipes/
JSON_Spirit.aspx
extern "C" void respond_to_cucumber(
FILE* stream,
const char* message) {
string s = message;
Value v;
read(s, v);
Array& ...
if (type == "step_matches") {
string name = step_name(v);
if (name == "I press the button") {
report_success(stream);
retu...
if (type == "step_matches") {
string name = step_name(v);
if (name == "I press the button") {
report_success(stream);
retu...
if (type == "step_matches") {
string name = step_name(v);
if (...) {
// ...
} else if (name.find("the LEDs") == 0)
const i...
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_led...
https://github.com/hparra/ruby-serialport
Serial
void loop() {
button.update();
bool buttonPressed = button.risingEdge();
if (buttonPressed) {
counter = (counter + 1) % EN...
void loop() {
button.update();
bool buttonPressed = button.risingEdge();
if ( buttonPressed ) {
counter = (counter + 1) % ...
void loop() {
button.update();
bool buttonPressed = button.risingEdge();
int command = (Serial.available() > 0 ?
Serial.re...
void loop() {
button.update();
bool buttonPressed = button.risingEdge();
int command = (Serial.available() > 0 ?
Serial.re...
void loop() {
button.update();
bool buttonPressed = button.risingEdge();
int command = (Serial.available() > 0 ?
Serial.re...
void loop() {
button.update();
bool buttonPressed = button.risingEdge();
int command = (Serial.available() > 0 ?
Serial.re...
require 'serialport'
module Arduino
@@port = SerialPort.open 2, 9600
at_exit { @@port.close }
def self.press
@@port.write ...
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 White
https://github.com/TestStack/White
namespace Calc.Spec
{
[Binding]
public class CalculatorSteps
{
private Window window;
[Before]
public void Before()
{
Appl...
namespace Calc.Spec
{
[Binding]
public class CalculatorSteps
{
private Window window;
[Before]
public void Before()
{
Appl...
namespace Calc.Spec
{
[Binding]
public class CalculatorSteps
{
private Window window;
[Before]
public void Before()
{
Appl...
[When(@"I multiply (.*) and (.*)")]
public void WhenIMultiply(string a, string b)
{
window.Keyboard.Enter(a + "*" + b + "=...
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, "/",
&sho...
int main()
{
struct mg_context *ctx = mg_start();
mg_set_option(ctx, "ports", "33333");
mg_set_uri_callback(ctx, "/",
&sho...
static void multiply(
struct mg_connection *conn,
const struct mg_request_info *request_info,
void *user_data)
{
char *ap ...
static void multiply(
struct mg_connection *conn,
const struct mg_request_info *request_info,
void *user_data)
{
char *ap ...
static void multiply(
struct mg_connection *conn,
const struct mg_request_info *request_info,
void *user_data)
{
char *ap ...
static void multiply(
struct mg_connection *conn,
const struct mg_request_info *request_info,
void *user_data)
{
char *ap ...
static void get_result(
struct mg_connection *conn,
const struct mg_request_info *request_info,
void *user_data)
{
mg_prin...
https://github.com/jnunemaker/httparty
HTTParty
When(/^I multiply (d+) and (d+)$/) do |a, b|
Calculator.multiply a, b
end
Then(/^I should get (d+)$/) do |n|
expect(Calcul...
require 'httparty'
class Calculator
include HTTParty
base_uri 'localhost:33333'
def self.multiply(a, b)
get "/multiply?mul...
(because tl;dr is passé)
In summary:
Have source? Device Technique
yes simple Direct
yes simple Serial
yes medium
Cucumber Wire
Protocol
yes powerful Custom TC...
Thank you
https://github.com/undees/cukeup
Cukeup nyc ian dees on testing embedded systems with cucumber
Upcoming SlideShare
Loading in …5
×

Cukeup nyc ian dees on testing embedded systems with cucumber

1,205
-1

Published on

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.

Published in: Technology, Business
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
1,205
On Slideshare
0
From Embeds
0
Number of Embeds
3
Actions
Shares
0
Downloads
15
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

Cukeup nyc ian dees on testing embedded systems with cucumber

  1. 1. Testing Embedded Systems With Cucumber Ian Dees • @undees CukeUp! NYC 2013
  2. 2. Plenty of Ruby, but...
  3. 3. There will be C
  4. 4. There will be C++
  5. 5. There will be C#
  6. 6. http://pragprog.com/ titles/dhwcr discount code: CucumberIanDees
  7. 7. The Embedded Continuum
  8. 8. almost a computer chip and some ROM
  9. 9. Simple devices No Ruby, no Cucumber
  10. 10. Gray Code A simple system to test
  11. 11. 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.. | | ... |
  12. 12. Arduino
  13. 13. void loop() { button.update(); bool buttonPressed = button.risingEdge(); if (buttonPressed) { counter = (counter + 1) % ENTRIES; updateLeds(); } delay(50); }
  14. 14. Drive code directly
  15. 15. void loop() { button.update(); bool buttonPressed = button.risingEdge(); if (buttonPressed) { counter = (counter + 1) % ENTRIES; updateLeds(); } delay(50); }
  16. 16. void loop() { button.update(); bool buttonPressed = button.risingEdge(); if (buttonPressed) { counter = (counter + 1) % ENTRIES; updateLeds(); } delay(50); }
  17. 17. static bool isFakeButtonPressed; class Bounce { public: // ... bool risingEdge() { bool result = isFakeButtonPressed; isFakeButtonPressed = false; return result; } };
  18. 18. extern "C" void press() { isFakeButtonPressed = true; }
  19. 19. 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; }
  20. 20. https://github.com/ffi/ffi FFI
  21. 21. 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, [], :void end
  22. 22. When /^I press the button$/ do Arduino.press Arduino.loop end Then /^the LEDs should read "(.*?)"$/ do |leds| expect(Arduino.leds).to eq(leds) end
  23. 23. Cucumber Wire Protocol
  24. 24. Cucumber-CPP https://github.com/cucumber/cucumber-cpp
  25. 25. host: localhost port: 3902 features/step_definitions/cucumber.wire
  26. 26. When /^I press the button$/ do Arduino.press Arduino.loop end Then /^the LEDs should read "(.*?)"$/ do |expected| expect(Arduino.leds).to eq(expected) end
  27. 27. WHEN("^I press the button$") { press(); loop(); } THEN("^the LEDs should read "(.*?)"$") { REGEX_PARAM(string, expected); BOOST_CHECK_EQUAL(leds(), expected); }
  28. 28. Bespoke wire server
  29. 29. http://www.2600.com/code/212/listener.c listen/accept/read/write
  30. 30. while(fgets(buf,sizeof buf,rStream)) { respond_to_cucumber(wStream, buf); }
  31. 31. step_matches invoke Two messages
  32. 32. Then the LEDs should read "..O" ➡ ["step_matches", {"name_to_match": "the LEDs should read"}] ➡ ["success", [{"id":"1", "args":[{"val":"..O", "pos":"22"}]}]]
  33. 33. json spirit http://www.codeproject.com/KB/recipes/ JSON_Spirit.aspx
  34. 34. 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); }
  35. 35. if (type == "step_matches") { string name = step_name(v); if (name == "I press the button") { report_success(stream); return; } else if (...) // ... } }
  36. 36. if (type == "step_matches") { string name = step_name(v); if (name == "I press the button") { report_success(stream); return; } else if (...) // ... } }
  37. 37. 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; } }
  38. 38. Then the LEDs should read "..O" ➡ ["invoke", {"id":"1", "args":["..O"]}] ➡ ["success", []]
  39. 39. if (type == "invoke") { string id = step_id(v); if (id == "0") { press(); loop(); } else if (id == "1") { // ... } } }
  40. 40. if (type == "invoke") { string id = step_id(v); if (id == "0") { press(); loop(); } else if (id == "1") { // ... } } }
  41. 41. 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; } } }
  42. 42. https://github.com/hparra/ruby-serialport Serial
  43. 43. void loop() { button.update(); bool buttonPressed = button.risingEdge(); if (buttonPressed) { counter = (counter + 1) % ENTRIES; updateLeds(); } delay(50); }
  44. 44. void loop() { button.update(); bool buttonPressed = button.risingEdge(); if ( buttonPressed ) { counter = (counter + 1) % ENTRIES; updateLeds(); delay(50); }
  45. 45. 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); }
  46. 46. 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); }
  47. 47. 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); }
  48. 48. 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); }
  49. 49. 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 end end
  50. 50. Ruby, C#, SpecFlow, Cucumber Almost a computer
  51. 51. Feature: Calculator Scenario: Add two numbers When I multiply 2 and 3 Then I should get 6
  52. 52. Run Cucumber directly
  53. 53. rsync -av --delete . remote1:test_path ssh remote1 'cd test_path && cucumber'
  54. 54. Drive the GUI
  55. 55. TestStack White https://github.com/TestStack/White
  56. 56. 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); } // ... } }
  57. 57. 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); } // ... } }
  58. 58. 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); } // ... } }
  59. 59. [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); }
  60. 60. Give your app an API
  61. 61. Mongoose web server
  62. 62. 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; }
  63. 63. 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; }
  64. 64. 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 OKrn Content-Type: text/plainrnrn"); mg_free(multiplier_s); mg_free(multiplicand_s); }
  65. 65. 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 OKrn Content-Type: text/plainrnrn"); mg_free(multiplier_s); mg_free(multiplicand_s); }
  66. 66. 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 OKrn Content-Type: text/plainrnrn"); mg_free(multiplier_s); mg_free(multiplicand_s); }
  67. 67. 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 OKrn Content-Type: text/plainrnrn"); mg_free(multiplier_s); mg_free(multiplicand_s); }
  68. 68. 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 OKrn Content-Type: text/plainrnrn%d", result); }
  69. 69. https://github.com/jnunemaker/httparty HTTParty
  70. 70. When(/^I multiply (d+) and (d+)$/) do |a, b| Calculator.multiply a, b end Then(/^I should get (d+)$/) do |n| expect(Calculator.result).to eq(n.to_i) end
  71. 71. 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 end end
  72. 72. (because tl;dr is passé) In summary:
  73. 73. Have source? Device Technique yes simple Direct yes simple Serial yes medium Cucumber Wire Protocol yes powerful Custom TCP no powerful GUI
  74. 74. Thank you https://github.com/undees/cukeup
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×