7

I have a class that is responsible for performing conversions from/to twenty-something types. Let's call this class ConvertUtils.

For C# programmers out there - this class expands .Net's Convert class.

It looks something like this:

public static class ConvertUtils {
    public static object ChangeType(object obj, Type newType) {
        if (conversionSupportedByFramework)
            return Convert.ChangeType(obj, newType);
        else
            ConvertSpecialCases(obj, newType);
    }
}

How would you go about testing it? I'm assuming we do need to black-box test this code.

The two ways we thought about, are:

  1. Writing 400+ unit tests - cover all to/from combinations that we can think of.
  2. Write tests only for the new conversions - the ones not supported by the Convert class - actually testing only the ConvertSpecialCases() function [which isn't our goal, as stated above]

The con of the first possibility is to have too many tests - which prolongs the build time, involves maintaining more code, etc.

The con of the second possibility is to not fully check the responsibility of the class - what if a certain if statement decides to implement (wrong) custom logic, instead of letting Convert do it's job? e.g. just before if (conversionSupportedByFramework) someone decides to call some custom logic?

What do you think about this issue?

3
  • 1
    Start with the most important cases, add one every time a bug is fixed or a feature added, and if stability is a priority (which is a business decision, not a technical one) schedule some additional time solely for boosting the test coverage.
    – Ixrec
    Jun 23, 2015 at 20:46
  • 1
    For a non-.NET-programmer: could you perhaps explain how these conversions work? E.g. if each conversion method has 20 cases, then yes you do need on the order of 20 test cases per method. If they use some common internal format instead, then you only need to test conversion to and from the common format for each method.
    – amon
    Jun 23, 2015 at 20:52
  • 1
    @Amon AFAIK, there is not "mediator object" that I can turn my input to, before calling Convert. Source Code shows that there is indeed a large switch-case inside DotNet's code, if that's what you are asking.
    – Berlo
    Jun 25, 2015 at 3:32

4 Answers 4

4

You don't have to test .NET Framework's code in your specific case, because:

  • You can't inherit from Convert class, since this class is static; even if you could do that, for example if Convert class weren't static:

  • The Convert class has no virtual methods,

  • There are no instance methods,

  • There are no abstract methods,

  • You cannot override static methods in a class,

  • You shouldn't hide methods by using new keyword anyway (and if you do, Code analysis will shout at you).

The only way you may be able to screw with existent functionality is through Reflection. If you use Reflection in order to play with the internals of the Convert class, then yes, you have to study and test the impact it may have.

Otherwise, you just have to test the new code you'll write in your custom class.

4
  • The OP wrote "tests [...] for the ones not supported by the 'Convert' class" - that is actually a bit unclear and could mean different things, but I do not believe he meant to write tests directly for the functions of the Convert class.
    – Doc Brown
    Jun 24, 2015 at 18:30
  • @MainMa: I don't plan on inheriting from Convert, but containing it instead. My ConvertUtils class has it's own 'ChangeType' method, that should probably delegate all Convert's supported conversions to DotNet's Convert class. Can I rely on that, or do I need to test this anyway? What happens if someone changes the code of ConvertUtils so that it does not call Convert.ChangeType anymore but rather performs its own logic?
    – Berlo
    Jun 25, 2015 at 3:23
  • @Berlo: this is classically white box testing vs. black box testing. With unit tests, you have the visibility over the tested code, so you know what is delegated to .NET Framework specifically. This means that if you need to protect yourself against the regression in which someone replaces the call to Convert by a custom (and eventually wrongly implemented) method, you need only one test—the test which ensures that the delegation is in place. You can do that through Dependency Injection by replacing Convert by a mock and checking that the mock is actually called. Jun 25, 2015 at 6:38
  • @MainMa: Thank you, your last comment actually lets me have my cake and eat it too - I can both black-box test the class, and only write one unit test that covers the "Regular" cases. That's why I will mark your answer.
    – Berlo
    Jun 26, 2015 at 8:16
4

This depends on how your conversion class looks like. If it looks like this:

  class MyConvert
  {
       public static Foo1 ToFoo1(Bar1 bar){...}
       public static Foo2 ToFoo2(Bar1 bar){...}
       public static Foo3 ToFoo3(Bar1 bar){...}
       // ...
       public static Foo1 ToFoo1(Bar2 bar){...}
       public static Foo2 ToFoo2(Bar2 bar){...}
       // ...
       public static Foo20 ToFoo20(Bar20 bar){...}
  }

you should implement at least one test (better two or three) for each method. And if you have 400 methods, you need a small multiple of 400 tests.

If, however, your class looks different, and you have something like an "internal representation class" for all of your Bar objects, you can reduce the number of tests. Assume something like this:

  class MyConvert
  {
       public static Foo1 ToFoo1(object bar)
       {
           MyInternalObj myObj = new MyInternalObj(bar);
           return myObj.ToFoo1();
       }
       public static Foo2 ToFoo2(object bar)
       {
           MyInternalObj myObj = new MyInternalObj(bar);
           return myObj.ToFoo2();
       }
      // ...
  }

you will only have to write tests for the constructor of MyInternalObj for all allowed type of objects (at least 20), and additional tests for the methods ToFoo1, ToFoo2,..., of MyInternalObj (also at least 20, or a small multiple of those).

Knowing your implementation looks like this, but you do not want to access MyInternalObj directly, you can also apply the same tests by calling MyConvert.ToFoo1 with 20 different Bar types, and MyConvert.ToFooXYZ in all 20 variations using the same "Bar" type.

Or more general: make sure you get a good code and branch coverage of all the code you have written.

2
  • If I understood your answer correctly, you recommend testing all possible combinations? DotNet's Convert can convert from a float to an enum, and my own ConvertUtils should probably use that built-in functionality. Should I have a test that try the conversion from float to enum, or not?
    – Berlo
    Jun 25, 2015 at 3:19
  • @Berlo: No, I recommend not to test "all possible combinations" (400 in my example above) as long as you do not have implemented 400 different conversion functions. And concerning the DotNet's Convert class: why do you offer conversion from float to enum in your ConvertUtil if Convert already has such a conversion? So why is there actually something to test? Without having a notion of what you implemented, its hard to give you a good advice, you should probably edit your question and add an example. Do not play the guessing game with us.
    – Doc Brown
    Jun 25, 2015 at 5:23
2

The fact that your class is a conversion class, the fact that it is static, and the fact that it expands an existing framework class are all irrelevant. This question is an instance of the more general question of black-box vs. white-box testing.

In lack of any very good reason to perform white-box testing, all testing should be black-box testing.

This means that your testing code should not be making any assumptions about the internals of the system-under-test.

This in turn means that your testing code should not assume that your conversion class delegates to some other conversion class. (Precisely because it may delegate, and then again who knows, it might not delegate.) So, testing the entire public interface of your conversion class is the way to go.

That having been said, let me add that since you are writing a static class which expands on the functionality of another static class, the way to reduce your testing code is by expressly refraining from duplicating the functionality of the existing framework class. If you want one of the conversions provided by the framework class, invoke the framework class. If you want one of the conversions provided by your class, invoke your class. There is no reason to wrap the functionality of a static framework class inside a static custom class, there is not even any real wrapping going on, only pretending, because the functionality of the framework class is still publicly available to any piece of code that wishes to invoke it.

1
  • 1
    Your point about making sure that I only call 'ConvertUtils' in places I actually need it, is interesting. I will look into that, thanks.
    – Berlo
    Jun 26, 2015 at 8:18
0

Firstly, determine whether your changes will impact other public methods from the Convert class. If they will, find out which ones are impacted. Now you are better equipped to make a decision.

In case there is no impact in other public methods and functionalities, then only focus on new things you add or the ones you change.

If the change affects other areas of the Converter class, start testing what you have added or changed, then test methods your modifications have impacted.

Another advice is, if your changes affect existing features, when testing existing features give priority to the ones you are already using. Probably they will not be a big number.

Something else to consider is the following. For methods that have not changed, use the original Converter class. Use your new one only for the changing elements. When you are sure that your new Converter class is working, then make all modifications. Even if you use a lot of these Converter methods, making the change will not be too hard because the method signatures will not change and since you are confident the new Converter is working, your software should have no impact.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.