Design And Testability


In the line of business applications that I build, it’s considered good practice to use a test-first approach; Test-Driven Development, Behavior-Driven Development, or whatever you want to call it. Write a test, verify that it fails for the right reasons, make it pass, refactor the code to ensure it’s up to all required standards. How a person goes about doing the implementation of the tests and the code to fulfill the tests depends largely on the platform, language and testing tools used. Each platform has different needs and different ways of approaching the idea of “testability” in code. Some languages require specific design decisions to enable testable code, while other languages pretty much guarantee that your code will be testable – even if some designs are easier to test than others.

 

Design And Testability In Ruby

If I were writing some code in Ruby, I could easily test this:

   1: class Foo

   2:   def bar

   3:     baz = Baz.new

   4:     baz.do_something

   5:   end

   6: end

</div> </div>

 

There are a number of options for being able to test the behavior of the Foo class’s .bar method. I could use the nature of Ruby’s open-type system and just replace the initializer and do_something method on the Baz class; I could use RSpec and it’s built in mocking syntax; I could use the not-a-mock gem (which is my preference) to stub the methods; etc.

We essentially get testability in ruby for “free” – it’s built into the dynamic nature of the language. Other dynamic languages such as Python, etc, also give us testable code by nature of the language. We are not required to do anything special to create code that is “testable”. Now that doesn’t mean all code is easily tested, though. There are still design principles and paradigms that will make your code easier to test, which also tends to lead to code that is easier to understand. The point, though, is that you don’t have to do anything special to isolate the behavior of the Foo class from the implementation of the Baz class in the above example.

 

Design And Testability In C#

Looking at the equivalent code in C#, we would say that this code is not “testable” from the perspective of unit tests:

   1: public class Foo

   2: {

   3:     public void Bar()

   4:     {

   5:         var Baz = new Baz();

   6:         Baz.DoSomething();

   7:     }

   8: }

</div> </div>

By all the principles, practices, and design standards that we preach in C# / .NET, this code is not testable because of the hard dependency on the Baz object and it’s implementation.

There are a significant number of principles that are being violated in these few lines of executable behavior, and we would need to change the code in a very significant way to create something that is “testable”. We would need to introduce an abstraction over Baz – but ensure that the Foo class owns the abstraction so we don’t violate the Dependency Inversion principle. And we would need to introduce Inversion Of Control and some form of Dependency Injection to ensure that Foo is not directly dependent on Baz’s implementation (neither the constructor nor the DoSomething method’s implementation). The resulting code, to be “testable” by all accounts, would look something like this:

   1: public class Foo

   2: {

   3:   private doSomething;

   4:   

   5:   public Foo(IDoSomething doSomething)

   6:   {

   7:     this.doSomething = doSomething;

   8:   }

   9:   

  10:   public void Bar()

  11:   {

  12:     doSomething.DoSomething();

  13:   }

  14: }

  15:  

  16: public interface IDoSomething

  17: {

  18:     void DoSomething();

  19: }

  20:  

  21: public class Baz: IDoSomething

  22: {

  23:     public void DoSomething()

  24:     {

  25:       // ... whatever this does...

  26:     }

  27: }

</div> </div>

(Note: I included the shell of the implementation for Baz in this example – but those extra few lines of code don’t diminish the expansion of the rest of the code. I included it to show the requirement of implementing the interface on the Baz class.)

 

Angels And Demons

As a person who dabbles in ruby and that community, I get the sense that we applaud Matz for the open nature of ruby, allowing the great minds of people like David Chelimsky to develop tools like RSpec with it’s built in mocking capabilities. We have the freedom to express the intent of our code without the significant ceremony of the abstraction, dependency inversion, and “testable” code the we say is required in C#. These people are the heroes – the angels – of the ruby community, held in high esteem because they have made the art of “testable” code approachable by anyone that can write code. And they deserve our applause for these efforts, without question. The tools and capabilities in Ruby and RSpec are quite wonderful and I enjoy working with them.

Why, then, do we demonize companies with tools like Telerik’s JustMock, Typemock’s various offerings, and Microsoft “Pex and Moles” for providing the same capabilities in C# / .NET? Why do we attack people like Roy Osherove and dismiss his contributions to the community? Have we become so dogmatic about our “principles” and “standards” that we no longer have a sense of pragmatism or exploration and questioning? Has the “alt.net” community become “dogma.net”, “elitist.net”, or “hate.net” as so many others have suggested, for so many years? What value do we truly gain – other than the admiration and awe of the people that wish they were “smart enough” to point out the “flaws” – through this continuous disregard for what is a valid perspective and approach to software development in .NET?

(Edit: the above content create a whirlwind of comments that would have been better off on another communication channel. I should not have taken the tone and stance that I did with this section. The LosTechies community should not be a place where I rant and say these types of incendiary things. As such, I’ve decided to moderate the comments from this post and strike out the above section. Please do not comment on this section, on this blog anymore. I’ll remove the comments. Please continue commenting on the rest of the post, though, as I believe it is still valid.)

 

What’s The Point?

I honestly ask – why? … or, why not? If I can write this test in rspec:

   1: Baz.should_receive(:do_something)

</div> </div>

or write this test in typemock:

   1: var fake = Isolate.Fake.Instance<Baz>();

   2: Isolate.Swap.NextInstance<Baz>().With(Fake);

   3: //... run the foo.Bar method, here

   4: Isolate.Verify.WasCalledWithAnyArguments(() => fake.DoSomething());

</div> </div>

why shouldn’t I write that one in typemock? Why should we applaud the ruby community for it’s contributions and not the .NET community that has given us the same core capabilities. Is it because the capabilities to do this are not “free” in a static language? Is it because we’re afraid of the profiling API that is required to do this in .NET? Is it because we’ve become dogmatic instead of pragmatic? Is it because TypeMock is expensive? or is there a legitimate reason that we have emotional reactions and cry-foul the possibilities that these tools introduce?

 

Searching …

I don’t know the answers. I’m asking because I want to find the answers. And yes, I recognized that I still have an attachment to the abstractions and interfaces. I’m not gong to go spend the $ on TypeMock or JustMock today, but at least I’m asking the question in an open and honest manner. I hope the rest of the .NET community will join in and begin to question everything we hold sacred. We might actually learn something if we do. </p>

Knowing when to ask