C# 单元测试和验证逻辑
我目前正在为包含验证例程的业务逻辑类编写一些单元测试。例如:C# 单元测试和验证逻辑,c#,.net,unit-testing,validation,tdd,C#,.net,Unit Testing,Validation,Tdd,我目前正在为包含验证例程的业务逻辑类编写一些单元测试。例如: public User CreateUser(string username, string password, UserDetails details) { ValidateUserDetails(details); ValidateUsername(username); ValidatePassword(password); // create and return user } 我的测试夹具是
public User CreateUser(string username, string password, UserDetails details)
{
ValidateUserDetails(details);
ValidateUsername(username);
ValidatePassword(password);
// create and return user
}
我的测试夹具是否应该包含针对Validate*方法中可能出现的每个验证错误的测试,还是最好将其留给一组单独的测试?或者验证逻辑应该以某种方式重构
我的理由是,如果我决定测试CreateUser中可能发生的所有验证错误,那么测试夹具将变得非常臃肿。而且大多数验证方法都是从多个地方使用的
在这种情况下,有什么好的模式或建议吗?每个测试都应该因为一个原因失败,并且只有一个测试应该因为这个原因失败 这对编写一组可维护的单元测试有很大帮助 我将为ValidateUserDetails、ValidateUsername和ValidateUserPassword分别编写两个测试。然后您只需要测试CreateUser是否调用了这些函数
重读你的问题;看来我有点误解了 你可能会对J.p Boodhoo写的行为驱动设计风格感兴趣。 BDD正在成为一个超负荷的术语,每个人都有不同的定义和不同的工具来实现它。据我所知,JPBoodhoo正在做的是根据关注点而不是类来划分测试夹具 例如,您可以创建单独的装置来测试用户详细信息验证、用户名验证、密码验证和创建用户。BDD的思想是,通过以正确的方式命名testfixture和测试,您可以通过打印testfixture名称和测试名称来创建类似于文档的内容。按关注点而不是按类对测试进行分组的另一个优点是,对于每个夹具,您可能只需要一个设置和拆卸例程 不过,我自己对此没有多少经验 如果你对阅读更多感兴趣,JP Boodhoo在他的博客上发布了很多关于这一点的信息(见上面的链接),或者你也可以听Scott Bellware的dot net rocks插曲,他在其中谈到了类似的分组和命名测试方法 我希望这正是您想要的。
- 让针对验证方法的单元测试(复数)确认它们的正确功能
- 让针对CreateUser方法的单元测试(复数)确认其正确运行
如果CreateUser只需要调用验证方法,而不需要自己做出验证决定,那么针对CreateUser的测试应该确认该要求。您肯定需要测试验证方法 不需要为所有可能的参数组合测试其他方法,只需确保执行了验证 您似乎将验证和合同设计混为一谈 验证通常用于友好地通知用户其输入不正确。它与业务逻辑密切相关(密码不够强,电子邮件格式不正确等) 契约式设计确保您的代码可以执行,而不会在以后抛出异常(即使没有异常,您也会得到异常,但要晚得多,而且可能更模糊) 关于应该包含验证逻辑的应用程序层,最好的可能是定义应用程序边界的应用程序层,它是清理应用程序输入的好地方。在这个边界内不应该有任何验证逻辑,只有契约式设计可以更早地检测错误
最后,当您想友好地通知用户他错了时,编写验证逻辑测试。否则,使用契约式设计并不断抛出异常。我将为每个Validatexx方法添加一组测试。然后在CreateUser中创建3个测试用例,用于检查当ValidateUserDetails、ValidateUsername和ValidatePassword中的每一个失败但另一个成功时会发生什么情况。我用于定义业务验证规则。下面是我如何测试角落案例(来自开源的示例): 其中,ValidConnection规则定义为:
public static void ValidConnection(ServiceConnection connection, IScope scope)
{
scope.Validate(connection.Username, "UserName", StringIs.Limited(6, 256), StringIs.ValidEmail);
scope.Validate(connection.Password, "Password", StringIs.Limited(1, 256));
scope.Validate(connection.Endpoint, "Endpoint", Endpoint);
}
static void Endpoint(Uri obj, IScope scope)
{
var local = obj.LocalPath.ToLowerInvariant();
if (local == "/timeseries.asmx")
{
scope.Error("Please, use TimeSeries2.asmx");
}
else if (local != "/timeseries2.asmx")
{
scope.Error("Unsupported local address '{0}'", local);
}
if (!obj.IsLoopback)
{
var host = obj.Host.ToLowerInvariant();
if ((host != "ws.lokad.com") && (host != "sandbox-ws.lokad.com"))
scope.Error("Unknown host '{0}'", host);
}
如果发现某个失败案例(即:添加了新的有效连接url),则规则和测试将得到更新
有关此模式的更多信息,请参阅。一切都是开源的,所以可以随意重用或提问
PS:请注意,此示例复合规则(即StringIs.ValidEmail或StringIs.Limited)中使用的基本规则是完全独立测试的,因此不需要过多的单元测试您的业务逻辑类的职责是什么?除了验证之外,它还做些什么?我想我会根据您的上下文将验证例程移动到自己的类(UserValidator)或多个类(UserDetailsValidator+UserCredentialsValidator),然后为测试提供模拟。所以你现在的课堂看起来像:
public User CreateUser(string username, string password, UserDetails details)
{
if (Validator.isValid(details, username, password)) {
// what happens when not valid
}
// create and return user
}
然后,您可以纯粹为验证提供单独的单元测试,业务逻辑类的测试可以关注验证通过和验证失败的时间,以及所有其他测试
public User CreateUser(string username, string password, UserDetails details)
{
if (Validator.isValid(details, username, password)) {
// what happens when not valid
}
// create and return user
}