It was a pleasure to speak at the recent norfolk developers conference.
My talk was "cyber-dojo: executing your code for fun and not for profit".
I spoke about cyber-dojo, demo'd its features, discussed its history, design, difficulties and underlying technology.
Videos of the talk are now on the infoq website. The slide-sync is not right at the start of part 2 but it soon gets corrected.
Hi. I'm Jon Jagger, director of software at Kosli.
I built cyber-dojo, the place teams practice programming.
Showing posts with label cyberdojo. Show all posts
Showing posts with label cyberdojo. Show all posts
cyber-dojo@KGISL Coimbatore
It was an honour and a pleasure to be invited to spend the day at KGISL Institute of Technology in Coimbatore, India.
After a presentation on Software Professionalism: How to do deliberate practice (skip to slide 33 if you know how cyber-dojo runs) I helped run a cyber-dojo using a custom server KGISL had built supporting more than sixteen participants - using famous computer scientists as avatars instead of animals. Fantastic! Youngsters from the local community also participated, encouraged by Sudharsan who has been running a local CoderDojo. I was hugely impressed to see Ashok, a founder of KGISL Group roll his sleeves up, literally, and participate in the practice. A terrific example of leadership. Then lunch, and another cyber-dojo, and some more consulting. I look forward to returning. Thank you KGISL.
Atlantec conference
It was a pleasure and an honour to speak at the first Atlantec conference held in Galway, Ireland on May 15th.
I talked about cyber-dojo and showed some statistics from a random sample of its 30,000+ cyber-dojos,
together with a few examples of code/tests typically submitted, a few dashboard patterns, and wrapped up linking testing to Le Chatelier's Law and some of my favourite Systems Thinking quotes from Bradford Keeney.
some cyber-dojo measurements
cyber-dojo has hosted about 13,000 practice sessions so far.
I've written a short ruby script to extract some measurements from a sample of 500 sessions.
I was looking at transitions between red, amber, and green traffic-lights:
The second column is colour → colour transition.
The third column is sample size.
Here's how I interpret the results:
- red means one or more tests failed
- amber means the tests did not run (eg syntax error)
- green means the tests ran and all passed
The second column is colour → colour transition.
The third column is sample size.
| 3.94 | amber → green | 447 |
| 4.65 | amber → red | 379 |
| 4.67 | amber → amber | 1462 |
| 5.39 | red → green | 607 |
| 6.01 | red → red | 604 |
| 7.52 | green → red | 420 |
| 13.65 | green → amber | 436 |
| 17.67 | red → amber | 432 |
| 22.18 | green → green | 598 |
Here's how I interpret the results:
- If you're at red or green and you make a small change (5.39,6.01,7.52) you're likely to stay at red or green.
- If you're at red or green and you make a large change (13.65,17.67) you're likely to transition to amber.
- There is a big spike in the number of amber → amber transitions (1462). I speculate that long sequences of these transitions are occuring after a large 13.65 green → amber or 17.67 red → amber transition.
- I think the green → green value of 22.18 is larger than it should be because it's including plain file renames.
dojo = way place
cyber-dojo Raspberry Pies in action
Liam Friel, who helps to run a CoderDojoBray (in Ireland) asked me for some Raspberry Pies which I was more than happy to give him, paid for from the donations lots of you generous people have made from cyber-dojo.
Liam sent me this wonderful photo of a CoderDojoBray session and writes:
Your Pies have been getting a lot of use... We've got 8 Pies in total. Got a reasonably steady turnout at the dojo, 75-85 kids turning up each week.
Awesome. If, like Liam, you would like some Raspberry Pies to help kids learn about coding, please email. Thanks
Poker hands in Ruby
John Cleary (@TheRealBifter) is doing a nice project -
The 12 TDD's of Xmas.
Day 11 was Poker Hands. I did it in Ruby. Here's the code from traffic-light 116. Tests first:
require './card'
require './hand'
require 'test/unit'
class TestUntitled < Test::Unit::TestCase
def test_start
hand = Hand.new("2H 4S 4C 2D 4H")
assert_equal Card.new('2',:hearts), hand[0]
assert_equal Card.new('4',:spades), hand[1]
assert_equal Card.new('4',:clubs), hand[2]
assert_equal Card.new('2',:diamonds), hand[3]
assert_equal Card.new('4',:hearts), hand[4]
end
def test_card_has_pips_and_suit_set_on_creation
card = Card.new('2',:hearts)
assert_equal '2', card.pips
assert_equal :hearts, card.suit
card = Card.new('T',:hearts)
assert_equal 'T', card.pips
assert_equal :hearts, card.suit
card = Card.new('J',:hearts)
assert_equal 'J', card.pips
assert_equal :hearts, card.suit
card = Card.new('Q',:hearts)
assert_equal 'Q', card.pips
assert_equal :hearts, card.suit
card = Card.new('K',:hearts)
assert_equal 'K', card.pips
assert_equal :hearts, card.suit
card = Card.new('A',:hearts)
assert_equal 'A', card.pips
assert_equal :hearts, card.suit
end
def test_hand_ranked_three_of_a_kind
assert_equal :three_of_a_kind, Hand.new("2H 4S 4C AD 4H").rank
end
def test_hand_ranked_one_pair
assert_equal :one_pair, Hand.new("2H 4S 5C JD 4H").rank
end
def test_hand_ranked_two_pairs
assert_equal :two_pairs, Hand.new("2H 4S 5C 2D 4H").rank
end
def test_hand_ranked_flush
assert_equal :flush, Hand.new("2H 4H 6H 8H TH").rank
end
def test_hand_ranked_straight
assert_equal :straight, Hand.new("2H 3C 4H 5H 6H").rank
end
def test_hand_ranked_full_house
assert_equal :full_house, Hand.new("2H 4S 4C 2D 4H").rank
end
def test_hand_ranked_four_of_a_kind
assert_equal :four_of_a_kind, Hand.new("2H 4S 4C 4D 4H").rank
end
def test_hand_ranked_straight_flush
assert_equal :straight_flush, Hand.new("2H 4H 3H 5H 6H").rank
end
def test_hand_ranked_high_card
assert_equal :high_card, Hand.new("2C 3H 4S 8C AH").rank
end
def test_full_house_beats_flush
black = Hand.new("2H 4S 4C 2D 4H")
white = Hand.new("2S 8S AS QS 3S")
assert_equal 1, black <=> white
end
def test_higher_card_wins_if_equal_rank
black = Hand.new("2H 3D 5S 9C KD")
assert_equal :high_card, black.rank
white = Hand.new("2C 3H 4S 8C AH")
assert_equal :high_card, white.rank
assert_equal -1, black <=> white
end
def test_equal_hands
black = Hand.new("2H 3D 5S 9C KD")
assert_equal :high_card, black.rank
white = Hand.new("2D 3H 5C 9S KH")
assert_equal :high_card, white.rank
assert_equal 0, black <=> white
end
end
Code second:
class Hand
def initialize(cards)
@cards =
cards.gsub(/\s+/, "")
.scan(/.{2}/)
.map{|ch| Card.new(ch[0],suit(ch[1]))}
end
def [](n)
return @cards[n]
end
def rank
return :straight_flush if straight? && flush?
return :flush if flush?
return :straight if straight?
pip_tallies = pip_counts.sort.reverse
return {
[4,1] => :four_of_a_kind,
[3,2] => :full_house,
[3,1] => :three_of_a_kind,
[2,2] => :two_pairs,
[2,1] => :one_pair,
[1,1] => :high_card
}[pip_tallies[0..1]]
end
def <=>(other)
keys <=> other.keys
end
def keys
[ranking,pip_counts]
end
private
def ranking
ranks.index(rank)
end
def ranks
[
:high_card,
:pair,
:two_pairs,
:three_of_a_kind,
:straight,
:flush,
:full_house,
:four_of_a_kind,
:straight_flush
]
end
def pip_counts
"23456789TJQKA"
.chars
.collect {|pips| pip_count(pips)}
end
def pip_count(pips)
@cards.count{|card| card.pips == pips}
end
def pip_flags
pip_counts.map{|n| n > 0 ? 'T' : 'F'}.join
end
def straight?
pip_flags.include? 'TTTTT'
end
def flush?
suit_counts.any?{|n| n == 5}
end
def suit_counts
suits.collect{|suit| suit_count(suit)}
end
def suits
[:clubs,:diamonds,:hearts,:spades]
end
def suit_count(suit)
@cards.count{|card| card.suit == suit}
end
def suit(ch)
return suits["CDHS".index(ch)]
end
end
class Card
def initialize(pips,suit)
@pips,@suit = pips,suit
end
def ==(other)
pips == other.pips && suit == other.suit
end
def pips
@pips
end
def suit
@suit
end
end
You can
replay my entire progression (warts and all) on Cyber-Dojo (naturally).
Phone Numbers in Ruby
John Cleary (@TheRealBifter) is doing a nice project -
The 12 TDD's of Xmas.
Day 10 was the Phone number prefix problem. I did it in Ruby. Here's the code from traffic-light 14. Tests first (very minimal - I'm a bit pressed for time):
require './consistent'
require 'test/unit'
class TestUntitled < Test::Unit::TestCase
def test_consistent_phone_list
list = {
'Bob' => '91125426',
'Alice' => '97625992',
}
assert consistent(list)
end
def test_inconsistent_phone_list
list = {
'Bob' => '91125426',
'Alice' => '97625992',
'Emergency' => '911'
}
assert !consistent(list)
end
end
Code second:
def consistent(list)
list.values.sort.each_cons(2).none? { |pair| prefix(*pair) }
end
def prefix(lhs,rhs)
rhs.start_with? lhs
end
The each_cons from the previous problem proved very handy here. As did none?
I really feel I'm starting to get the hang of ruby.
You can
replay my entire progression (warts and all) on Cyber-Dojo (naturally).
Monty Hall in Ruby
John Cleary (@TheRealBifter) is doing a nice project -
The 12 TDD's of Xmas.
Day 4 was the Monty Hall problem. I did it in Ruby. Here's the code from traffic-light 111. Tests first:
require './monty_hall'
require 'test/unit'
class TestMontyHall < Test::Unit::TestCase
def test_either_goat_door_is_opened_when_you_choose_the_car_door
check_either_goat([:car, :goat, :goat],
{ :chosen_door => 0,
:goat_doors => [1, 2]
})
check_either_goat([:goat, :car, :goat],
{ :chosen_door => 1,
:goat_doors => [0, 2]
})
check_either_goat([:goat, :goat, :car],
{ :chosen_door => 2,
:goat_doors => [0, 1]
})
end
def test_other_goat_door_is_opened_when_you_choose_a_goat_door
prizes = [:car, :goat, :goat]
check_other_goat(prizes,
{ :chosen_door => 1,
:opened_door => 2,
:offered_door => 0
})
check_other_goat(prizes,
{ :chosen_door => 2,
:opened_door => 1,
:offered_door => 0
})
prizes = [:goat, :car, :goat]
check_other_goat(prizes,
{ :chosen_door => 0,
:opened_door => 2,
:offered_door => 1
})
check_other_goat(prizes,
{ :chosen_door => 2,
:opened_door => 0,
:offered_door => 1
})
prizes = [:goat, :goat, :car]
check_other_goat(prizes,
{ :chosen_door => 0,
:opened_door => 1,
:offered_door => 2
})
check_other_goat(prizes,
{ :chosen_door => 1,
:opened_door => 0,
:offered_door => 2
})
end
def test_strategy_of_sticking_with_chosen_door
wins = Array.new(big) { MontyHall.new() }
.count { |game| game.chosen_door == game.car_door }
puts "Win car(sticking with chosen door):#{wins}/#{big}"
end
def test_strategy_of_switching_to_offered_door
wins = Array.new(big) { MontyHall.new() }
.count { |game| game.offered_door == game.car_door }
puts "Win car(switching to offered door):#{wins}/#{big}"
end
#- - - - - - - - - - - - - - - - - - - - - - - -
def check_either_goat(prizes, expected)
chosen_door = expected[:chosen_door]
goat_doors = expected[:goat_doors]
check_params(prizes, chosen_door, goat_doors[0], goat_doors[1])
goat_counts = [0,0]
100.times do |n|
game = MontyHall.new(prizes,chosen_door)
opened_door = game.opened_door
offered_door = game.offered_door
assert_equal chosen_door, game.chosen_door
assert_equal goat_doors.sort, [opened_door,offered_door].sort
assert_equal doors, [chosen_door,opened_door,offered_door].sort
assert_equal :car , prizes[chosen_door]
assert_equal :goat, prizes[opened_door]
assert_equal :goat, prizes[offered_door]
[0,1].each do |n|
goat_counts[n] += (offered_door == goat_doors[n] ? 1 : 0)
end
end
[0,1].each { |n| assert goat_counts[n] > 25 }
end
def check_other_goat(prizes, expected)
chosen_door = expected[:chosen_door]
opened_door = expected[:opened_door]
offered_door = expected[:offered_door]
check_params(prizes, chosen_door, opened_door, offered_door)
game = MontyHall.new(prizes, chosen_door)
assert_equal chosen_door, game.chosen_door
assert_equal opened_door, game.opened_door
assert_equal offered_door, game.offered_door
assert_equal :goat, prizes[ chosen_door]
assert_equal :goat, prizes[ opened_door]
assert_equal :car , prizes[offered_door]
end
def check_params(prizes, door1, door2, door3)
assert_equal 3, prizes.length
prizes.each { |prize| assert [:goat,:car].include? prize }
assert_equal doors, [door1,door2,door3].sort
end
def doors
[0,1,2]
end
def big
1000
end
end
Code second:
class MontyHall
def initialize(prizes = [:goat,:goat,:car].shuffle,
chosen_door = doors.shuffle[0])
@prizes = prizes
@chosen_door = chosen_door
@car_door = prizes.find_index { |prize| prize == :car }
if prizes[chosen_door] == :car
@opened_door = goat_doors.shuffle[0]
end
if prizes[chosen_door] == :goat
@opened_door = (goat_doors - [chosen_door])[0]
end
@offered_door = (doors - [chosen_door, opened_door])[0]
end
def chosen_door
@chosen_door
end
def car_door
@car_door
end
def opened_door
@opened_door
end
def offered_door
@offered_door
end
private
def doors
[0,1,2]
end
def goat_doors
doors.select { |door| @prizes[door] == :goat }
end
end
You can
replay my entire progression (warts and all) on Cyber-Dojo (naturally).
Bowling Game in Ruby
John Cleary (@TheRealBifter) is doing a nice project -
The 12 TDD's of Xmas.
Day 9 was the Bowling Game problem. I did it in Ruby. Here's the code from traffic-light 100. Tests first:
require './score'
require 'test/unit'
class TestScore < Test::Unit::TestCase
def test_score_uninteresting_game
# 7 7 7 7 7 = 35
# 6 6 6 6 6 = 30
balls = "51|52|51|52|51|52|51|52|51|52"
assert_equal 65, score(balls)
end
def test_score_all_frames_5_spare
# 15 15 15 15 15 = 75
# 15 15 15 15 15 = 75
balls = "5/|5/|5/|5/|5/|5/|5/|5/|5/|5/|5"
assert_equal 150, score(balls)
end
def test_perfect_score
# 30 30 30 30 30 = 150
# 30 30 30 30 30 = 150
balls = "X|X|X|X|X|X|X|X|X|X|XX"
assert_equal 300, score(balls)
end
def test_10_strikes_then_33
# 30 30 30 30 16 = 136
# 30 30 30 30 23 = 143
balls = "X|X|X|X|X|X|X|X|X|X|33"
assert_equal 279, score(balls)
end
def test_game_with_spare_in_middle_of_strikes
# 20 30 30 30 30 = 140
# 25 20 30 30 30 = 135
balls = "X|X|5/|X|X|X|X|X|X|X|XX"
assert_equal 275, score(balls)
end
def test_game_with_strike_in_middle_of_spares
# 15 20 15 13 20 = 83
# 13 11 20 14 12 = 70
balls = "2/|3/|5/|1/|X|3/|5/|4/|3/|2/|X"
assert_equal 153, score(balls)
end
def test_game_with_zero_balls
# 8 7 7 7 7 = 36
# 6 5 6 6 6 = 29
balls = "51|62|50|52|51|52|51|52|51|52"
assert_equal 65, score(balls)
end
def test_game_with_dash_as_zero_balls
# 8 7 7 7 8 = 37
# 6 5 6 6 6 = 29
balls = "51|62|5-|52|51|52|51|52|51|62"
assert_equal 66, score(balls)
end
end
Code second:
def score(balls)
frames = (balls).split("|")
while frames.length != 12
frames << "0"
end
frames.each_cons(3).collect{ |frame|
frame_score(frame)
}.inject(:+)
end
def frame_score(frames)
if strike? frames[0]
10 + strike_bonus(frames[1..2])
elsif spare? frames[0]
10 + ball_score(frames[1][0])
else
frames[0].chars.collect{ |ball|
ball_score(ball)
}.inject(:+)
end
end
def strike_bonus(frames)
if frames[0] == "XX"
20
elsif strike? frames[0]
10 + ball_score(frames[1][0])
elsif spare? frames[0]
10
else
ball_score(frames[0][0]) + ball_score(frames[0][1])
end
end
def ball_score(ball)
if strike? ball
10
else
ball.to_i
end
end
def strike?(frame)
frame == "X"
end
def spare?(frame)
frame[-1] == "/"
end
It took me a while to realize that each_slice should have been each_cons.
You can
replay my entire progression (warts and all) on Cyber-Dojo (naturally).
Agile on the beach
Last week I attended the excellent Agile on the beach conference in Falmouth, Cornwall. I'm really proud to have been the consultant who provided most of the technical-tdd agile training that has helped create 50 new jobs in Cornwall.
I thought Schalk Cronjé's talk was excellent. So was the one Ed Sykes did. And Seb Rose too. The highlight for me was Eben Upton's endnote. Inspiring. I'm going to visit my old secondary school and see if they want half-a-dozen Raspberry Pies. And I'm going to buy one for myself and put Cyber-Dojo onto it. I ran a double cyber-dojo session at the conference and several teachers said they wanted to run cyber-dojos as part of their course - fantastic.
I thought Schalk Cronjé's talk was excellent. So was the one Ed Sykes did. And Seb Rose too. The highlight for me was Eben Upton's endnote. Inspiring. I'm going to visit my old secondary school and see if they want half-a-dozen Raspberry Pies. And I'm going to buy one for myself and put Cyber-Dojo onto it. I ran a double cyber-dojo session at the conference and several teachers said they wanted to run cyber-dojos as part of their course - fantastic.
no scaffolding means we're done
Suppose I'm doing the print-diamond kata in cyber-dojo in Java. I start with a test
But suppose you get hit by a bus tomorrow. How easily could your colleagues tell that this code was work in progress? Code that was not finished?
As an experiment I thought I would try an alternative style. One that is not so relaxed about the asymmetry. One that tries a bit harder to be more explicit about distinguishing code that's a temporary step still on the path from code that has reached its destination.
First I wrote a test that expresses the fact that nothing is implemented.
I have unreachable scaffolding.
Time for the scaffolding to come down.
I delete the throw statement.
We're green.
The scaffolding is gone.
We're done.
@Test
public void diamond_A() {
String[] expected = {
"A"
};
String[] actual = new Diamond('A').toLines();
assertArrayEquals(expected, actual);
}
I slime a solution as follows
public class Diamond {
private char widest;
public Diamond(char widest) {
this.widest = widest;
}
public String[] toLines() {
return new String[]{ "A" };
}
}
now I add a second test
@Test
public void diamond_B() {
String[] expected = {
" A ",
"B B",
" A ",
};
String[] actual = new Diamond('B').toLines();
assertArrayEquals(expected, actual);
}
and I slime again as follows
public class Diamond {
private char widest;
public Diamond(char widest) {
this.widest = widest;
}
public String[] toLines() {
if (widest == 'A')
return new String[] {
"A"
};
else
return new String[] {
" A ",
"B B",
" A ",
};
}
}
Like all techniques this approach has a certain style.
In this case there is a small but definite asymmetry between the specificness of the tests
(one for 'A' and another one for 'B') and the slightly less specificness of the code (an explicit if for 'A' but a default everything-else for 'B').
This is a style that is relaxed about the asymmetry, a style that emphasises this step as merely one temporary step on the path of many steps leading towards something more permanent. A style that recognises that code, by it's nature, is always going to be more general than tests.
But suppose you get hit by a bus tomorrow. How easily could your colleagues tell that this code was work in progress? Code that was not finished?
As an experiment I thought I would try an alternative style. One that is not so relaxed about the asymmetry. One that tries a bit harder to be more explicit about distinguishing code that's a temporary step still on the path from code that has reached its destination.
First I wrote a test that expresses the fact that nothing is implemented.
@Test(expected = ScaffoldingException.class)
public void scaffolding() {
new Diamond('Z').toLines();
}
I make this pass as follows
public class Diamond {
private char widest;
public Diamond(char widest) {
this.widest = widest;
}
public String[] toLines() {
throw new ScaffoldingException("not done");
}
}
The scaffolding, as its name suggests, will have to be taken down once the code is done.
While it remains, it indicates that the code is not done.
Now I start.
I add the diamond_A test (as before) and make it pass
public class Diamond {
...
public String[] toLines() {
if (widest == 'A') {
return new String[]{ "A" };
}
throw new ScaffoldingException("not done");
}
}
I add a second diamond_B test (as before) and make it pass
public class Diamond {
...
public String[] toLines() {
if (widest == 'A')
return new String[] {
"A"
};
if (widest == 'B')
return new String[] {
" A ",
"B B",
" A ",
};
throw new ScaffoldingException("not done");
}
}
The scaffolding is still there.
We haven't finished yet.
Now suppose I refactor the slime (by deliberately duplicating) and end up with this
public class Diamond {
...
public String[] toLines() {
if (widest == 'A') {
String[] inner = innerDiamond();
String[] result = new String[inner.length-1];
int mid = inner.length / 2;
for (int dst=0,src=0; src != inner.length; src++)
if (src != mid)
result[dst++] = inner[src];
return result;
}
if (widest == 'B') {
String[] inner = innerDiamond();
String[] result = new String[inner.length-1];
int mid = inner.length / 2;
for (int dst=0,src=0; src != inner.length; src++)
if (src != mid)
result[dst++] = inner[src];
return result;
}
throw new ScaffoldingException("not done");
}
}
The code inside the two if statements is (deliberately) identical so I refactor to this
public class Diamond {
...
public String[] toLines() {
if (widest == 'A' || widest == 'B') {
String[] inner = innerDiamond();
String[] result = new String[inner.length-1];
int mid = inner.length / 2;
for (int dst=0,src=0; src != inner.length; src++)
if (src != mid)
result[dst++] = inner[src];
return result;
}
throw new ScaffoldingException("not done");
}
}
Now I add a new test for 'C'
@Test
public void diamond_C() {
String[] expected = {
" A ",
" B B ",
"C C",
" B B ",
" A ",
};
String[] actual = new Diamond('C').toLines();
assertArrayEquals(expected, actual);
}
This fails. I make it pass by changing the line
if (widest == 'A' || widest == 'B')
to
if (widest == 'A' || widest == 'B' || widest == 'C')
Now I remove the if completely
public class Diamond {
...
public String[] toLines() {
String[] inner = innerDiamond();
String[] result = new String[inner.length-1];
int mid = inner.length / 2;
for (int dst=0,src=0; src != inner.length; src++)
if (src != mid)
result[dst++] = inner[src];
return result;
throw new ScaffoldingException("not done");
}
}
And it no longer compiles. I have unreachable scaffolding.
Time for the scaffolding to come down.
I delete the throw statement.
public class Diamond {
...
public String[] toLines() {
String[] inner = innerDiamond();
String[] result = new String[inner.length-1];
int mid = inner.length / 2;
for (int dst=0,src=0; src != inner.length; src++)
if (src != mid)
result[dst++] = inner[src];
return result;
}
}
Now the scaffolding() test fails. I delete that too.We're green.
The scaffolding is gone.
We're done.
cyber-dojo in the cloud
http://cyber-dojo.com is now properly hosted in the Amazon cloud :-)
I'll leave the old server, 81.31.112.23 (in my house, under the stairs, with the flaky internet connection - I live in a rural area) up for a few days, but will be gone soon.
A few people have been asking if they could donate something towards running Cyber-Dojo. Now that I'm paying for the hosting that seems a sensible idea, so I've added a donate button. Thanks.
A few people have been asking if they could donate something towards running Cyber-Dojo. Now that I'm paying for the hosting that seems a sensible idea, so I've added a donate button. Thanks.
cyber-dojo Skillsmatter podcast
I had the pleasure of running a Cyber-Dojo at Skillsmatter's
2 day Progressive Java tutorial last week.
The session was video'd and is available
here.
Fun and learning at Ericsson
I had the pleasure of teaching a TDD C++ course at Ericsson's Jorvas centre in Helsinki this week.
As usual I made heavy use of cyber-dojo.
Above is a screen shot of the dashboard of one of the practice katas.
My evals were 5.8 for the course as a whole and 5.9 for teaching skills (out of 6).
Some of the comments were:
- Very clear.
- You really got us understanding TDD.
- Transformation of the mindset became clearly visible during the course.
- Thanks!
- Best course I've taken at Ericsson, thank you.
- The cyberdojo is an excellent environment.
- Very good hands on training.
- cyber-dojo extremely nice.
- It was fun!
- You really need to try to do TDD in practice to see how beneficial and fun it is.
- You made it visible in practice.
- Fun fun fun.
Nicer HTML radio buttons
One of my pet peeves is plain html radio buttons.
If I have a set of 5 radio buttons with 5 text labels of differing lengths then I want to be able to click anywhere on the shaded area and not just the text labels.
This is quite easy to achieve. Simply put each <input> into a <div>...
<div class="radio">
<input type="radio" ... value="Red"/>
<label>Red</label>
</div>
<div class="radio">
<input type="radio" ... value="Amber"/>
<label>Amber</label>
</div>
<div class="radio">
<input type="radio" ... value="Green"/>
<label>Green</label>
</div>
...and use a bit of jQuery
var $j = jQuery.noConflict();
$j(document).ready(function() {
$j('div.Radio').each(function(n,node) {
$j(node).click(function() {
$j(this).children(':first').attr('checked', true);
});
});
});
Using a <div> changes the layout - so you might want to put
each <div> into a table layout.
You can add some CSS to highlight the clickable whitespace...
.Radio { background-color: Moccasin; }
.Radio::after { content: "\00a0"; }
If you want to remove the bullet simply add the following
$j(document).ready(function() {
$j('input[type=radio]').hide();
};
If you want to see an example of this, just try
CyberDojo
Bare bones ruby unit testing
This morning I spent a happy hour exploring a little of ruby's Test::Unit::TestCase.
I started with this:
require 'test/unit'
class MyTest < Test::Unit::TestCase
def test_one_plus_one_equals_two
assert_equal 2, 1+1.1
end
end
I wanted to see how little I needed to write my own,
super-minimal implementation of Test::Unit::TestCase...
require 'test/unit'
class MyTest < MyTestCase
def test_one_plus_one_equals_two
assert_equal 2, 1+1.1
end
end
After 204 traffic lights in cyber-dojo I ended up with this...
require 'assertion_failed_error'
class MyTestCase
def self.test_names
public_instance_methods.select{|name| name =~ /^test_/}
end
def assert_equal( expected, actual )
message =
"#{expected.inspect} expected but was\n" +
"#{actual.inspect}\n"
assert_block(message) { expected == actual }
end
def assert_block( message )
if (! yield)
raise AssertionFailedError.new(message.to_s)
end
end
end
at_exit do
::ObjectSpace.each_object(Class) do |klass|
if (klass < MyTestCase)
klass.test_names.each do |method_name|
begin
klass.new.send method_name
rescue AssertionFailedError => error
print "#{klass.name}:#{method_name}:\n" +
"#{error.message}"
end
end
end
end
end
class AssertionFailedError < RuntimeError; endwhich allowed me write...
require 'my_test_case'
class MyTest < MyTestCase
def test_one_plus_one_equals_two
assert_equal 2, 1+1.1
end
end
and finally, I added this...
class MyTestCase
...
def self.test( name, &block )
define_method("test_#{name}".to_sym, &block)
end
...
end
which allowed me to rewrite the test as...
require 'my_test_case'
class MyTest < MyTestCase
test "1+1 == 2" do
assert_equal 2, 1+1.1
end
end
Fun :-)
Agilis Deliberate Practice
Here's the
slide-deck
I presented at the Agilis conference in Iceland.
It contains numerous examples of the kind of improvements a group of developers typically work through in just a few
facilitated CyberDojo iterations.
Agile A-Z Keynote
I attended the excellent Agile .NET 2011 conference in Ghent, Belgium this week. Jason Gorman pulled out at the last minute and Erik asked me if I'd step in and do the keynote. I said yes of course and prepared this on the train+plane there.
An Agile A to Z
View more presentations from JonJagger.
More CyberDojos
I ran a CyberDojo at the excellent Tampere goes Agile conference recently (left photo). It got a 100% green card vote.
A few days later I ran another CyberDojo for the devs at Solita Oy, also in Tampere. A nicer bunch of people you couldn't ask to meet.
And a few days after that I ran yet another CyberDojo at the Ericsson Agile conference in Helsinki (right photo). @jussikm @htaubert and @karmolis tweeted that it was the most fun they'd had at work in 2011.
Subscribe to:
Posts (Atom)


