存在這樣一個場景:
當項目啟動時間過長,又沒辦法縮短的時候,寫單元測試就是一個十分耗時的工作,
這工作不在於使用編寫代碼,而在於每次run junit test 都需要完整啟動一次項目,白白浪費寶貴的生命。
當由於某個字段沒有賦值,或者某個簡單判斷寫錯,導致需要再等個3-5分鍾啟動 junit test,是否會想要執行一次san check?
於是乎:
假若能使用controller來調用test類方法的話,那么在本地調試單元測試時,對於一些簡單的代碼修改,
通過熱部署,只需要重新進行一次url訪問就可執行一個完整的單元測試,
無需再次啟動整個項目。
正題:
1. 如何在controller訪問src/test ?
2. 如何編寫 ?
如何在controller訪問src/test
maven項目的默認配置中, src/test目錄是測試目錄,不會被編譯到jar中,也就是在controller調用時會報ClassNotFoundException
解決辦法最好的是在pom文件中修改maven默認的測試目錄,將src/test 作為正常目錄使用
<build><!-- 將測試目錄更改為其他目錄 --> <testSourceDirectory>src/main/test</testSourceDirectory> </build>
需要重新maven-update。update后重新將src/test use for building path
完成后便可以成功啟動項目,並可以正常訪問。
如何編寫
可以做一個參考:
controller,最主要內容在於使用controller時,junit的自動回滾可能不會生效,所以手動設置事務,手動觸發回滾
@RestController @RequestMapping(value = "/junit", produces = "application/json;charset=utf-8") public class TestController { private final Logger logger = LoggerFactory.getLogger(TestController.class); @Autowired private BizTest bizTest; @GetMapping("test")
@Transactional(rollbackFor = Exception.class) public void startJunit() { bizTest.insertTest(); // 手動開啟事務回滾 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } }
測試類,即可以通過其他類訪問,也可以直接執行junit,增加@Componet 或者@Service 還可以通過spring注入方式調用
@Component public class BizTest extends AbstractTest { @Autowired private Biz biz; @Test public void insertTest() { // 測試業務 } }
測試抽象父類,用於直接使用junit測試時的配置
@RunWith(SpringRunner.class) @SpringBootTest(classes = WebApplication.class) @Transactional // @Rollback(false) public abstract class AbstractTest { protected final Logger logger = LoggerFactory.getLogger(AbstractTest.class); protected void println(Object object) { System.out.println(object); } }
改進: Controller 與Test類解耦
直接在controller引用Test類,提交代碼后容易引起打包錯誤,這里就需要對兩者進行解耦。
咱的解決辦法如下:
利用spring動態bean注冊+類限定名來令兩者解耦,並令Test類接受spring容器管理,代碼如下:
@GetMapping("test") @ResponseBody public String startJunit() {
// 測試主類限定名 String testImplName = "com.terra.test.JunitTester";
// 若bean未注冊,則注冊,SpringConetxtUtil可參考其余地方的Spring動態注冊bean if (SpringContextUtil.getBean(testImplName) == null) { registerBean(testImplName); } try { IJunitTest junitTester = SpringContextUtil.getBean(testImplName); junitTester.startTest(); } catch (Exception e) { if (e.getClass() != UnexpectedRollbackException.class) { return e.getMessage(); } } logger.info("執行單元測試成功"); return "操作成功, 執行完成時間:" + DateUtil.getNowDateTime(); } // 注冊bean private void registerBean(String className) { DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) SpringContextUtil.getApplicationContext().getAutowireCapableBeanFactory(); if (beanFactory.getBean(className) == null) { BeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClassName(className); beanFactory.registerBeanDefinition(className, beanDefinition); } } // 單元測試主類接口 public static interface IJunitTest { void startTest(); }
測試主類代碼:
@Component public class JunitTester extends AbstractTest implements IJunitTest { @Autowired private BizTest1 bizTest1; @Autowired private BizTest2 bizTest2; @Transactional(rollbackFor = Exception.class) @Override public void startTest() { // 手動開啟事務回滾 bizTest1.before(); bizTest1.test(); TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } }
這樣做的好處是,環境上的代碼沒有src/test也不會報錯,而本地只需要進行注釋和去除注釋@RequestMapping,便無需要再修改controller代碼
所有單元測試代碼操作便可在測試主類及其測試類中進行,可喜可賀。