单元测试'结构'的方法?
很抱歉写了这么长的文章……
在被介绍到一个棕地项目时,我对某些单元测试集和思考内容存有疑问。假设您有一个存储库类,包装了一个存储过程,并且在开发人员指南中,有一组特定的准则(规则),描述了应该如何构造该类。该类可能如下所示:
public class PersonRepository
{
public PersonCollection FindPersonsByNameAndCity(string personName, string cityName)
{
using (new SomeProfiler("someKey"))
{
var sp = Ioc.Resolve<IPersonStoredProcedure>();
sp.addNameArguement(personName);
sp.addCityArguement(cityName);
return sp.invoke();
}
} }
现在,我当然会编写一些集成测试,测试 SP 是否可以被调用,以及行为是否符合预期。但是,我是否会编写断言以下内容的单元测试:
- 调用具有输入参数“someKey”的 SomeProfiler 的构造函数 调用
- PersonStoredProcedure 的构造函数
- 调用存储过程上的 addNameArgument 方法,并使用参数 personName
- 调用存储过程上的 addCityArgument 方法使用参数 cityName
- 在存储过程上调用 invoke 方法 -
如果是这样,除了行为之外,我可能会测试方法的整个结构。我最初的想法是,这太过分了。然而,就团队强制执行的编码实践而言,这些测试确保了统一且“正确”的结构,并且正确调用了下一层(从 DAL 到 DB、BLL 到 DAL 等)。
就我而言,这些类型的测试是针对应用程序的每一层执行的。
后续问题 - SomeProfiler 类的使用对我来说有点像一种约定 - 可以通过使用静态代码分析或单元测试 + 反射来创建约定样式的测试,而不是为此创建显式测试吗?
提前致谢。
Sorry for the long post...
While being introduced to a brown field project, I'm having doubts regarding certain sets of unit tests and what to think. Say you had a repostory class, wrapping a stored procedure and in the developer guide book, a certain set guidelines (rules), describe how this class should be constructured. The class could look like the following:
public class PersonRepository
{
public PersonCollection FindPersonsByNameAndCity(string personName, string cityName)
{
using (new SomeProfiler("someKey"))
{
var sp = Ioc.Resolve<IPersonStoredProcedure>();
sp.addNameArguement(personName);
sp.addCityArguement(cityName);
return sp.invoke();
}
} }
Now, I would of course write some integration tests, testing that the SP can be invoked, and that the behavior is as expected. However, would I write unit tests that assert that:
- Constructor for SomeProfiler with the input parameter "someKey" is called
- The Constructor of PersonStoredProcedure is called
- The addNameArgument method on the stored procedure is called with parameter personName
- The addCityArgument method on the stored procedure is called with parameter cityName
- The invoke method is called on the stored procedure -
If so, I would potentially be testing the whole structure of a method, besides the behavior. My initial thought is that it is overkill. However, in regards to the coding practices enforced by the team, these test ensure a uniform and 'correct' structure and that the next layer is called correctly (from DAL to DB, BLL to DAL etc).
In my case these type of tests, are performed for each layer of the application.
Follow up question - the use of the SomeProfiler class smells a little like a convention to me - Instead creating explicit tests for this, could one create convention styled test by using static code analysis or unittest + reflection?
Thanks in advance.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我认为你最初的想法是正确的——这是一种矫枉过正的行为。尽管您可以使用反射来确保该类具有您期望的方法,但我不确定您是否想以这种方式测试它。
也许您应该使用一些工具(例如 FxCop/StyleCop 或 nDepend)来确保特定程序集/dll 中的所有类都具有这些属性,而不是进行单元测试。
话虽如此,我是“只编写你需要的代码”的信徒,为什么要测试一个方法是否存在,要么你在代码中的某个地方使用它,你可以测试特定的情况,要么你不测试 - 所以它是无关紧要。
I think that your initial thought was right - this is an overkill. Although you can use reflection to make sure that the class has the methods you expect I'm not sure you want to test it that way.
Perhaps instead of unit testing you should use some tool such as FxCop/StyleCop or nDepend to make sure all of the classes in a specific assembly/dll has these properties.
Having said that I'm a believer of "only code what you need" why test that a method exist, either you use it somewhere in your code and in that can you can test the specific case or you don't - and so it's irrelevant.
单元测试应该关注行为,而不是实现。因此,编写测试来验证某些参数是否已设置或传入并不会给您的测试策略增加太多价值。
由于提供的示例似乎正在与您的数据库进行通信,因此它不能真正被视为“单元测试”,因为它必须与具有额外设置和先决条件的物理依赖项进行通信,例如环境的可用性、数据库模式、现有数据、存储过程等。您编写的任何测试实际上也在验证这些先决条件。
在目前的情况下,这些类型的测试的最佳选择是测试类提供的行为——调用存储库上的方法,然后验证结果是否符合您的预期。然而,您会突然意识到这里有一个隐藏的成本——数据库在测试运行之间维护状态,并且您需要额外的设置或拆卸逻辑来确保数据库处于众所周知的状态。
虽然我意识到问题的意图是关于测试“黑匣子”,但很明显,您的 API 中存在一些隐藏的魔力。我倾向于使用当前测试范围内的内存数据库来解决众所周知的状态问题,这将我与环境考虑隔离开来,并使我能够并行化我的集成测试。我敢打赌,在当前的设计下,不存在以编程方式引入数据库配置的“接缝”,因此您会“陷入困境”。根据我的经验,魔法会带来伤害。
然而,对现有设计的轻微改变解决了这个问题,“魔法”消失了:
Unit tests should focus on behavior, not implementation. So writing a test to verify that certain arguments are set or passed in doesn't add much value to your testing strategy.
As the example provided appears to be communicating with your database, it can't truly be considered a "unit test" as it must communicate with physical dependencies that have additional setup and preconditions, such as availability of the environment, database schema, existing data, stored-procedures, etc. Any test you write is actually verifying these preconditions as well.
In it's present condition, your best bet for these types of tests is to test the behavior provided by the class -- invoke a method on your repository and then validate that the results are what you expected. However, you'll suddenly realize that there's a hidden cost here -- the database maintains state between test runs, and you'll need additional setup or tear-down logic to ensure that the database is in a well-known state.
While I realize the intent of the question was about the testing a "black box", it seems obvious that there's some hidden magic here in your API. My preference to solve the well-known state problem is to use an in-memory database that is scoped to the current test, which isolates me from environment considerations and enables me to parallelize my integration tests. I'd wager that under the current design, there is no "seam" to programmatically introduce a database configuration so you're "hemmed in". In my experience, magic hurts.
However, a slight change to the existing design solves this problem and the "magic" goes away: