开发者

Unit Testing - not testable code converted to testable code

I have read so many places is that if your code is not test-able that mean code is not well written. So that makes me start writing a code that is test-able and to start using some unit testing framework.

With this though I start looking for some example with piece of 开发者_开发问答code that is not testable and gradually converted to a testable code. I find tons of examples on unit testing but if someone can provide an example like above it probably can jump start things for me.

TIA


Here are two great books that will help you get started:

  1. The Art of Unit Testing
  2. Working Effectively with Legacy Code

Good luck.


Put a bunch of code in a button click event and try to unit test it. It's not impossible, but will either be non-trivial or require some copy-paste finagling to get it done.

protected void buttonClick(object sender, EventArgs e)
{
    string currUser =
        User.Identity.Name.ToString().Trim()
            .Substring(User.Identity.Name.ToString().Trim()
            .IndexOf("\\") + 1);

    Inventory.Employee.DB objEmpDB = new Inventory.Employee.DB();
    Inventory.Employee.Details objEmpDetails = 
        new Inventory.Employee.Details();

    objEmpDetails = objEmpDB.Get(currUser);

    Welcome.Text = 
        "Current User: " + objEmpDetails.Employee_Full_Name;

    var objUserDetails = new Inventory.User.Details();
    Inventory.User.DB objUserDB = new Inventory.User.DB();

    if (objUserDB.UserAuthenticates(currUser))
    {
        objUserDetails = objUserDB.Get(currUser);
        currUserToken = objUserDetails.User_Token.Value;

        userID.Text = currUser;

        if (objUserDetails.Active_User_Name != objUserDetails.User_Name)
        {
            lShadow.Text = "Showin: " + objUserDetails.Active_User_Name;
            lServer.Text = "(" +
            objUserDB.UserPermissionName(objUserDetails.Active_Logon_Name)
                + ") - " + System.Environment.MachineName;
            lShadow.ToolTip = Inventory.Properties.Settings.Default
                .connectionString.Substring(0, Inventory.Properties
                .Settings.Default.connectionString.IndexOf(';'));
            divShadow.Visible = true;
        }
        else
            divShadow.Visible = false;

        lWelcome.Text = "Current User: " + objUserDetails.User_Name;
    }
}

Not only is this hard because of the difficulty of emulating a user button click, but look how much is going on in that button click. If your unit test fails, there's about 100 freakin things that could have gone wrong. DRY, single concern, and other design principles lead to code that's easy to test and easy to fix. After all, what good is a unit test if you are testing brigades rather than units :)

UPDATE: (How to fix the above code)
I'm not going to pretend that the code above is an easy fix. That's a "small" sample from a code base I've worked on in the past. I wanted to show how bad things can get in real life.

There's two major problems with the code.

  1. Its hard to test button click events.
  2. There's too much going on in one method.

Its easy to fix the Event driven/reproducing a button click event problem. You can wrap all that code into another method:

protected void buttonClick(object sender, EventArgs e)
{
   EasyToCallMethod();
}

public void EasyToCallMethod()
{
    string currUser =
        User.Identity.Name.ToString().Trim()
        .Substring(User.Identity.Name.ToString().Trim().IndexOf("\\") + 1);
    //...rest of code
}

Now its easy to call from a unit test. But, that's a little silly because it really doesn't solve the second problem.

Easy Fix
So there's a good 15-20 tests that we can make out of this one method call. Just make a test for each line that has a specific purpose (like where method calls are made) and you should have good unit tests that are small enough to tell where something broke and good code coverage.
Advanced stuff
Much more work can be done. We can implement n-tier MVC or MVVM . At some point, you have to ask yourself if you are over-engineering. Unit tests should make your code more maintainable, but don't over-abstract yourself into nothingness. This is where your own style and experience come into play. When you feel like you've got the basics you should come back to SO with new questions or pickup a good book.


I don't agree completely. For example, let's assume you have a program which executes things based on a timer. If you wanted to test it in a deterministic way, you'd have to change and pause the system clock. There's tons of literature about deterministic testing of timed stuff. So everything is testable, the question is, how easily. And it doesn't depend on how well the code is written, it depends on the actual task that the program does and how it's designed, not implemented. Even with good designs, you can get code that is hard to test.


The most important key factor to make code more (unit-) testable is dependency injection.

The first chapter of Mark Seemann's book

Dependency Injection in .NET

is freely available here http://www.manning.com/seemann/ as a PDF file. It contains a very complete example how to make a piece of tightly coupled code more testable by reducing the dependencies from lower layers.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