我正在编写一个类来帮助我对代码进行单元测试。它看起来像这样:
/// <summary>
/// Wrapper for the LogManager class to allow us to stub the logger
/// </summary>
public class Logger
{
private static ILogger _logger = null;
/// <summary>
/// This should be called to get a valid logger.
/// </summary>
/// <returns>A valid logger to log issues to file.</returns>
public static ILogger GetLogger()
{
if (_logger == null)
_logger = LogManager.GetLogger("logger");
return _logger;
}
/// <summary>
/// This is used by unit tests to allow a stub to be used as a logger.
/// </summary>
/// <param name="logger"></param>
/// <returns></returns>
public static ILogger GetLogger(ILogger logger)
{
_logger = logger;
return _logger;
}
}
第二种方法仅用于单元测试。我从不打算在我的生产代码中调用它。
这是不好的做法吗?我应该找到另一种不这样做的方式吗?
答案 0 :(得分:12)
在我看来是的,这是一种不好的做法。单元测试旨在测试代码的实现,而不是真正影响它。在某些情况下,我发现以一种方式组织我的代码/方法以使其更容易/彻底测试是切实可行的,但是在测试特定用途的类中编写代码是一个不同的故事。
答案 1 :(得分:11)
通常,代码仅用于促进单元测试hides a design flaw。
在这种情况下,您隐藏的缺陷是使用global state。使用您的记录器的类是秘密依赖它的:除非您阅读它们的源代码,否则您无法告诉他们需要记录器!
这些类应该在其构造函数中需要ILogger
,这使得它们显然需要工作并使它们易于测试。
答案 2 :(得分:3)
你可以考虑这个:
/// <summary>
/// Wrapper for the LogManager class to allow us to stub the logger
/// </summary>
public class Logger
{
private static ILogger _logger = null;
/// <summary>
/// This should be called to get a valid logger.
/// </summary>
/// <returns>A valid logger to log issues to file.</returns>
public static ILogger Logger
{
get
{
if (_logger == null)
{
_logger = LogManager.GetLogger("logger");
}
return _logger
}
set
{
_logger = value;
}
}
}
如果没有使用setter设置,则第一次调用getter时设置记录器。
答案 3 :(得分:2)
IMO,是的,这是糟糕的代码。有两个原因:
我会推荐以下一项或多项:
答案 4 :(得分:1)
任何时候你在文件/库中的代码/不打算在生产容量中使用的代码都只是代码膨胀。您的生产对象不应编码为支持您的单元测试,应实施单元测试以支持您的生产对象。
答案 5 :(得分:1)
是的,设计并不完美:( GetLogger
方法实际上设置了记录器!使用setter方法 - 这看起来更合适。
并且看过Moq图书馆? http://code.google.com/p/moq/我喜欢它嘲笑。
答案 6 :(得分:1)
我没有任何奇怪的构造函数。相反,我在视图中有一个私有变量:
private readonly ILog _log = LogManager.GetLogger(typeof(MyViewClassName));
然后可以这样使用,在这种情况下,它在catch块中被调用:
_log.Error("SomePage.aspx.cs Page_Load failed", ex);
没有log4net的包装器。 ILog是log4net的一部分。我很好奇你为什么不使用Mocks。这是有道理的,然后你不需要做你正在做的事情。
答案 7 :(得分:1)
回答一般问题“为单元测试添加代码是不是很糟糕?”,一般来说不是没有坏。可测试性是系统的一个重要特征,因为它有助于保证系统的正确性(通过实际测试),并且与其他良好的设计原则高度相关,如松耦合和高内聚。
以另一种方式看待它;如果您有两种类似的方法来构建一些代码,主要区别在于可测试性,那么请选择更可测试的选项。
但至于你的代码的具体情况,我同意这里的大多数其他帖子;有一种更好的方法来编写该代码。
答案 8 :(得分:0)
我不会说这段代码是问题。只要您在xmldoc注释中声明方法仅用于单元测试,没有(理智的!)人员会在生产代码中使用它。
尽管如此,尽量在单元测试库中保留这些内容。正如Joel Etherton所说,无论如何......这都是代码膨胀......
答案 9 :(得分:0)
这里的错误是获取记录器的代码必须知道它想要的记录器。事实上,它没有理由关心。
这就是控件反转 海报孩子的原因。最简单的方法是,在配置文件中粘贴记录器的名称,并在测试代码中使用不同的值。在更复杂的级别,使用类似MEF的内容来决定加载哪个记录器。
答案 10 :(得分:0)
不,这绝对不是件坏事。
好的代码应该满足所有要求而不做任何其他事情。
但是,需求有多种形式,所有代码的非功能性要求是您可以向其他人证明其有效。
从这个角度来看,暴露对测试日志记录的访问是一件好事。比未经测试的所有日志要好得多,即使它们的内容很重要。更重要的是,在难以在运行时以“黑匣子”方式测试的罕见情况下,日志记录往往是最重要的 - 单元测试可能是在不良情况下测试故障模式的唯一方法,例如,当内存中有重要数据时,暂时失去与持久层的连接。
那就是说 - 你需要努力调和竞争要求。 API设计中的一个重要要求是不要泄漏对您不打算使用的API部分的访问权限。实现这一目标的一种方法是将您为测试做出的API决策明显地显而易见 - 例如通过重命名方法GetLoggerForUnitTesting
。