如何组织这些测试以鼓励代码重用
本文关键字:代码 何组织 测试 | 更新日期: 2023-09-27 17:51:12
我总结
正在努力组织我的单元测试,我需要一些指导来优化我的努力。
遵循单元测试的最佳实践,抽象类应通过其派生类型进行测试,从而在我的单元测试中构建与我的领域模型中相同的继承层次结构。
例外,某些测试是多余的,并且测试代码通过此层次结构成倍增加。
例如,属性测试最终通过执行几乎相同的测试和编写相同的代码行来结束。
您将如何组织测试?
我认为无论您测试什么,有些测试都是相同的。其中一些是:
- 测试属性是否在分配时返回预期值;
- 测试字符串属性在赋值为 null 时是否引发;
- 测试字符串属性在分配的字符串比允许的长字符串时是否引发;
- 测试在分配超出范围的值时是否引发整数属性;
- 测试传入 null 参数时方法是否引发;
- 。
尽管上述示例与属性相关,但对于方法和可能想要测试的任何其他方法也是如此。
因此,这些基本测试可以属于其他测试类可以从中继承的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
可以属于一个类库,以便通过多个项目使用代码。
通过继承重新组织测试是可行的,尽管与类图中使用的继承类型不同。
[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 中组织测试。