單元測試之初步使用篇(testng + jmockit + springboot)

單元測試研究

(testng + jmockit + springboot)

環境配置可以參考最下面的參考資料,其中已經非常清晰了。這裏不再描述了

引入spring自動配置

  1. 利用spingTesting 直接使用@ContextConfiguration
//1.ContextConfiguration中注入所需要的類,由於UserServiceImpl中@Autowired了UserInfoDao、AccountDao、 OssConfig,所以都需要注入。
@ContextConfiguration(classes = {UserServiceImpl.class, UserInfoDaoImpl.class, 
AccountDaoImpl.class, OssConfig.class})
//2.需要繼承AbstractTestNGSpringContextTests類
public class UserServiceImplTest extends AbstractTestNGSpringContextTests {

    @Autowired
    private UserServiceImpl serviceInterface;

    @Test
    public void testSpringAutoConfig(){
        Person abc = serviceInterface.getUserInfoByName("abc");
        System.out.println(abc);
    }
}
//3. UserServiceImpl、UserInfoDaoImpl、AccountDaoImpl修飾@Component
  1. 利用配置類@Configuration結合@ContextConfiguration
    利用一個配置類config.java配置完依賴之後,利用
    @ContextConfiguration(classes = Config.class),其他操作和1一樣。
  2. 利用springBootTesting
@SpringBootTest(classes = {UserServiceImpl.class, UserInfoDaoImpl.class, 
AccountDaoImpl.class, OssConfig.class})
public class UserServiceImplTest extends AbstractTestNGSpringContextTests {

    @Autowired
    private UserServiceImpl serviceInterface;

    @Test
    public void testSpringAutoConfig(){
        Person abc = serviceInterface.getUserInfoByName("abc");
        System.out.println(abc);
    }
}

其他和@ContextConfiguration一樣,springboottest也可以使用@Configuration方式
4. 可以使用@SpringBootConfiguration(沒有研究跳過)
5. 使用@ComponentScan掃描Bean,前面都是使用@SpringBootTest或@ContextConfiguration(classes = {….})裝配bran,還可以使用@ComponentScan去掃描指定路徑

@SpringBootTest
@ComponentScan(basePackages = "com.dullbird")
//其中使用@SpringBootTest(classes = {OssConfig.class})或者@ContextConfiguration總是獲取不到配置文件的值,但是使用ComponentScan可以獲取到。
//OssConfig 類如下
@Component
@ConfigurationProperties(prefix = "oss")
public class OssConfig {
    private String endpoint;
    private String outEndpoint;
    private String accessKeyId;
    private String accessKeySecret;
    private String readPublicBucket;
    //省略get和set方法
}
  1. 使用@SpringBootApplication配置
@SpringBootTest
/**
 * 使用SpringBootTest和ContextConfiguration,加載配置文件沒有值,增加ComponentScan掃描就有值
 */
@SpringBootApplication(scanBasePackages = "com.dullbird")

7.@TestPropertySource會覆蓋之前的配置。

@SpringBootApplication(scanBasePackages = "com.dullbird")
@TestPropertySource(properties = {"oss.accessKeyId = aaaaaa"})

mock對象或方法基於jmockit

比如測試類A,類A注入了bean B和C,測試A的方法時,調用了B和C的方法。這樣就引入了很多變量,需要用mock的方式把B和C的方法表現mock掉。接下來有幾種場景。
示例代碼:

//接口
public interface ServiceInterface {
    Person getUserInfoByName(String name);
}
public interface AccountDao {
    String getAccountTest(String name);
}
public interface UserInfoDao {
    Person getUserInfoByName(String name);
    String getTestWord(String context);
}
@Component
public class AccountDaoImpl implements AccountDao {
    @Override
    public String getAccountTest(String name) {
        return name;
    }
}
@Component
public class UserInfoDaoImpl implements UserInfoDao {
    @Override
    public Person getUserInfoByName(String name) {
        return new Person(name, "18");
    }

    @Override
    public String getTestWord(String context) {

        return context;
    }
}
@Component
public class UserServiceImpl implements ServiceInterface {
    public static Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
    @Autowired
    private AccountDao accountDao;
    @Autowired
    private UserInfoDao userInfoDao;
    @Autowired
    private OssConfig ossConfig;
    //需要測試的方法
    @Override
    public Person getUserInfoByName(String name) {
        logger.info("進入類UserServiceImpl,獲取的參數name={}", name);
        String testWord = userInfoDao.getTestWord(name);
        logger.info("====testWord======================={}", testWord);
        String accountTest = accountDao.getAccountTest(name);
        logger.info("===accountTest========================{}", accountTest);
        //可以忽略
        logger.info("===ossConfig========================{}", ossConfig.getAccessKeyId());
        return userInfoDao.getUserInfoByName(name);
    }
}
1.全部都不mock代碼,僅僅注入
@SpringBootTest
@SpringBootApplication(scanBasePackages = "com.dullbird")
public class UserServiceImplTest extends AbstractTestNGSpringContextTests {
    @Autowired
    private UserServiceImpl serviceInterface;

