我正在努力变得更熟悉测试驱动的开发。到目前为止,我已经看到了一些简单的例子,但我仍然遇到了复杂逻辑的问题,例如我的DAL中的这个方法:
public static void UpdateUser(User user)
{
SqlConnection conn = new SqlConnection(ConfigurationSettings.AppSettings["WebSolutionConnectionString"]);
SqlCommand cmd = new SqlCommand("WS_UpdateUser", conn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("@UserID", SqlDbType.Int, 4);
cmd.Parameters.Add("@Alias", SqlDbType.NVarChar, 100);
cmd.Parameters.Add("@Email", SqlDbType.NVarChar, 100);
cmd.Parameters.Add("@Password", SqlDbType.NVarChar, 50);
cmd.Parameters.Add("@Avatar", SqlDbType.NVarChar, 50);
cmd.Parameters[0].Value = user.UserID;
cmd.Parameters[1].Value = user.Alias;
cmd.Parameters[2].Value = user.Email;
cmd.Parameters[3].Value = user.Password;
if (user.Avatar == string.Empty)
cmd.Parameters[4].Value = System.DBNull.Value;
else
cmd.Parameters[4].Value = user.Avatar;
conn.Open();
cmd.ExecuteNonQuery();
conn.Close();
}
这种方法有什么好的TDD实践?
答案 0 :(得分:6)
鉴于代码已经编写,让我们谈谈是什么让它难以测试。这里的主要问题是这个方法纯粹是一个副作用:它没有返回任何内容(void),并且它的效果在你的代码中是不可观察的,在object-land中 - 可观察的副作用应该是远在数据库中的某个地方,记录现已更新。
如果您认为您的单元测试符合“鉴于这些条件,当我这样做,那么我应该观察到这一点”,您可以看到您所拥有的代码对于单元测试是有问题的,因为前提条件(给定连接到有效的DB)并且单元测试不能直接访问后置条件(记录已更新),并且取决于代码运行的位置(2个人在2台机器上“按原样”运行代码没有理由期待相同的结果)。
这就是为什么在技术上,不是纯粹在内存中的测试不被认为是单元测试,并且有点超出了“经典TDD”的范围。
在你的情况下,这里有两个想法:
1)整合测试。如果要验证代码如何与数据库一起工作,那么您将进行集成测试而不是单元测试。像BDD这样的TDD技术可以提供帮助。而不是测试“代码单元”(通常是方法),而是关注整个用户或系统场景,在更高级别上运行。在这种情况下,例如你可以把它放在更高的层次上,假设你的DAL上有一个叫做CreateUser,UpdateUser,ReadUser的方法,你可能要测试的场景是“给定我创建了一个用户” ,当我更新用户名时,然后当我读取用户时,名称应该更新“ - 然后您将针对完整设置执行方案,涉及数据以及DAL和可能的UI。
我从这方面发现了以下MSDN article on BDD + TDD有趣的内容 - 它很好地说明了2可以如何融合在一起。
2)如果你想使你的方法可测试,你必须暴露一些状态。该方法的主要部分围绕构建命令。您可以通过这种方式概述方法:
* grab a connection
* create the parameters and types of the command
* fill in the parameters of the command from the object
* execute the command and clean up
您实际上可以测试大多数这些步骤:可观察状态是命令本身。你可以沿着这些方向做点什么:
public class UpdateUserCommandBuilder
{
IConnectionConfiguration config;
public void BuildAndExecute(User user)
{
var command = BuildCommand(user);
ExecuteCommand(command);
}
public SqlCommand BuildCommand(User user)
{
var connection = config.GetConnection(); // so that you can mock it
var command = new SqlCommand(...)
command = CreateArguments(command); // you can verify that method now
command = FillArguments(command, user); // and this one too
return command;
}
}
我不会一直走到这里,但我认为大纲传达了这个想法。走这条路线有助于使构建器的步骤可以验证:您可以断言是否创建了正确的命令。这有一些价值,但仍然不会告诉你命令的执行是否成功,所以值得考虑这是否值得使用你的测试预算!可以说,运行整个DAL的更高级别的集成测试可能更经济。
希望这有帮助!
答案 1 :(得分:1)
我会将方法声明更改为:
public static void UpdateUser(User user,SqlConnection conn);
然后您可以传入已配置的SQL连接。在真实的应用程序中,您依赖于AppSettings
告诉您所需连接的内容,但在测试中,您会给它一个假连接,只允许您记录针对该连接执行的命令。然后,您可以验证该方法是否正确地请求存储的查询,并作为结果发送正确的参数。