如何对抽象类进行单元测试:使用存根扩展?
我想知道如何对抽象类和扩展抽象类的类进行单元测试。
我应该通过扩展抽象类、删除抽象方法来测试抽象类,然后测试所有具体方法吗? 然后只测试我重写的方法,并在单元测试中测试扩展我的抽象类的对象的抽象方法?
我是否应该有一个可用于测试抽象类的方法的抽象测试用例,并在我的测试用例中为扩展抽象类的对象扩展此类?
请注意,我的抽象类有一些具体方法。
I was wondering how to unit test abstract classes, and classes that extend abstract classes.
Should I test the abstract class by extending it, stubbing out the abstract methods, and then test all the concrete methods? Then only test the methods I override, and test the abstract methods in the unit tests for objects that extend my abstract class?
Should I have an abstract test case that can be used to test the methods of the abstract class, and extend this class in my test case for objects that extend the abstract class?
Note that my abstract class has some concrete methods.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(14)
有两种使用抽象基类的方法。
您正在专门化您的抽象对象,但所有客户端都将通过其基接口使用派生类。
您正在专门化您的抽象对象,但所有
您正在使用抽象基类来消除设计中对象内的重复,并且客户端通过自己的接口使用具体实现。!
解决方案 1 - 策略模式
如果你遇到第一种情况,那么你实际上有一个由派生类正在实现的抽象类中的虚拟方法定义的接口。
您应该考虑使其成为一个真正的接口,将抽象类更改为具体的,并在其构造函数中获取该接口的实例。 然后,您的派生类将成为这个新接口的实现。
这意味着您现在可以使用新接口的模拟实例来测试以前的抽象类,并且通过现在公共接口。 一切都很简单且可测试。
解决方案2
如果您遇到第二种情况,那么您的抽象类正在充当辅助类。
看一下它包含的功能。 看看是否可以将其中任何一个推送到正在操作的对象上,以最大限度地减少这种重复。 如果您还有任何剩余内容,请考虑将其设为一个辅助类,您的具体实现将在其构造函数中采用该辅助类并删除其基类。
这再次导致了简单且易于测试的具体类。
作为规则
优先考虑简单对象的复杂网络,而不是复杂对象的简单网络。
可扩展、可测试代码的关键是小型构建块和独立布线。
更新:如何处理两者的混合?
可以让一个基类执行这两个角色......即:它有一个公共接口,并且有受保护的辅助方法。 如果是这种情况,那么您可以将辅助方法分解为一个类(场景 2),并将继承树转换为策略模式。
如果您发现您的基类直接实现了一些方法,而其他方法是虚拟的,那么您仍然可以将继承树转换为策略模式,但我也将其视为职责未正确对齐的一个很好的指标,并且可能会需要重构。
更新2:抽象类作为垫脚石(2014/06/12)
前几天我遇到了使用抽象的情况,所以我想探讨一下原因。
我们的配置文件有标准格式。 这个特定的工具有 3 个配置文件,全部采用该格式。 我希望每个设置文件都有一个强类型的类,因此,通过依赖项注入,类可以请求它关心的设置。
我通过拥有一个知道如何解析设置文件格式的抽象基类和公开这些相同方法但封装了设置文件位置的派生类来实现此目的。
我可以编写一个由 3 个类包装的“SettingsFileParser”,然后委托给基类以公开数据访问方法。 我选择暂时不这样做,因为这会导致 3 个派生类中的委托代码比其他任何代码都多。
然而......随着代码的发展,每个设置类的使用者变得更加清晰。 每个设置用户都会要求一些设置并以某种方式转换它们(因为设置是文本,他们可能会将它们包装在对象中,然后将它们转换为数字等)。 当发生这种情况时,我将开始将此逻辑提取到数据操作方法中,并将它们推回强类型设置类。 这将为每组设置带来更高级别的界面,最终不再意识到它正在处理“设置”。
此时,强类型设置类将不再需要公开底层“设置”实现的“getter”方法。
那时我不再希望他们的公共接口包含设置访问器方法; 所以我将更改此类以封装设置解析器类,而不是从中派生。
因此,抽象类是:我现在避免委托代码的一种方法,也是代码中提醒我稍后更改设计的标记。 我可能永远不会接触到它,所以它可能会存在很长一段时间......只有代码可以告诉我们。
我发现任何规则都是如此......比如“没有静态方法”或“没有私有方法”。 它们表明代码中存在某种气味……这很好。 它让您不断寻找您错过的抽象......并让您同时继续为客户提供价值。
我想象这样的规则定义了一种景观,可维护的代码就存在于山谷中。 当您添加新行为时,就像雨落在您的代码上一样。 最初,你把它放在它落地的任何地方……然后你进行重构,让良好设计的力量推动行为,直到它最终落入山谷。
There are two ways in which abstract base classes are used.
You are specializing your abstract object, but all clients will use the derived class through its base interface.
You are using an abstract base class to factor out duplication within objects in your design, and clients use the concrete implementations through their own interfaces.!
Solution For 1 - Strategy Pattern
If you have the first situation, then you actually have an interface defined by the virtual methods in the abstract class that your derived classes are implementing.
You should consider making this a real interface, changing your abstract class to be concrete, and take an instance of this interface in its constructor. Your derived classes then become implementations of this new interface.
This means you can now test your previously abstract class using a mock instance of the new interface, and each new implementation through the now public interface. Everything is simple and testable.
Solution For 2
If you have the second situation, then your abstract class is working as a helper class.
Take a look at the functionality it contains. See if any of it can be pushed onto the objects that are being manipulated to minimize this duplication. If you still have anything left, look at making it a helper class that your concrete implementation take in their constructor and remove their base class.
This again leads to concrete classes that are simple and easily testable.
As a Rule
Favor complex network of simple objects over a simple network of complex objects.
The key to extensible testable code is small building blocks and independent wiring.
Updated : How to handle mixtures of both?
It is possible to have a base class performing both of these roles... ie: it has a public interface, and has protected helper methods. If this is the case, then you can factor out the helper methods into one class (scenario2) and convert the inheritance tree into a strategy pattern.
If you find you have some methods your base class implements directly and other are virtual, then you can still convert the inheritance tree into a strategy pattern, but I would also take it as a good indicator that the responsibilities are not correctly aligned, and may need refactoring.
Update 2 : Abstract Classes as a stepping stone (2014/06/12)
I had a situation the other day where I used abstract, so I'd like to explore why.
We have a standard format for our configuration files. This particular tool has 3 configuration files all in that format. I wanted a strongly typed class for each setting file so, through dependency injection, a class could ask for the settings it cared about.
I implemented this by having an abstract base class that knows how to parse the settings files formats and derived classes that exposed those same methods, but encapsulated the location of the settings file.
I could have written a "SettingsFileParser" that the 3 classes wrapped, and then delegated through to the base class to expose the data access methods. I chose not to do this yet as it would lead to 3 derived classes with more delegation code in them than anything else.
However... as this code evolves and the consumers of each of these settings classes become clearer. Each settings users will ask for some settings and transform them in some way (as settings are text they may wrap them in objects of convert them to numbers etc.). As this happens I will start to extract this logic into data manipulation methods and push them back onto the strongly typed settings classes. This will lead to a higher level interface for each set of settings, that is eventually no longer aware it's dealing with 'settings'.
At this point the strongly typed settings classes will no longer need the "getter" methods that expose the underlying 'settings' implementation.
At that point I would no longer want their public interface to include the settings accessor methods; so I will change this class to encapsulate a settings parser class instead of derive from it.
The Abstract class is therefore: a way for me to avoid delegation code at the moment, and a marker in the code to remind me to change the design later. I may never get to it, so it may live a good while... only the code can tell.
I find this to be true with any rule... like "no static methods" or "no private methods". They indicate a smell in the code... and that's good. It keeps you looking for the abstraction that you have missed... and lets you carry on providing value to your customer in the mean time.
I imagine rules like this one defining a landscape, where maintainable code lives in the valleys. As you add new behaviour, it's like rain landing on your code. Initially you put it wherever it lands.. then you refactor to allow the forces of good design to push the behaviour around until it all ends up in the valleys.
编写一个 Mock 对象并仅将它们用于测试。 它们通常非常非常小(从抽象类继承),而不是更多。然后,在单元测试中,您可以调用要测试的抽象方法。
您应该测试包含一些逻辑的抽象类,就像您拥有的所有其他类一样。
Write a Mock object and use them just for testing. They usually are very very very minimal (inherit from the abstract class) and not more.Then, in your Unit Test you can call the abstract method you want to test.
You should test abstract class that contain some logic like all other classes you have.
我对抽象类和接口所做的事情如下:我编写一个测试,该测试使用具体的对象。 但是测试中没有设置X类型(X是抽象类)的变量。 该测试类不会添加到测试套件中,而是添加到它的子类中,这些子类具有将变量设置为 X 的具体实现的设置方法。这样我就不会重复测试代码。 如果需要,未使用的测试的子类可以添加更多测试方法。
What I do for abstract classes and interfaces is the following: I write a test, that uses the object as it is concrete. But the variable of type X (X is the abstract class) is not set in the test. This test-class is not added to the test-suite, but subclasses of it, that have a setup-method that set the variable to a concrete implementation of X. That way I don't duplicate the test-code. The subclasses of the not used test can add more test-methods if needed.
要专门对抽象类进行单元测试,您应该出于测试目的派生它,在继承时测试 base.method() 结果和预期行为。
您通过调用方法来测试方法,因此通过实现抽象类来测试它......
To make an unit test specifically on the abstract class, you should derive it for testing purpose, test base.method() results and intended behaviour when inheriting.
You test a method by calling it so test an abstract class by implementing it...
如果您的抽象类包含具有商业价值的具体功能,那么我通常会通过创建一个删除抽象数据的测试替身来直接测试它,或者使用模拟框架来为我执行此操作。 我选择哪一种很大程度上取决于我是否需要编写抽象方法的特定于测试的实现。
我需要执行此操作的最常见情况是当我使用模板方法模式时,例如当我构建某种可供第三方使用的可扩展框架时。 在这种情况下,抽象类定义了我想要测试的算法,因此测试抽象基础比测试特定实现更有意义。
然而,我认为重要的是这些测试应该仅关注真实业务逻辑的具体实现; 您不应该对抽象类的实现细节进行单元测试,因为您最终会得到脆弱的测试。
If your abstract class contains concrete functionality that has business value, then I will usually test it directly by creating a test double that stubs out the abstract data, or by using a mocking framework to do this for me. Which one I choose depends a lot on whether I need to write test-specific implementations of the abstract methods or not.
The most common scenario in which I need to do this is when I'm using the Template Method pattern, such as when I'm building some sort of extensible framework that will be used by a 3rd party. In this case, the abstract class is what defines the algorithm that I want to test, so it makes more sense to test the abstract base than a specific implementation.
However, I think it's important that these tests should focus on the concrete implementations of real business logic only; you shouldn't unit test implementation details of the abstract class because you'll end up with brittle tests.
一种方法是编写与抽象类相对应的抽象测试用例,然后编写子类化抽象测试用例的具体测试用例。 对原始抽象类的每个具体子类执行此操作(即您的测试用例层次结构反映了您的类层次结构)。 请参阅 junit 食谱书中的测试接口: http://safari.informit.com/9781932394238/ch02lev1sec6< /a>. https://www.manning.com/books/junit-recipes 或 https://www.amazon.com/JUnit-Recipes-Practical-Methods-Programmer/dp/ 1932394230(如果您没有 Safari 帐户)。
另请参阅 xUnit 模式中的测试用例超类: http://xunitpatterns.com/Testcase%20Superclass.html
one way is to write an abstract test case that corresponds to your abstract class, then write concrete test cases that subclass your abstract test case. do this for each concrete subclass of your original abstract class (i.e. your test case hierarchy mirrors your class hierarchy). see Test an interface in the junit recipies book: http://safari.informit.com/9781932394238/ch02lev1sec6. https://www.manning.com/books/junit-recipes or https://www.amazon.com/JUnit-Recipes-Practical-Methods-Programmer/dp/1932394230 if you don't have a safari account.
also see Testcase Superclass in xUnit patterns: http://xunitpatterns.com/Testcase%20Superclass.html
我反对“抽象”测试。 我认为测试是一个具体的想法,没有抽象。 如果您有通用元素,请将它们放入辅助方法或类中以供每个人使用。
至于测试抽象测试类,请确保问自己正在测试的是什么。 有多种方法,您应该找出适合您的场景的方法。 您是否想在子类中测试新方法? 然后让您的测试仅与该方法交互。 您正在测试基类中的方法吗? 然后可能只为该类提供一个单独的固定装置,并根据需要进行尽可能多的测试来单独测试每个方法。
I would argue against "abstract" tests. I think a test is a concrete idea and doesn't have an abstraction. If you have common elements, put them in helper methods or classes for everyone to use.
As for testing an abstract test class, make sure you ask yourself what it is you're testing. There are several approaches, and you should find out what works in your scenario. Are you trying to test out a new method in your subclass? Then have your tests only interact with that method. Are you testing the methods in your base class? Then probably have a separate fixture only for that class, and test each method individually with as many tests as necessary.
这是我在设置用于测试抽象类的工具时通常遵循的模式:
以及我在测试中使用的版本:
如果在我不期望的情况下调用抽象方法,则测试会失败。 在安排测试时,我可以使用执行断言、抛出异常、返回不同值等的 lambda 轻松地删除抽象方法。
This is the pattern I usually follow when setting up a harness for testing an abstract class:
And the version I use under test:
If the abstract methods are called when I don't expect it, the tests fail. When arranging the tests, I can easily stub out the abstract methods with lambdas that perform asserts, throw exceptions, return different values, etc.
如果具体方法调用任何抽象方法,则该策略将不起作用,并且您需要单独测试每个子类的行为。 否则,按照您所描述的那样扩展它并存根抽象方法应该没问题,前提是抽象类的具体方法与子类分离。
If the concrete methods invoke any of the abstract methods that strategy won't work, and you'd want to test each child class behavior separately. Otherwise, extending it and stubbing the abstract methods as you've described should be fine, again provided the abstract class concrete methods are decoupled from child classes.
我想您可能想要测试抽象类的基本功能...但是您最好的方法可能是扩展该类而不重写任何方法,并对抽象方法进行最小努力的模拟。
I suppose you could want to test the base functionality of an abstract class... But you'd probably be best off by extending the class without overriding any methods, and make minimum-effort mocking for the abstract methods.
使用抽象类的主要动机之一是在应用程序中启用多态性——即:您可以在运行时替换不同的版本。 事实上,这与使用接口非常相似,只是抽象类提供了一些通用的管道,通常称为模板模式。
从单元测试的角度来看,有两件事需要考虑:
抽象类与其相关类的交互。 使用模拟测试框架非常适合这种情况,因为它表明您的抽象类与其他类可以很好地配合。
派生类的功能。 如果您有为派生类编写的自定义逻辑,则应该单独测试这些类。
编辑:RhinoMocks 是一个很棒的模拟测试框架,它可以通过从类动态派生来在运行时生成模拟对象。 这种方法可以为您节省大量手动编码派生类的时间。
One of the main motivations for using an abstract class is to enable polymorphism within your application -- i.e: you can substitute a different version at runtime. In fact, this is very much the same thing as using an interface except the abstract class provides some common plumbing, often referred to as a Template pattern.
From a unit testing perspective, there are two things to consider:
Interaction of your abstract class with it related classes. Using a mock testing framework is ideal for this scenario as it shows that your abstract class plays well with others.
Functionality of derived classes. If you have custom logic that you've written for your derived classes, you should test those classes in isolation.
edit: RhinoMocks is an awesome mock testing framework that can generate mock objects at runtime by dynamically deriving from your class. This approach can save you countless hours of hand-coding derived classes.
首先,如果抽象类包含一些具体方法,我认为你应该考虑这个例子来这样做
First if abstract class contained some concrete method i think you should do this considered this example
如果抽象类适合您的实现,请测试(如上所述)派生的具体类。 你的假设是正确的。
为了避免将来的混乱,请注意这个具体的测试类不是模拟的,而是假的。
严格来说,模拟由以下特征定义:
-- 在测试时提供(同样,通过使用模拟框架)。 这样,您就可以避免正在测试的实现与其依赖项的实现(都应该有自己的离散测试)耦合。
If an abstract class is appropriate for your implementation, test (as suggested above) a derived concrete class. Your assumptions are correct.
To avoid future confusion, be aware that this concrete test class is not a mock, but a fake.
In strict terms, a mock is defined by the following characteristics:
-- are supplied at test-time (again, by use of a mocking framework). This way, you avoid coupling of the implementation being tested with the implementation of its dependencies (which should all have their own discrete tests).
根据@patrick-desjardins的回答,我实现了抽象及其实现类以及
@Test
,如下所示:抽象类 - ABC.java
作为抽象类不能实例化,但可以子类化,具体类DEF.java,如下:
@Test类来测试两者抽象为以及非抽象方法:
Following @patrick-desjardins answer, I implemented abstract and it's implementation class along with
@Test
as follows:Abstract class - ABC.java
As Abstract classes cannot be instantiated, but they can be subclassed, concrete class DEF.java, is as follows:
@Test class to test both abstract as well as non-abstract method: