文件访问、单元测试、依赖注入
我最近问了一个关于将业务逻辑与数据访问分离以使应用程序可测试的最佳方法的问题(
假设我有一个函数,可以将数据库中的数据加载到数据集中,格式化加载的数据(格式存储在某些外部 xml 文件中),最后将数据集序列化到文件中。因此,为了支持可测试性,我需要将访问文件系统的所有函数移至某个接口。但首先 - 我发现调用 dataset.WriteXml(file) 非常方便,但为了可测试性,我必须创建接口并将 dataset.WriteXml() 移至其实现,这在我看来是不必要的层并使代码不那么明显。其次 - 如果我将访问文件系统的所有方法移至单个接口,它将违反 SRP 原则,因为序列化\反序列化数据集和从文件读取数据格式似乎是不同的职责,对吗?
I've recently asked a question regarding best ways of separating business logic from data access to make application testable (here). Thanks to Jeff Sternal, I've created interface for the data access and passing its concrete implementation from the application top level to BL. But now I'm trying to figure out how can I separate file access methods from business logic.
Let's say I have a function that loads data from a database into dataset, formats loaded data (formats are stored in some external xml file) and finally serializes dataset to a file. So, to support testability I need to move all the functions that access file system to some interface. But first - I find it quite handy to just call dataset.WriteXml(file), but for the testability I have to create interface and move dataset.WriteXml() to its implementation, which looks to me as unnecessary layer and makes code less obvious. And second - if I move all methods that access file system to a single interface, it will violate SRP principle, because serializing\deserializing datasets and reading data formats from a file seems to be different responsibilities, correct?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
您可以使用采用
Stream
或TextWriter
的WriteXml()
版本并设置您的代码,以便将此对象传递到您的代码中,然后传入模拟对象进行测试。You could use the versions of
WriteXml()
that takes aStream
orTextWriter
and set up your code such that this object is passed into your code, then for testing pass in a mock object.从您的描述中尚不完全清楚您的代码在做什么。你的代码所做的最重要的事情是什么?是不是格式化了?我想说不要费心测试 xml 的编写。无论如何,你将如何验证这一点?
如果您必须读取和写入文件,您可能最好更改代码,以便传入流读取器/流写入器或文本读取器/文本写入器,调用代码注入诸如用于测试的内存流和用于实际 i/ 的文件流之类的实例。 Ø 生产中。
It's not completely clear from your description what your code is doing. What is the most important thing your code is doing? Is it the fomatting? I would say don't bother testing the writing of the xml. How would you verify that anyway?
If you must read from and write to files, you might be better altering your code so that you pass in a streamreader/streamwriter or textreader/textwriter, with the calling code injecting an instance of something like memorystream for testing and filestream for real i/o in production.
我认为你需要进一步拆分你的代码......
你说:
假设我有一个函数,可以
这听起来至少需要 3-4 个工作...
如果您将代码再拆分一些,那么您就可以测试每个函数,而无需周围的所有其他功能。
如果您只想执行 Dataset.WriteXML,那么您不必测试它。这是一个运行良好的框架功能。尝试在那里添加一些模拟来伪造它。具体如何取决于您的解决方案...
评论答案:
用自己的测试创建所有这些小类将使测试变得容易,并且它还会使您的函数小而紧凑(->易于测试)您将测试是否数据集的内容正是您所需要的,而不是数据集正确序列化为 xml 文件时的情况。您还将测试您的格式化程序是否能够正确执行其功能,而不依赖于任何其他逻辑。您还可以测试数据访问,但无需再次访问数据库(存根/模拟)
在您知道所有这些都按预期工作后,您“只需”验证数据集上的正确方法是否将被调用,并且应该满足你,因为你已经单独测试了其他部分。
单元测试的棘手部分是进行有意义的测试。它们应该是 -快速 - 和 -简单 -
为了使测试快速,你不应该接触你无法控制的东西:
为了使它们简单,你让你的类专注于一个任务,即SRP 进来了,你已经提到了。看看这个答案...它还会指出“SOLID”开发的其他原则
https: //stackoverflow.com/questions/1423597/solid-principles/1423627#1423627
I think you need to split up your code a little bit more...
You say:
Let's say I have a function that
That sounds like 3-4 jobs at least...
If you split your code some more, then you can test each of those functions without all the other goo around them.
If you just want to do Dataset.WriteXML, Then you dont have to test that. Thats a Framework function thats working quite nicely. Try getting some mocks in there to fake it out. How exactly depends on your solution...
Answer to comment:
Creating all those small classes with their own tests will make testing easy, and it will also make your functions small and compact (-> easy to test) You would test if the content of the Dataset is exactly what you require, not if the dataset gets serialized correctly into an xml file. You would also test if your formater would perform its function correctly, without any dependencies to any other logic. You would also test the Data access, but without accessing the DB(Stubs/Mocks again)
After you know that all this works like it should, you "just" verify that the propper method on the dataset will get called, and that should satisfy you, since you tested the other parts in isolation already.
The tricky part about unittesting is getting meaningfull tests. They should be -fast- and -simple-
To make the tests fast, you should not touch stuff you have no control over:
To make them simple, you keep you clases focused on a single task, that where the SRP comes in, which you already mentioned. Take a look at this answer... It will also point out the other principles of "SOLID" development
https://stackoverflow.com/questions/1423597/solid-principles/1423627#1423627
去参加额外的课程。测试时更容易:
这并不意味着您不这样做:
只要您使用 v.简单依赖注入,您实际上只是保持一切简单......这将在短期和长期内为您带来更好的结果。
诗。在测试期间不访问文件系统很重要,因为当您的测试数量增加时,您希望它们运行得更快,因此不要被误导到测试的第一次“快速”访问
Go with the extra classes. When testing it its easier to:
This doesn't mean you don't:
As long as you are using v. simple dependency injection, you are really just keeping everything simple ... which will get you better results both in the short and in the long run.
Ps. not hitting the file system during tests is important, as when your number of tests grow you want them to run v. fast, so don't be misguided to what appears at first "quick" accesses for a test
为了快速总结,如果您确实想让这个可测试,我建议:
将代码写入一个新类(其中
实现一个您可以使用的接口
嘲笑)。
DataSet
。DataSet
作为IXmlSerialized
你也可以嘲笑。
在没有看到当前代码的情况下,我必须做出一些假设(希望不要太多!) - 所以当前代码可能看起来像这样:
这个代码简单有效,但不可测试(没有副作用)。此外,将所有逻辑集中在一处违反了单一职责原则。
根据您的需求,这可能没问题! 实现 SRP 始终只是一个目标,并且我们总是需要平衡可测试性与影响我们设计的其他因素。
但是,如果您想进一步分离职责并使其可测试,您可以通过将格式化逻辑移至其自己的类(它将实现 IEmployeeDataSetFormatter ),然后注入一个 IEmployeeDataSetFormatter 来实现 到此方法调用中(或者我们可以将其注入到服务的构造函数中,就像
IEmployeeRepository
一样)。格式化数据的方法将返回一个 IXmlSerialized,以便我们可以模拟它以进行安全、隔离的测试:这有一定的成本。它添加了一个额外的接口、一个额外的类,并且增加了一点复杂性。但它是可测试的并且更加模块化(我认为这是一种很好的方式)。
For a quick summary, if you really want to make this testable, I recommend:
code into a new class (which
implements an interface you can use
to mock).
DataSet
.DataSet
as anIXmlSerializable
that you can also mock.
Without seeing the current code, I have to make some assumptions (hopefully not too many!) - so perhaps the current code looks something like this:
This code is simple and effective, but not testable (without side-effects). Additionally, having all that logic in one place is a violation of the single-responsibility principal.
Depending on your needs, that's probably fine! Fulfilling the SRP is always just a goal, and we always need to balance testability with other factors that influence our designs.
However, if you want to segregate responsibilities a bit more and make it testable, you could do so by moving the formatting logic into its own class (which will implement
IEmployeeDataSetFormatter
), then inject anIEmployeeDataSetFormatter
into this method call (alternately we could inject it into the service's constructor, just like theIEmployeeRepository
). The method that formats the data will return anIXmlSerializable
so we can mock it for safe, isolated testing:This has definite costs. It adds an additional interface, an additional class, and a bit more complexity. But it's testable and more modular (in a good way, I think).