views:

263

answers:

5

Consider:

class MyClass<T> where T : class
{
}

In that case, the where clause is enforcing a specification that MyClass is only a generic of a reference type.

Ideally I should have a unit test that tests this specification. However, this unit test obviously won't work, but it explains what I'm trying to accomplish:

[Test]
[DoesNotCompile()]
public void T_must_be_a_reference_type()
{
    var test = new MyClass<int>();
}

What can I do to test a spec that's implemented by not allowing the code to compile?

EDIT:

More info: Ok, so my reasoning for doing this (haha) is that I've been following a TDD methodology, in which you can't write any code unless you have a failing unit test. Let's say you had this:

class MyClass<T> { }

What test can you write that would fail unless T were a class? Something like default(T) == null?

Further EDIT:

So after a "root cause analysis" on this, the problem is that I was relying on default(T) being null in a consumer of this class, in an implicit way. I was able to refactor that consumer code into another class, and specify a generic type restriction there (restricting it to class) which effectively makes that code not compile if someone were to remove the restriction on the class I'm talking about above.

+8  A: 

You shouldn't test whether the compiler does its work. If you specify this in code, this is enough. From a code perspective, this is roughly the same as this:

[Test]
public void Test_whether_add_works()
{
    int i = 1 + 2;

    Assert.AreEqual(3, i);
}
Pieter
'[Test] public void Running1984() { Assert.AreEqual(5, 2 + 2); }'
Dan Bryant
Hihi, Radiohead.
Pieter
+2  A: 

Are you writing a correct unit test? It looks like your a going to test C# compiler but not your code.

Andrew Bezzub
+23  A: 

Why would you need a unit test for this? Do you write a unit test for a method such as

public void Foo(string x)

to check that it can only take strings, and not integers? If not, what do you see as the difference?

EDIT: Just to be slightly less whimsical: in this case the spec is validated by the declaration. Tests should generally test behaviour. That's one of the things I like about Code Contracts: I don't feel any need to unit test the contracts unless they express something complicated - in which case it's that complexity that I'd be testing, not the "contracts are enforced" bit.

EDIT: To respond to the question edit:

What test can you write that would fail unless T were a class?

You could write something like:

Type definition = typeof(MyClass<>);
Assert.Throws<ArgumentException>(() => definition.MakeGenericType(typeof(int)));

However, that seems to be going against the real purpose of testing...

Jon Skeet
Well, I'm trying to make my unit tests include all specs for the unit under test.
Scott Whitlock
@Jon Funny, we thought about exactly the same example
vc 74
@Scott Whitlock - But simply that the code compiles **is** your test.
Pieter
Please see my edit, this has to do with TDD.
Scott Whitlock
@Scott: I don't think TDD should require this. Again, you wouldn't write a test to make sure that you can't pass invalid arguments. Your tests drive the *positive* side of the design ("I can use a reference type argument") but not necessarily the *negative* side. Ultimately all testing should be pragmatic - but testing the compiler *isn't* a pragmatic use of your time, IMO.
Jon Skeet
Ok, you explained why I didn't need the test, and gave me a working answer anyway, +2 for you! :
Scott Whitlock
+1  A: 

I think you shouldn't unit test things that will be prevented by the compiler. You don't test that Foo(string) cannot be invoked as Foo(int) for instance.

Your test would be useful if C# didn't have constraints on generics but as it does provide this feature it's not.

vc 74
+3  A: 

This is a great question! So much agree with your test driven approach. What you are struggling with is actually the clash of two paradigms. The old paradigm that programs should be proven correct using mathematics without running them (a legacy of our profession’s ancestry in mathematics) and the new paradigm that programd should be proven correct by executing with example use cases, ie tests. So what you are about to try is to apply the practice of the new paradigm on an artifact of the old paradigm, which of course won’t really work…

More on the dichotomy of types and tests, see this excellent article by Chris Smith, http://web.archive.org/web/20080822101209/http://www.pphsg.org/cdsmith/types.html

Adrian