Background
上周在写单例的UT的时候发现UT非常难写,问了同事以后解决了这个问题。现在想复现一下当时的场景。
Gains
- 找到Root Cause
- UT难写,思考需不需要重构代码
经验教训:如果啥时候觉得UT非常难写,想想是不是需要重构一下代码
Singletone Example
Base Code
-
Interface
package com.hqwhqwhq; import com.hqwhqwhq.model.Notification; public interface Publisher { void publish(Notification notification); }
-
Implement
package com.hqwhqwhq; import com.hqwhqwhq.model.Notification; import lombok.NonNull; public class PublisherImpl implements Publisher { @Override public void publish(@NonNull final Notification notification) { } }
-
Factory - V1
package com.hqwhqwhq; public final class PublisherFactory { private PublisherFactory() { throw new UnsupportedOperationException(); } private static final Publisher publisher = createPublisher(); public static Publisher getPublisher() { return publisher; } private static Publisher createPublisher() { return new PublisherImpl(new ExternalPublisherClient()); } }
Pain Point
- Scene1: 如果
ExternalPublisherClient
可以直接new出来,那也没什么问题。
package com.hqwhqwhq;
import com.hqwhqwhq.model.Notification;
import lombok.NonNull;
public final class ExternalPublisherClient {
public void send(@NonNull final Notification notification) {
}
}
- Scene2: 但是我们实际在开发的时候很多外部的
client
是没有办法直接new出来的,受限于证书,环境等的影响。
package com.hqwhqwhq;
import com.hqwhqwhq.model.Notification;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public final class ExternalPublisherClient {
public ExternalPublisherClient() {
if (!getCredential()) {
throw new UnsupportedOperationException();
}
}
public void send(@NonNull final Notification notification) {
}
private Boolean getCredential() {
log.info("Test environment can not get credential.");
return false;
}
}
所以给PublisherFactory
写UT的矛盾点就来了.
- mock
ExternalPublisherClient
-> buildpublisher
. - build
publisher
-> mockExternalPublisherClient
.
后来重写了PublisherFactory
,比较好的解决了这个问题。
Solution
重构PublisherFactory
- PublisherFactory - V2
package com.hqwhqwhq; import java.util.Objects; public final class PublisherFactory { private PublisherFactory() { throw new UnsupportedOperationException(); } private static Publisher publisher; public static synchronized Publisher getPublisher() { if (Objects.isNull(publisher)) { publisher = createPublisher(); } return publisher; } private static Publisher createPublisher() { return new PublisherImpl(new ExternalPublisherClient()); } }
- PublisherFactoryTest
package com.hqwhqwhq; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; @RunWith(PowerMockRunner.class) @PrepareForTest({PublisherFactory.class, ExternalPublisherClient.class}) public class PublisherFactoryTest { @Mock private ExternalPublisherClient client; @Test public void testGetPublisher() throws Exception { PowerMockito.whenNew(ExternalPublisherClient.class).withNoArguments().thenReturn(client); final Publisher expectedPublisher = PublisherFactory.getPublisher(); final Publisher publisher = PublisherFactory.getPublisher(); Assert.assertEquals(expectedPublisher, publisher); PowerMockito.verifyNew(ExternalPublisherClient.class).withNoArguments(); } }