I have an extension method for testing so I can do this:
var steve = new Zombie();
steve.Mood.ShouldBe("I'm hungry for brains!");
The extension method:
public static void ShouldBe<T>(this T actual, T expected)
{
Assert.That(actual, Is.EqualTo开发者_开发技巧(expected));
}
This shows:
Expected: "I'm hungry for brains!"
But was: "I want to shuffle aimlessly"
Is there any hack I can pull off to get the name of the property "BrainsConsumed" from within my extension method? Bonus points would be the instance variable and type Zombie.
UPDATE:
The new ShouldBe:
public static void ShouldBe<T>(this T actual, T expected)
{
var frame = new StackTrace(true).GetFrame(1);
var fileName = frame.GetFileName();
var lineNumber = frame.GetFileLineNumber() - 1;
var code = File.ReadAllLines(fileName)
.ElementAt(lineNumber)
.Trim().TrimEnd(';');
var codeMessage = new Regex(@"(^.*)(\.\s*ShouldBe\s*\()([^\)]+)\)").Replace(code, @"$1 should be $3");
var actualMessage = actual.ToString();
if (actual is string)
actualMessage = "\"" + actual + "\"";
var message = string.Format(@"{0} but was {1}", codeMessage, actualMessage);
Assert.That(actual, Is.EqualTo(expected), message);
}
and this prints out:
steve.Mood should be "I'm hungry for brains!" but was "I want to shuffle aimlessly"
Thanks everyone, esp. Matt Dotson, this is awesome. BTW don't feed the silky trolls people.
You can get the code if it's a debug build by using some of the diagnostics classes. Given that this is for unit tests, DEBUG is probably reasonable.
public static void ShouldBe<T>(this T actual, T expected)
{
var frame = new StackTrace(true).GetFrame(1);
var fileName = frame.GetFileName();
var lineNumber = frame.GetFileLineNumber() - 1;
string code = File.ReadLines(fileName).ElementAt(lineNumber).Trim();
Debug.Assert(actual.Equals(expected), code);
}
For your example, code = "steve.BrainsConsumed.ShouldBe(0);"
Obviously you should add some error checking to this code, and you could probably make it faster by not reading all the lines in the file.
The best I can do would be:
steve.Property(p => p.BrainsConsumed).ShouldBe(0);
or:
steve.ShouldBe(p => p.BrainsConsumed, 0);
or:
Assert.AreEqual(() => steve.BrainsConsumed, 0);
Re:
Bonus points would be the instance variable
By using Expression<Func<TSource, TValue>>
(or just Expression<Func<T>>
) you can get the property name and value fairly easily. I'll do an example for the middle - note that the first requires an extra type for the DSL, but nothing heavy:
public static class Test
{
public static void AssertEqual<TSource, TValue>(
this TSource source,
Expression<Func<TSource, TValue>> selector,
TValue expected)
where TSource : class
{
TValue value = selector.Compile()(source);
string paramName = selector.Parameters[0].Name;
System.Diagnostics.Debug.Assert(
EqualityComparer<TValue>.Default.Equals(value, expected),
typeof(TSource) + " " + paramName + ": " +
value + " doesn't match expected " + expected);
}
}
Or slightly better message:
public class Zombie
{
public int BrainsConsumed { get; set; }
static void Main() {
Zombie steve = new Zombie { BrainsConsumed = 2 };
Test.ShouldBeEqual(() => steve.BrainsConsumed, 0);
}
}
public static class Test
{
static string GetName(Expression expr)
{
if (expr.NodeType == ExpressionType.MemberAccess)
{
var me = (MemberExpression)expr;
string name = me.Member.Name, subExpr = GetName(me.Expression);
return string.IsNullOrEmpty(subExpr)
? name : (subExpr + "." + name);
}
return "";
}
public static void ShouldBeEqual<TValue>(
Expression<Func<TValue>> selector,
TValue expected)
{
TValue value = selector.Compile()();
string name = GetName(selector.Body);
System.Diagnostics.Debug.Assert(
EqualityComparer<TValue>.Default.Equals(value, expected),
typeof(TValue) + " " + name + ": " +
value + " doesn't match expected " + expected);
}
}
No, I don't think you can.
Let's assume that BrainsConsumed is an integer (which looks likely). In that case, the parameter is passed by value - all you get is a copy of the integer you're testing. It has no name apart from the one in the local scope (actual).
This similar question may clarify:
Finding the variable name passed to a function
Unfortunately in that position you won't be able to get the name of the property at that stage. The problem is that you're passing the value of the BrainsConsumed field in and there's simply no link back to the Zombie at that point (as far as your method is concerned it's an int and it can't work out where the int originally came from).
The best thing I could come up with for you is that Environment.StackTrace would have the pertinent info in it since you've called steve.BrainsConsumed 1 step up the stack (Only recommend this if what you're trying to do is get some idea what's failed in your unit tests - not if it's to actually traverse up the stack in regular program flow).
This would allow you to test it, but won't get you the name of the method.
You could have this extension:
public static void ShouldBe<T>(this Func<T> func, T expected)
{
Assert.AreEqual(func(), expected);
}
With the following ussage:
((Func<int>)Program.TestMethod).ShouldBe(2);
精彩评论