Behaviour driven design using rspec IronRuby and C#
about 15 years ago
Get a copy of IronRuby
You can download the latest IronRuby release from: IronRuby.net / Get IronRuby
Get yourself a mini IronRuby command line environment set up
- Decompress the Zip file somewhere
- Create batch files to start a command prompt with all the IronRuby paths. Place them in the bin directory (where iirb.exe lives):
bin/ruby-prompt.bat
%comspec% /k %cd%\settings.bat
bin/settings.bat
set PATH=%CD%;%cd%\..\lib\IronRuby\gems\1.8\bin;%PATH%
Great! Now when you click on ruby-prompt.bat you will get a command prompt that can exec iirb, igem and the rest of the IronRuby commands.
Get rspec
(in your IronRuby command prompt run)
igem install rspec
Write your tests
In this example I am testing an “LRU Cache”:Cache replacement policies - Wikipedia
require File.dirname(__FILE__) + '\..\LRUCache\bin\Debug\LRUCache.dll'
include System::Collections::Generic
include System
# mostly by the sadly missing _why
class Object
def metaclass; class << self; self; end; end
def meta_eval &blk; metaclass.instance_eval &blk; end
def meta_alias(new, old)
meta_eval { alias_method new, old}
end
def meta_def(name, &blk)
meta_eval { define_method name, &blk }
end
end
describe "LRUCache" do
def create_cache(capacity)
cache = LRUCache::LRUCache.of(System::String,System::String).new(capacity)
[
[:try_get, :TryGetValue],
[:contains_key?, :ContainsKey],
[:add, :Add],
[:remove, :Remove],
[:count, :Count]
].each { |new,old| cache.meta_alias(new, old) }
cache.meta_def(:replay) do |array|
array.each do |key,val|
if val.nil?; cache[key]; else; cache[key] = val; end
end
cache
end
cache
end
it "should never exceed its capacity" do
cache = create_cache(10)
(11).times { |i|
cache[i.to_s] = "data"
}
cache.count.should == 10
end
it "should throw an exception if an item is accessed via the index "\
"and its not there" do
cache = create_cache(10)
lambda { cache["bla"] }.should raise_error(KeyNotFoundException)
end
it "should expire stuff that was not recently used, when capacity is reached" do
cache = create_cache(3)
cache.replay(
[
["a","aa"],
["b","bb"],
["c","cc"],
"a",
"b",
["d","dd"]
]
)
cache.contains_key?("c").should == false
["a","b","d"].each{|key| cache.contains_key?(key).should be_true }
end
it "should increase the count when stuff is added" do
cache = create_cache(3)
lambda { cache.add("a","aa") }.should change(cache, :count).by(1)
end
it "should decrease the count when stuff is removed" do
cache = create_cache(3)
cache.add("a", "aa")
lambda { cache.remove("a") }.should change(cache, :count).by(-1)
end
it "should throw if a cache is initialized with 0 capacity" do
lambda { create_cache(0) }.should raise_error(ArgumentException)
end
it "should allow us to enumerate through the items" do
input = [["a","aa"],["b","bb"], ["c","cc"]]
cache = create_cache(3).replay(input)
data = []
cache.each do |pair|
data << [pair.Key, pair.Value]
end
data.should == input
end
describe "(try get)" do
before :each do
@cache = create_cache(3)
end
it "should support missing items" do
found, value = @cache.try_get("a")
found.should == false
end
it "should support existing items" do
@cache["a"] = "aa"
found, value = @cache.try_get("a")
found.should == true
value.should == "aa"
end
end
end
Write the C# classes to power the LRUCache
IndexedLinkedList.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace LRUCache {
public class IndexedLinkedList<T> {
LinkedList<T> data = new LinkedList<T>();
Dictionary<T, LinkedListNode<T>> index = new Dictionary<T, LinkedListNode<T>>();
public void Add(T value) {
index[value] = data.AddLast(value);
}
public void RemoveFirst() {
index.Remove(data.First.Value);
data.RemoveFirst();
}
public void Remove(T value) {
LinkedListNode<T> node;
if (index.TryGetValue(value, out node)) {
data.Remove(node);
index.Remove(value);
}
}
public int Count {
get {
return data.Count;
}
}
public void Clear() {
data.Clear();
index.Clear();
}
public T First {
get {
return data.First.Value;
}
}
}
}</code></pre>
LRUCache.cs
<pre><code>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace LRUCache {
public class LRUCache<TKey, TValue> : IDictionary<TKey, TValue> {
Dictionary<TKey, TValue> data;
IndexedLinkedList<TKey> lruList = new IndexedLinkedList<TKey>();
ICollection<KeyValuePair<TKey, TValue>> dataAsCollection;
int capacity;
public LRUCache(int capacity) {
if (capacity <= 0) {
throw new ArgumentException("capacity should always be bigger than 0");
}
data = new Dictionary<TKey, TValue>(capacity);
dataAsCollection = data;
this.capacity = capacity;
}
public void Add(TKey key, TValue value) {
if (!ContainsKey(key)) {
this[key] = value;
} else {
throw new ArgumentException("An attempt was made to insert a duplicate key in the cache.");
}
}
public bool ContainsKey(TKey key) {
return data.ContainsKey(key);
}
public ICollection<TKey> Keys {
get {
return data.Keys;
}
}
public bool Remove(TKey key) {
bool existed = data.Remove(key);
lruList.Remove(key);
return existed;
}
public bool TryGetValue(TKey key, out TValue value) {
return data.TryGetValue(key, out value);
}
public ICollection<TValue> Values {
get { return data.Values; }
}
public TValue this[TKey key] {
get {
var value = data[key];
lruList.Remove(key);
lruList.Add(key);
return value;
}
set {
data[key] = value;
lruList.Remove(key);
lruList.Add(key);
if (data.Count > capacity) {
Remove(lruList.First);
lruList.RemoveFirst();
}
}
}
public void Add(KeyValuePair<TKey, TValue> item) {
Add(item.Key, item.Value);
}
public void Clear() {
data.Clear();
lruList.Clear();
}
public bool Contains(KeyValuePair<TKey, TValue> item) {
return dataAsCollection.Contains(item);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
dataAsCollection.CopyTo(array, arrayIndex);
}
public int Count {
get { return data.Count; }
}
public bool IsReadOnly {
get { return false; }
}
public bool Remove(KeyValuePair<TKey, TValue> item) {
bool removed = dataAsCollection.Remove(item);
if (removed) {
lruList.Remove(item.Key);
}
return removed;
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
return dataAsCollection.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
return ((System.Collections.IEnumerable)data).GetEnumerator();
}
}
}
Run your tests:
C:\Users\sam\Desktop\Source\LRUCache\spec>spec lru_spec.rb ......... Finished in 0.3340191 seconds 9 examples, 0 failures
Yay, we have a working rspec test suite.
I know, there is a lot to chew on here, in future posts I will try to explain some of the trickery that is going on.
Are you trying to teach how to implement the cache, or introduce people to RSpec? If you're trying to introduce people to RSpec, you might consider using an example that isn't too geeky, as the example will distract from the attention that you're trying to call to RSpec.