单元测试的重点是保持你的代码“固定”,这样如果你改变实现,你就会知道你没有破坏任何东西,或者如果你确实破坏了某些东西,你会立即确切地知道你破坏了什么。
这样做的一个附带好处是知道您当前编写的代码可以执行您希望它执行的操作,但这不是(或不一定)单元测试的主要目标。单元测试使您可以放心地更改代码。
如果上述类没有经过单元测试,则可以破坏该类而不会导致任何测试失败。例如:
public class Employee
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get { return "Smith"; } set { FirstName = value; } }
public Address Address { get; set; }
}
通过单元测试,您可以知道在您的整个代码库中没有发生过类似的事情。(这个具体的例子有可能吗?也许没有。但这样的事情是很有可能的,而且基本上是不可避免的。)
只需四个简单的单元测试,您就可以“修复”此代码,因为您知道如果没有测试失败,它不可能改变它的行为*:
[TestMethod]
public void TestId() {
var emp = new Employee { Id = 3 };
Assert.AreEqual(3, emp.Id);
}
[TestMethod]
public void TestFirstName() {
var emp = new Employee { FirstName = "asdf" };
Assert.AreEqual("asdf", emp.FirstName);
}
[TestMethod]
public void TestLastName() {
var emp = new Employee { LastName = "asdf" };
Assert.AreEqual("asdf", emp.LastName);
}
[TestMethod]
public void TestAddress() {
var address = new Address();
var emp = new Employee { Address = address };
Assert.AreEqual(address, emp.Address);
}
(*也许除了模仿您用来测试的值的损坏实现的侧面情况)
我不会在这里实现它,但您也可以使用反射来简化编写这些类型的测试。例如,您可以相对容易地构建一些允许您执行以下操作的东西:
[TestClass]
public sealed TestEmployee {
[TestMethod]
public void TestSimpleProperties() {
Assert.IsTrue(
SimplePropertyTester.Create(
new SimplePropertyTestCollection<Employee> {
{ emp => emp.Id, 3 },
{ emp => emp.FirstName, "asdf" },
{ emp => emp.LastName, "1234" },
{ emp => emp.Address, new Address() }
}
).Test()
);
}
}
您失去了将每个属性都放在自己的测试中的好处,但是您获得了非常容易修改测试集的能力。
您也可以为每个单元测试构建一个单独的测试器,然后将测试分开,但这与手动编写它们并没有太大区别,并且您最终编写了基本上完全相同的代码行一遍又一遍。
您可以做的一件事是将Console.WriteLine
语句放入Test()
方法中,然后您应该能够查看一些文本输出以更快地查明问题。
编辑:实际上我现在已经让这些看起来像下面这样:
[TestClass]
public sealed TestEmployee {
[TestMethod]
public void TestSimpleProperties() {
var factory = SimplePropertyTestFactory.Create<Employee>();
new SimplePropertyTestCollection<Employee> {
factory.IntTest(emp => emp.ID),
factory.StringTest(emp => emp.FirstName),
factory.StringTest(emp => emp.LastName),
factory.ReferenceTest(emp => emp.Address)
}.Test();
}
}