我正在使用C#。是否建议单元测试处理方法?如果是这样,为什么要测试这些方法?
答案 0 :(得分:9)
是的,但可能很难。在Dispose
实施中通常会发生两件事:
在这种情况下,很难验证代码是否被调用,例如Marshal.Release
。一种可能的解决方案是注入一个可以进行处理的对象,并在测试期间将模拟传递给它。有这样的效果:
interface ComObjectReleaser {
public virtual Release (IntPtr obj) {
Marshal.Release(obj);
}
}
class ClassWithComObject : IDisposable {
public ClassWithComObject (ComObjectReleaser releaser) {
m_releaser = releaser;
}
// Create an int object
ComObjectReleaser m_releaser;
int obj = 1;
IntPtr m_pointer = Marshal.GetIUnknownForObject(obj);
public void Dispose() {
m_releaser.Release(m_pointer);
}
}
//Using MOQ - the best mocking framework :)))
class ClassWithComObjectTest {
public DisposeShouldReleaseComObject() {
var releaserMock = new Mock<ComObjectReleaser>();
var target = new ClassWithComObject(releaserMock);
target.Dispose();
releaserMock.Verify(r=>r.Dispose());
}
}
Dispose
方法称为对此的解决方案可能不像上面那么简单。在大多数情况下,Dispose的实现不是虚拟的,所以嘲笑它很难。
一种方法是将这些其他对象包装在一个可模拟的包装器中,类似于System.Web.Abstractions
命名空间为HttpContext
类所做的事情 - 即使用所有简单委托的虚拟方法定义HttpContextBase
类方法调用真实的HttpContext
类。
有关如何执行此类操作的更多想法,请查看System.IO.Abstractions项目。
答案 1 :(得分:6)
如果您的类创建并使用非托管资源,那么您应该确保Dispose按预期工作 - 尽管可以认为它更多是集成测试,因为您将要使用的类型必须跳过。
如果你的类只创建/使用托管资源(即它们实现IDisposable),那么你真正需要确保的是在正确的时间调用这些资源上的Dispose方法 - 如果你使用某种形式的DI然后你可以注入一个mock并断言已调用Dispose。
看看你的处置方法的复杂性 - 如果它们只有几行,可能只有1个条件,那么问问自己单元测试它们是否真的有好处。
答案 2 :(得分:5)
当然不能伤害。客户端代码在处理完之后可能会尝试使用该类的对象。如果您的类由其他IDisposable
个对象组成,那么如果它处于不再可用的状态,则应始终抛出ObjectDisposedException
异常。
当然,您应该只测试对象的外部状态。在下面的示例中,我将属性Disposed
设置为外部以给我状态。
考虑:
internal class CanBeDisposed : IDisposable
{
private bool disposed;
public bool Disposed
{
get
{
if (!this.disposed)
return this.disposed;
throw new ObjectDisposedException("CanBeDisposed");
}
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
//// Dispose of managed resources.
}
//// Dispose of unmanaged resources.
this.disposed = true;
}
}
}
因此我将如何测试这个:
CanBeDisposed cbd;
using (cbd = new CanBeDisposed())
{
Debug.Assert(!cbd.Disposed); // Best not be disposed yet.
}
try
{
Debug.Assert(cbd.Disposed); // Expecting an exception.
}
catch (Exception ex)
{
Debug.Assert(ex is ObjectDisposedException); // Better be the right one.
}
答案 3 :(得分:2)
很好 - 如果您的情况要求您实施Dispose功能 - 您最好确保它符合您的想法!
例如,我们有一些用于协调数据库任务的类(想想SSIS包,但是使用SqlConnection和SqlCommand以及SqlBulkCopy等)。
如果我没有正确实现我的Dispose,我可能会有一个未提交的SqlTransaction,或者悬挂SqlConnection。如果我在系列中运行这些数据库任务的多个实例,那将非常糟糕。
答案 4 :(得分:0)
作为一个实用技巧(因为是的,你应该测试 Dispose()
)我的经验是,有两种方法可以轻松完成。
第一个遵循 Igor 接受的答案 - 注入类似 IDisposer
的内容,以便您可以调用
public void Dispose()
{
_disposer.Release(_disposable);
}
哪里
public interface IDisposer
{
void Release(IDisposable disposable);
}
然后您需要做的就是模拟 IDisposer
并断言它被调用一次并且您很成功。
第二个,也是我个人最喜欢的,是有一个工厂来生产你需要测试处理的东西。这仅在工厂生成可模拟类型(接口、抽象类)时才有效,但是,嘿,情况几乎总是如此,尤其是对于要处理的内容。出于测试目的,模拟工厂,但让它生成您要测试处理的事物的模拟实现。然后您可以直接在您的模拟上断言对 Dispose
的调用。类似的东西
public interface IFooFactory
{
IFoo Create(); // where IFoo : IDisposable
}
public class MockFoo : IFoo
{
// ugly, use something like Moq instead of this class
public int DisposalCount { get; privat set; }
public void Dispose()
{
DisposalCount++;
}
}
public class MockFooFactory
{
public MockFoo LatestFoo { get; private set; }
public IFoo Create()
{
LatestFoo = new MockFoo();
return LatestFoo;
}
}
现在您可以随时要求工厂(将在您的测试中提供)给您最新的 MockFoo
,然后您处理掉外面的东西并检查 DisposalCount == 1
(尽管您应该使用而是使用测试框架,例如 Moq)。