Unit Test of Singleton Pattern

Background

上周在写单例的UT的时候发现UT非常难写,问了同事以后解决了这个问题。现在想复现一下当时的场景。

Gains

  1. 找到Root Cause
  2. UT难写,思考需不需要重构代码

经验教训:如果啥时候觉得UT非常难写,想想是不是需要重构一下代码

Singletone Example

Base Code

  1. Interface

    package com.hqwhqwhq;
    
    import com.hqwhqwhq.model.Notification;
    
    public interface Publisher {
       void publish(Notification notification);
    }
    
  2. 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) {
      }
    }
    
  3. 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

  1. 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) {
    }
}
  1. 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 -> build publisher.
  • build publisher -> mock ExternalPublisherClient.

后来重写了PublisherFactory,比较好的解决了这个问题。

Solution

重构PublisherFactory

  1. 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());
        }
    }
    
  2. 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();
        }
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值