.NET to Ruby: Learning How to Write Tests, Part I

Tweet

If you’re a .NET developer who have been writing tests, this post may encourage you to continue doing so when working with Ruby. If, instead, you have not been writing tests, we must change that! I know I have mentioned this before, but I can’t stress enough how important it is.

On my first years as a C# developer I relied too much on the compiler. I had the idea that if the compiler didn’t complain, then my code was correct. Back then I was “writing code that satisfies the compiler”, as opposed to “building applications that satisfy my clients”. So how did I transition from one to the other? I can assure you it hasn’t happened over night, as much as I can assure you I’m very glad every time I see my bacon saved by tests I have in place.

I’ll release this post in two parts: Part 1 covers my testing experiences in .NET, while part 2 covers my experiences in Ruby. Even though all of the code in part 1 is written in C#, certain parts look very Ruby-esque, as you’ll probably realize when you get to part 2.

Starting with Simple Assertions

My first experience with tests was with writing what I thought to be “unit tests”. At the time I was really just writing some assertions to make sure certain methods on objects returned what I expected. Something along these lines:

[TestClass]
public class CalculatorTests
{
    [TestMethod]
    public void Test1()
    {
        var calculator = new Calculator();
        var result = calculator.Sum(1, 1);
        Assert.AreEqual(2, result);
    }

   [TestMethod]
   public void Test2()
   {
       var calculator = new Calculator();
       var result = calculator.Subtract(5, 3);
       Assert.AreEqual(2, result);
   }

}

All I knew is that those tests were a lot better than running the application, waiting for the login screen to come up, type username and password, wait for authentication to finish, wait for menus to come up, selected some menu item, jump through UI controls until I find whatever it is I wanted to test manually. It was either that, or creating Console applications (or other types of “test benches”) to exercise my code.

At the time, I didn’t organize my test code too well: I used to name methods like Test1 and Test2, which didn’t really tell me what was being tested. There was always redundancy in the test code, with the same class under test being instantiated over and over again.

Taking Better Care of My Test Code

After spending sometime writing lots of tests like that, I noticed the code was messy and getting out of hand when time came for me to maintain it. I realized I should write better test code. I started to apply refactoring techniques to my test code: introduce variables or constants and eliminate magic values, extracting redundant code into separate methods, naming methods after their intent, etc.

Based on the work other people were doing, I began to name my test method after the requirement it was testing:

[TestMethod]
public void Sum_should_return_the_two_given_numbers_added_together()
{
     ...
}

I liked that because if the test failed, the error message would contain the test method that failed, and I’d quickly see what requirement was broken.

When C# 3.0 introduced extension methods, I followed the cool kids and created my “test extensions”, with assertions so that my tests would look like so:

[TestMethod]
public void Sum_should_return_the_two_given_numbers_added_togeter()
{
    var calculator = new Calculator();
    var result = calculator.Sum(1, 1);
    result.ShouldBe(2);
}

You see, like many others, reading something in code like Assert.AreEqual(2, result) seems counterintuitive; it almost reads like “assert are equal to result”. Of course, as coders we tend to parse code like that and make some sense out of it. Honestly, I rather not have to do that mental parsing, and just read code like this: result.ShouldBe(2). Dude, just let me use my tiny brain for something else.

By the way, in case you didn’t get into extension methods yet, this is how that ShouldBe method can be created:

public static class TestExtensions
{
    public static void ShouldBe(this object expected, object actual)
    {
        Assert.AreEqual(expected, actual);
    }
}

Once the test code looked clean, my test method names started to somewhat reflect requirements, and my test code could be read without a lot of mental parsing, I started to use it in place of documentation: if I needed to remember how certain parts of the code was supposed to be used, I’d read the tests.

The Jump to Test-Driven Development

Several months went by and I heard people talking about Test-Driven Development (TDD). The idea of writing tests before writing code was totally alien to me. I had to force myself into trying it, even at times when it didn’t seem to make sense. The practice grew on me, and I could see why people liked it.

