Unit testing 如何重构和单元测试复杂的遗留JavaEE5EJB方法?
我的同事和我目前正在将单元测试引入我们遗留的JavaEE5代码库。我们主要使用JUnit和Mockito。在编写测试的过程中,我们注意到EJB中的一些方法很难测试,因为它们同时做了很多事情 我对整个测试业务相当陌生,因此我希望了解如何更好地构造代码或测试。我的目标是写一个好的测试而不头痛 这是我们在管理消息队列的服务中的一个方法及其逻辑步骤的示例:Unit testing 如何重构和单元测试复杂的遗留JavaEE5EJB方法?,unit-testing,design-patterns,java-ee-5,Unit Testing,Design Patterns,Java Ee 5,我的同事和我目前正在将单元测试引入我们遗留的JavaEE5代码库。我们主要使用JUnit和Mockito。在编写测试的过程中,我们注意到EJB中的一些方法很难测试,因为它们同时做了很多事情 我对整个测试业务相当陌生,因此我希望了解如何更好地构造代码或测试。我的目标是写一个好的测试而不头痛 这是我们在管理消息队列的服务中的一个方法及其逻辑步骤的示例: 消费信息 确认先前下载的消息 GetNewUnderMessages 加法器消息(取决于稍微复杂的条件) markmessagesas已下载 序
- 消费信息
- 确认先前下载的消息
- GetNewUnderMessages
- 加法器消息(取决于稍微复杂的条件)
- markmessagesas已下载
- 序列化消息对象
您会推荐其他通用OO模式还是JavaEE模式?正如您所注意到的,很难对具体的高级程序进行单元测试。您还发现了两个最常见的问题: 通常,程序被配置为使用特定的资源,例如特定的文件、IP地址、主机名等。为了解决这个问题,您需要重构程序以使用依赖项注入。这通常是通过向构造函数添加参数来完成的,这些参数将替换ahrdcoded值 测试大型类和方法也非常困难。这通常是由于测试复杂逻辑所需的测试数量的组合爆炸。为了解决这个问题,您通常会首先进行重构以获得更多(但更短)的方法,然后尝试从原始类中提取几个类,每个类都有一个单一入口方法(public)和几个实用方法(private),从而使代码更具通用性和可测试性。这基本上是单一责任原则 现在,您可以通过测试新类开始“升级”。这将容易得多,因为此时组合运算更容易处理 在此过程中,您可能会发现,通过使用以下设计模式,您可以大大简化代码:命令、组合、适配器、工厂、生成器和外观。这些是减少混乱的最常见模式 旧程序的某些部分可能在很大程度上是不稳定的,要么因为它们太粗糙,要么因为它不值得麻烦。在这里,您可以接受一个简单的测试,只检查已知输入的输出是否没有改变。本质上是回归测试。依赖注入(DI)和单一责任原则(SRP)高度相关 SRP基本上是说,每个类只应该做一件事,并将所有其他事务委托给单独的类。例如,您的
serializeMessageObjects
方法应该被提取到它自己的类中——我们称之为MessageObjectSerializer
DI意味着将MessageObjectSerializer
对象作为参数注入(传递)到MessageQueue
对象中——要么在构造函数中,要么在对consumeMessages
方法的调用中。您可以使用DI框架来完成这项工作,但我建议手动完成,以获得概念
现在,如果您为MessageObjectSerializer
创建了一个接口,您可以将其传递到MessageQueue
,然后获得模式的完整值,因为您可以创建mock/stub以便于测试。突然,consumeMessages
不必关注serializeMessageObjects
的行为
下面,我试图说明这种模式。注意,当您想要测试消息时,不必使用MessageObjectSerializer
对象。您可以创建一个mock或stub,它完全执行您希望它执行的操作,并传递它,而不是传递具体的类。这真的让测试变得容易多了。请原谅语法错误。我没有访问Visual Studio的权限,因此它是在文本编辑器中编写的
// THE MAIN CLASS
public class MyMessageQueue()
{
IMessageObjectSerializer _serializer;
//Constructor that takes the gets the serialization logic injected
public MyMessageQueue(IMessageObjectSerializer serializer)
{
_serializer = serializer;
//Also a lot of other injection
}
//Your main method. Now it calls an external object to serialize
public void consumeMessages()
{
//Do all the other stuff
_serializer.serializeMessageObjects()
}
}
//THE SERIALIZER CLASS
Public class MessageObjectSerializer : IMessageObjectSerializer
{
public List<MessageObject> serializeMessageObjects()
{
//DO THE SERILIZATION LOGIC HERE
}
}
//THE INTERFACE FOR THE SERIALIZER
Public interface MessageObjectSerializer
{
List<MessageObject> serializeMessageObjects();
}
//主类
公共类MyMessageQueue()
{
IMessageObjectSerializer\u序列化程序;
//接受的构造函数将注入序列化逻辑
公共MyMessageQueue(IMessageObjectSerializer序列化程序)
{
_序列化程序=序列化程序;
//还有很多其他的注射剂
}
//现在它调用一个外部对象来序列化
公共消息()
{
//做所有其他的事情
_serializer.serializeMessageObjects()
}
}
//序列化程序类
公共类MessageObjectSerializer:IMessageObjectSerializer
{
公共列表序列化MessageObjects()
{
//进行系列化
public class Engine {
public void doActionOnEngine() {}
public void doOtherActionOnEngine() {}
}
public class Car {
private Engine engine;
// the setter is used for dependency injection
public void setEngine(Engine engine) {
this.engine = engine;
}
// notice that there is no getter for engine
public void doActionOnCar() {
engine.doActionOnEngine();
}
public void doOtherActionOnCar() {
engine.doActionOnEngine();
engine.doOtherActionOnEngine(),
}
}