我正在为我们的大型企业应用程序构建业务层,目前我们正在进行不到500个单元测试。方案是我们有两个public
方法public AddT(T)
和public UpdateT(T)
,它们都对private AddOrUpdateT(T)
进行内部调用,因为两者之间的核心逻辑很多,但不是所有的;他们是不同的。
因为它们是独立的公共API(不管私有实现),我为每个API编写了单元测试,即使它们是相同的。这可能看起来像
[TestMethod]
public void AddT_HasBehaviorA()
{
}
[TestMethod]
public void UpdateT_HasBehaviorA()
{
}
目前,对于这个特定的业务对象,大约有30个用于添加的单元测试和40个用于更新的单元测试,其中30个更新测试与添加测试相同。
这是正常的,还是我应该将常见行为抽象到单独进行单元测试的公共帮助器类中,而这两个API只是使用该辅助类而不是具有实现的私有方法?
对于这些情况,什么是最佳做法?
答案 0 :(得分:4)
首先,理解为什么要通过单元测试来覆盖这些方法非常重要,因为这会影响答案。只有您和您的团队知道这一点,但如果我们假设至少部分单元测试的动机是获得值得信赖的回归测试套件,那么您应该测试可观察行为被测系统(SUT)。
换句话说,单元测试应该是黑盒测试。测试不应该知道SUT的实现细节。因此,您可以从中得出的天真结论是,如果您有重复的行为,您还应该有重复的测试代码。
但是,您的系统越复杂,并且越依赖于常见的行为和策略,实施此测试策略就越难。这是因为您可能通过系统进行组合爆炸。 J.B. Rainsberger explains it better than I do
更好的选择通常是倾听您的测试(由GOOS推广的概念)。在这种情况下,听起来将公共行为提取到公共方法中是有价值的。然而,这本身并不能解决组合爆炸的问题。虽然您现在可以单独测试常见行为,但您还需要证明两个原始方法(Add
和Update
)使用新的公共方法(而不是,例如,一些复制和粘贴的代码)。
最好的方法是使用新策略撰写方法:
public class Host<T>
{
private readonly IHelper<T> helper;
public Host(IHelper<T> helper)
{
this.helper = helper;
}
public void Add(T item)
{
// Do something
this.helper.AddOrUpdate(item);
// Do something else
}
public void Update(T item)
{
// Do something
this.helper.AddOrUpdate(item);
// Do something else
}
}
(显然,您需要为类型和方法提供更好的名称。)
这使您可以使用Behaviour Verification 证明 Add
和Update
方法正确使用AddOrUpdate
方法:
[TestMethod]
public void AddT_HasBehaviorA()
{
var mock = new Mock<IHelper<object>>();
var sut = new Host<object>(mock.Object);
var item = new object();
sut.Add(item);
mock.Verify(h => h.AddOrUpdate(item));
}
[TestMethod]
public void UpdateT_HasBehaviorA()
{
var mock = new Mock<IHelper<object>>();
var sut = new Host<object>(mock.Object);
var item = new object();
sut.Update(item);
mock.Verify(h => h.AddOrUpdate(item));
}
答案 1 :(得分:1)
尽可能避免重复是一种很好的做法。这有助于代码的可读性和可维护性(可能还有其他*可能性;-))。它还使测试更容易,因为您可以开始在一个地方测试常用功能,而不必进行如此多的重复测试。此外,测试中的重复也很糟糕。
另一个最佳实践是仅为具有唯一逻辑的功能编写单元测试。如果在Add和Update方法中,您只调用另一个方法,则不需要在该级别编写单元测试,您应该关注被调用的方法。
这回到了不重复代码的初始点,如果你有共享重复代码的私有方法,那么将它分解成可以运行测试的其他东西可能是一个好主意。
答案 2 :(得分:1)
“由于很多核心逻辑在两者之间是相同的,但不是全部;它们是不同的。”
我认为你应该对这些进行单独的单元测试,因为正如你所说它们并不完全相同。此外,如果您稍后更改实现以调用两个不同的方法,该怎么办?然后你的单元测试只会测试其中一个,因为它们与两种方法的实现细节相关联。测试将通过,但您可能已经介绍了一个错误。
我认为更好的方法是测试两种方法,但添加辅助方法/类来完成常规工作。