摘要
我正在组织我的单元测试,我需要一些指导来优化我的工作。
遵循单元测试的最佳实践,抽象类应通过其派生类型进行测试,从而在单元测试中构建与我的域模型中的继承层次相同的继承层次结构。
异常,一些测试是多余的,测试代码通过这个层次结构倍增。
例如,对属性的测试最终通过执行相同的测试并编写相同的代码行来结束。
您将如何组织测试?
我认为无论你进行什么测试,一些测试都是一样的。其中一些是:
虽然上述示例与属性相关,但对于方法以及可能想要测试的其他任何方法都采用相同的方法。
因此,这些基本测试可能属于其他测试类可以继承的SuperTestBaseClass
,并从基础调用测试方法为目标测试成员。
一些例子
AuditableEntity
public abstract class AuditableEntity {
protected AuditableEntity() { }
DateTime CreatedAt { get; set; }
string CreatedBy { get; set; }
DateTime DeletedAt { get; set; }
string DeletedBy { get; set; }
int Id { get; protected set; }
DateTime UpdatedAt { get; set; }
string UpdatedBy { get; set; }
}
Customer
public class Customer : AuditableEntity {
public class Customer() : base() { Invoices = new Collection<Invoice>(); }
public string Name { get; set; }
public IEnumerable<Invoice> Invoices { get; private set; }
public long PhoneNumber { get; set; }
}
Invoice
public class Invoice : AuditableEntity {
public class Invoice() : base() { Items = new Collection<Item>(); }
public IEnumerable Items { get; private set; }
public double GrandTotal { get { return Items.Sum<Item>(i => i.Price); } }
}
SuperTestBaseClass
public abstract class SuperTestBaseClass {
protected SuperTestBaseClass() { }
protected void Throws<TException>(Action<T> action) {
// arrange
Type expected = typeof(TException);
Exception actual = null;
// act
try { action; } catch (Exception ex) { actual = ex; }
// assert
Assert.IsInstanceOfType(actual, expected);
}
protected void PropertyGetSetValue(Action<T> action, T value) {
// arrange
T expected = value;
action; // assign the value to the property, let's say
// act
T actual = action; // gets the value out of the property, let's say
// assert
Assert.AreEqual(expected, actual);
}
}
AuditableEntityTests<T>
public abstract class AuditableEntityTests<T> where T : IAuditableEntity : SuperTestBaseClass {
[TestMethod]
public CreatedAt_ReturnsNowByDefault() {
// arrange
DateTime expected = DateTime.Now;
// act
DateTime actual = Entity.CreatedAt;
// assert
Assert.AreEqual(expected, actual);
}
[TestMethod]
public void CreatedBy_ThrowsArgumentNullExceptionWhenNullOrWhiteSpace() {
Throws<ArgumentNullException>(Entity.CreatedBy = null);
}
protected T Entity { get; set; }
}
CustomerTests
[TestClass]
public class CustomerTests : AuditableEntityTests<Customer> {
public CustomerTests() : base() { }
[TestMethod]
public void Name_GetSetValue() {
PropertyGetSetValue(customer.Name, RandomValues.RandomString());
}
[TestMethod]
public void Name_CannotBeNull() {
Throws<ArgumentNullException>(Customer.Name = null);
}
[ClassInitialize]
public void CustomerEntitySetUp() { Entity = customer; }
[TestInitialize]
public void CustomerSetUp() { customer = new Customer(); }
private Customer customer;
}
以及其他一些基本问题
虽然这些问题可以提出一些其他好的问题,但我在这里问他们,因为我希望答案能够针对这种情况所说明的情况。
Func<T, TResult>
我想要的方式使用它?Action<T>
我想要的方式使用它?从这两个问题中,我希望我能让他们完全按照我作为委托参数传递的内容。
最后,
SuperTestBaseClass
可能属于一个类库,可供多个项目使用代码。 答案 0 :(得分:0)
通过继承来重新组织测试是可行的,尽管不是类图中使用的那种继承。
[TestClass]
public abstract class AuditableEntityTests {
protected AuditableEntityTests(IAuditableEntity entity) { Entity = entity; }
protected IAuditableEntity Entity { get; set; }
public abstract void GetsAndSetsValue();
public virtual Throws<TException>(Action action) {
// arrange
Type expected = typeof(TException);
Exception actual = null;
// act
try { action(); } catch(Exception ex) { actual = ex; }
// assert
Assert.IsInstanceOfType(actual, expected);
}
[TestClass]
public class CreatedAt : AuditableEntityTests {
public CreatedAt() : base(new Customer()) { }
[TestMethod]
public void GetsAndSetsValue() {
// arrange
DateTime expected = DateTime.Now;
Entity.CreatedAt = expected;
// act
DateTime actual = Entity.CreatedAt;
// assert
Assert.AreEqual(expected, actual);
}
}
[TestClass]
public class CreatedBy : AutditableEntityTests {
public CreatedBy() : base(new Customer());
[TestMethod]
public void GetsAndSetsValue() {
// arrange
string expected = RandomValues.RandomString();
Entity.CreatedBy = expected;
// act
string actual = Entity.CreatedBy;
// assert
Assert.AreEqual(expected, actual);
}
[TestMethod]
public void CannotBeNull() {
// arrange
string unexpected = null;
Entity.CreatedBy = RandomValues.RandomString();
// act
Entity.CreatedBy = unexpected;
string actual = Entity.CreatedBy;
// assert
Assert.IsNotNull(actual);
}
[TestMethod]
public void ThrowsWhenLongerThan12() {
// arrange
int length = 256;
string tooLong = RandomValues.RandomString(length);
// act
Action action = () => { Entity.CreatedBy = tooLong; };
// assert
Throws<ArgumentOutOfRangeException>(action);
}
}
}
这不仅鼓励代码重用,而且还以易于管理的方式在TestExplorer中组织您的测试。