组件测试
组件测试有几个别名,如子系统测试或集成测试。不管用哪个术语,这样的测试验证了应用程序的若干部分,甚至还可能需要一个完全安装的系统或一组更有限的外部依赖项,如数据库、文件系统、或网络端点。实质上,这些测试验证了不同组件交互以产生预期组合行为的过程。
典型的组件测试需要一个种子数据库(seeded database);此外,测试本身可能要跨架构边界来验证行为。由于组件测试要处理大量的代码,所以实现了更广泛的代码覆盖范围;但运行此类测试要比运行单元测试占用更长时间。
因为组件测试有相关成本——依赖项必须就位并被配置好,所以不该在每次执行构建时运行,而应以规定的时间间隔运行。记住,这些测试本身可能只需几秒钟,但更多的组件测试被添加到套件中时,整个测试时间就增加了,而且往往增加的非常快。
例如,下面的组件测试用DbUnit 播种一个底层数据库。这一测试用例中,设置本身所用的时间比大多数单元测试的运行时间都要长。
//imports removed... public class WordDAOImplTest { private WordDAO dao = null; /** * @testng.before-method = "true" */ private void setUp() throws Exception { final ApplicationContext context = new ClassPathXmlApplicationContext( "spring-config.xml"); this.dao = (WordDAO) context.getBean("wordDAO"); } /** * @testng.before-class = "true" */ public void oneTimeSetUp() throws Exception{ final IDatabaseConnection conn = this.getConnection(); final IDataSet data = this.getDataSet(); try{ DatabaseOperation.CLEAN_INSERT.execute(conn, data); }finally{ conn.close(); } } /** * @testng.test */ public void createWord() { final IWord word = new Word(); word.setSpelling("pabulum"); word.setPartOfSpeech( PartOfSpeechEnum.NOUN.getPartOfSpeech()); final IDefinition defOne = new Definition(); defOne.setDefinition("food"); defOne.setWord(word); final Set defs = new HashSet(); defs.add(defOne); word.setDefinitions(defs); try{ this.dao.createWord(word); }catch(CreateException e){ Assert.fail("CreateException was thrown"); } } private IDataSet getDataSet() throws IOException, DataSetException { return new FlatXmlDataSet( new File("test/conf/words-seed.xml")); } private IDatabaseConnection getConnection() throws ClassNotFoundException, SQLException { final Class driverClass = Class.forName("org.gjt.mm.mysql.Driver"); final Connection jdbcConnection = DriverManager. getConnection("jdbc:mysql://localhost/words", "words", "words"); return new DatabaseConnection(jdbcConnection); } }
但是,组件测试不总是依靠数据库。例如依靠文件系统创建一个耦合,这会增加配置的复杂性,在某些情况下,也会增加所需时间。举个例子,下面的组件测试使用XMLUnit 验证所生成的XML。注意这个测试依靠文件系统路径来比较两个XML文档。
//imports removed... public class BatchDepXMLReportValidationTest { /** * @testng.before-class = "true" */ protected void configure() throws Exception { XMLUnit.setControlParser( "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl"); XMLUnit.setTestParser( "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl"); XMLUnit.setSAXParserFactory( "org.apache.xerces.jaxp.SAXParserFactoryImpl"); XMLUnit.setIgnoreWhitespace(true); } private Filter[] getFilters(){ return new Filter[] { new RegexPackageFilter("java|org"), new SimplePackageFilter("junit.") }; } private Dependency[] getDependencies(){ return new Dependency[] { new Dependency("com.vanward.resource.XMLizable"), new Dependency("com.vanward.xml.Element") }; } /** * @testng.test */ public void assertToXML() throws Exception{ BatchDependencyXMLReport report = new BatchDependencyXMLReport( new Date(9000000), this.getFilters()); report.addTargetAndDependencies( "com.vanward.test.MyTest", this.getDependencies()); report.addTargetAndDependencies( "com.xom.xml.Test", this.getDependencies()); Diff diff = new Diff(new FileReader( new File("./test/conf/report-control.xml")), new StringReader(report.toXML())); Assert.assertTrue( diff.identical(),"XML was not identical"); } }
虽然不应在每次运行构建时执行组件测试,但在将代码提交到储存库之前运行组件测试却是一个好主意。在持续集成环境中,时常运行它很可能是个好主意,比如每小时一次。