Writing code is easy: we can use code generators, or micro code generators (such as CodeRush or Resharper), or code snippets, or master Intellisense, or just type really fast. However, I ended up writing a lot more code than it was actually necessary. You know, “I’m going to put this code here because the user may need that at some point, or the business may require that, or maybe this, maybe that, maybe the other”. Lots of assumptions that many times never become true, in applications that never saw the light of the day.

Practicing TDD I started thinking about “the code I wanted to have” as I was writing the tests. I’d learn to think more carefully as to how I’d use the objects I was about to implement, or more importantly, whether it’d make sense to other developers trying to use it. I’d ask my peers to take a look at the tests and give me feedback before I spent anytime implementing the classes. I’d think more carefully about parameters getting passed into methods: should I pass in a decimal or string, or should I pass in a more explicit type, such as Money or SSN?

Knowing what my objects should do, I started to think more about only writing enough code to make the tests pass. No more, no less. “But what if/if/but/maybe”. I don’t care. If I have more questions than certainties, I won’t just get creative and write whatever code I can make up. This code will most certainly get thrown away or, worst still, pollute the codebase when nobody needs it.

Next Step: Focusing on the Behavior

My first steps in Behavior Driven Development (BDD) focused on the behavior of certain objects or components of my applications. After going back and forth between “Given-When-Then” and “Context-Specification” test frameworks, the one that really worked well for me was SubSpec, where I could write tests like so:

[Specification]
public void dynamic_properties()
{
    "Given additional dynamic data for a customer"
        .Context(() => make_data_for_a_customer());
    "When constructing a customer object"
        .Do(() => { _customer = GetCustomer(); });
    "Then the object should expose the additional data as properties"
        .Assert(properties_are_exposed);
    " And getters are available"
        .Assert(getters_are_exposed);
    " And setters are available"
        .Assert(setters_are_exposed);
}

That was still just C#, but I’d start with writing requirements just using plain English sentences in the form of “Given some context, When certain thing happens, Then some expectation should be met”. It may sound like a small thing, but it isn’t: instead of thinking in C#, I was thinking in English. Once I had a clear idea as to how I could explain what my code was supposed to do, it was much easier to write what the code should look like.

Was it Worth the Trouble?

Trouble? What trouble? I can tell you that going through all of these iterations of improving how I write my tests has made me a much better developer:

  • My C# skills improved: I learned a lot about lambdas, extension methods, generics, and a bunch other things in the language;
  • My OOP skills improved: I learned about the SOLID principles, how to write better objects, methods, abstractions, etc.;
  • My English skills improved: if I’m dropping English sentences in my code, they have to be short and to the point. I honestly think that if I can’t communicate clearly in English, my coding is also going to suffer and my code is going to be inexpressive.
  • My communication skills improved: if I can’t communicate my thoughts and understanding of the work I need to do, it’s very likely that I’ll be building an application that no user is going to want to use. With better communication skills, I have much better chance to only spend time building what the client really needs.

So, no trouble? Alright, there was some trouble. It ranges from using the wrong tools, which makes writing tests a hard task, to using frameworks or components that are just not test-friendly (we can’t test our code because it uses some class that is untestable; at least not in an easy way).

There is also a cultural problem: people seem to have trouble understanding why a developer would write a test. Apparently, it is OK to spend hours on a debugger, but it is not OK to spend time writing tests. Do clients really care whether a developer has written an if-block, a switch-block, has written a test, or has spent time on the debugger?

Ok, I digress…

Back to your question: Yes, as far as I’m concerned, it was more than worth any trouble. In part 2 of this series, I go over how working with tests has helped me get up and running with Ruby relatively quickly, as well as how some of the tools in Ruby have allowed me to improve the way I develop software. See you there…

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Greg Lawrence

    Thanks for sharing! Looking forward to seeing part 2.

    • http://www.lassala.net Claudio Lassala

      You’re welcome! Good thing is that Part 2 is written already, so we should see it up in a couple of days. :)