    @Test
    public void testServiceInterface() throws Exception {
        Person abc = serviceInterface.getUserInfoByName("abc");
        System.out.println(abc);
    }

這裏寫圖片描述

2. mock部分內容,比如例子中,僅僅希望mock部分代碼。比如僅mock類userInfoDao的getTestWord(String name )方法和accountDao.getAccountTest(String name)方法。
@SpringBootTest
@SpringBootApplication(scanBasePackages = "com.dullbird")
public class UserServiceImplTest extends AbstractTestNGSpringContextTests {
    //首先需要mock的對象需要注入,這裏要對比一個@Injectable註釋
    @Autowired
    private AccountDao accountDao;
    @Autowired
    private UserInfoDao userInfoDao;
    @Autowired
    private UserServiceImpl serviceInterface;

    @Test
    public void testServiceInterface() throws Exception {
        //進行mock,需要注意傳入了參數userInfoDao,表示僅僅mock實例userInfoDao的getTestWord方法,可以看到userInfoDao.getUserInfoByName方法的表現還是原來的樣子。下面會介紹一下Expectations。
        new Expectations(userInfoDao){
            {
                userInfoDao.getTestWord(anyString);
                result = "userInfoDao mock result";
            }
        };
        new Expectations(accountDao){
            {
                accountDao.getAccountTest(anyString);
                result = "accountDaoImpl mock result ";
            }
        };
        Person abc = serviceInterface.getUserInfoByName("abc");
        System.out.println(abc);
    }
}

這裏寫圖片描述

如果使用@Injectable測試一下
    @Autowired
    @Injectable
    private UserInfoDao userInfoDao;

這裏寫圖片描述
說明了一種情況,如果使用@Injectable修飾實例,實例所有的方法都會返回默認值。比如:
Person getUserInfoByName(String name);
String getTestWord(String context);
兩個方法返回的都是null, 基本類型的返回0.如果其中方法被mock。會按照mock來表現。

Expectations使用
    //參數可以傳入a.class或者實例,如果傳入實例,那麼僅僅mock部分方法。如果傳入.class會mock所有方法
    new Expectations(){
        {
            a.getTest();
            result = null;
            times =1;//調用次數,僅調用一次
            a.test();
            result = "two";
            times =1;
            //如果使用了Expectations,那麼Expectations裏面mock的方法都需要調用,並且需要按照順序。按照當前例子,需要先調用a.getTest(),再調用a.test()。如果多調用或者少調用,就會報錯。如果需要mock方法,但是不一定調用,需要使用NonStrictExpectations,使用方法一樣。調用沒有嚴格要求。
        }
    }
@Tested標籤(概念沒有那麼理解)

JMockit會自動創建註解爲“@Tested”的類對象,並將其做爲被測試對象。“@Tested”標註的必須是實體,不能是接口。否則會報錯
比如上面例子中UserServiceImpl serviceInterface;就是我們需要的測試對象。

@SpringBootTest
@SpringBootApplication(scanBasePackages = "com.dullbird")
public class UserServiceImplTest extends AbstractTestNGSpringContextTests {
    @Injectable
    private AccountDao accountDao;
    @Injectable
    private UserInfoDao userInfoDao;
    //這裏雖然注入了ossConfig,但是由於@Injectable標籤。ossConfig的所有方法獲取的值都是null或者0
    @Autowired
    @Injectable
    private OssConfig ossConfig;
    /**
     *如果使用@Tested描述,那麼UserServiceImpl注入的對象都需要@Injectable,否則會報錯,官方有提到部分mock,但    是這裏我沒有嘗試成功
     */
    @Tested
    private UserServiceImpl serviceInterface;

    @Test
    public void testServiceInterface() throws Exception {
        new Expectations(){
            {
                userInfoDao.getTestWord(anyString);
                result = "userInfoDao mock result";
            }
        };
        new Expectations(){
            {
                accountDao.getAccountTest(anyString);
                result = "accountDaoImpl mock result ";
            }
        };

        Person abc = serviceInterface.getUserInfoByName("abc");
        System.out.println(abc);
        System.out.println(abc.getName());
        System.out.println(abc.getAge());

    }
}

這裏寫圖片描述

留下的問題

  1. 需要自己研究的是不同的mock的作用範圍,有的是類級別的,有的是用例級別的。這個還沒有具體整理處理。
  2. @Tested是否是必須的,使用場景。根據官方文檔說明的部分mock我還沒有實現。

參考資料

感謝龍哥的:
http://blog.longjiazuo.com/archives/5251 試了部分內容
http://www.importnew.com/27523.html 10篇系列文章,雖然不是jmockit。
http://jmockit.github.io/tutorial.html 官方教程文檔
https://blog.csdn.net/bjo2008cn/article/details/54893085 幾個常用標籤的名詞解釋

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章