單元測試研究
(testng + jmockit + springboot)
環境配置可以參考最下面的參考資料,其中已經非常清晰了。這裏不再描述了
引入spring自動配置
- 利用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
- 利用配置類@Configuration結合@ContextConfiguration
利用一個配置類config.java配置完依賴之後,利用
@ContextConfiguration(classes = Config.class),其他操作和1一樣。 - 利用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方法
}
- 使用@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());
}
}
留下的問題
- 需要自己研究的是不同的mock的作用範圍,有的是類級別的,有的是用例級別的。這個還沒有具體整理處理。
- @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 幾個常用標籤的名詞解釋