1 2 3 package de.tivano.junit; 4 5 import java.io.IOException ; 6 import java.io.InputStream ; 7 import java.lang.reflect.Constructor ; 8 import java.lang.reflect.Field ; 9 import java.lang.reflect.InvocationTargetException ; 10 import java.lang.reflect.Method ; 11 import java.util.Enumeration ; 12 import java.util.Iterator ; 13 import java.util.Properties ; 14 import java.util.Set ; 15 import java.util.TreeSet ; 16 17 import junit.framework.AssertionFailedError; 18 import junit.framework.Test; 19 import junit.framework.TestCase; 20 import junit.framework.TestFailure; 21 import junit.framework.TestResult; 22 import junit.framework.TestSuite; 23 24 120 public class ParametrizedTestSuite extends TestSuite { 121 122 private Properties TEST_DATA = null; 123 124 125 static class Warning extends TestCase { 126 private final String MESSAGE; 127 public Warning(String message) { 128 super("warning"); 129 MESSAGE = message; 130 } 131 protected void runTest() { fail(MESSAGE); } 132 } 133 134 138 class ParametrizedTestWrapper extends TestCase { 139 final Test WRAPPED; 140 143 public ParametrizedTestWrapper(Test test) { 144 super(null); 145 if (test instanceof TestCase) 146 setName(((TestCase)test).getName()); 147 WRAPPED = test; 148 } 149 150 public Test getTest() { return WRAPPED; } 151 152 153 public void run(TestResult result) { 154 result.startTest(this); 155 if (parametrizeTest(result)) { 156 TestResult tmp = new TestResult(); 160 WRAPPED.run(tmp); 161 Enumeration errors = tmp.errors(); 162 while (errors.hasMoreElements()) { 163 Throwable err = ((TestFailure)errors.nextElement()).thrownException(); 164 result.addError(this, err); 165 } 166 Enumeration failures = tmp.failures(); 167 while (failures.hasMoreElements()) { 168 AssertionFailedError err = 169 (AssertionFailedError)((TestFailure)failures.nextElement()).thrownException(); 170 result.addFailure(this, err); 171 } 172 } 173 result.endTest(this); 174 } 175 176 177 private String getKeyPrefix() { 178 return ParametrizedTestSuite.this.getName(); 179 } 180 181 185 private boolean parametrizeTest(TestResult result) { 186 boolean success = true; 187 Enumeration keys = TEST_DATA.keys(); 188 String keyPrefix = getKeyPrefix() + "."; 189 int prefixLen = keyPrefix.length(); 190 Test test = WRAPPED; 191 Class testClass = test.getClass(); 192 while (keys.hasMoreElements()) { 193 String key = keys.nextElement().toString(); 194 if (!key.startsWith(keyPrefix) || key.length() <= prefixLen) { 198 continue; 199 } 200 String attributeName = key.substring(prefixLen); 201 try { 204 Method setterMethod = 205 findSetterMethod(testClass, attributeName); 206 Field field = null; 207 if (setterMethod == null) 208 field = findPublicField(testClass, attributeName); 209 if (setterMethod == null && field == null) { 210 fail("Cannot set value for attribute '" + attributeName + 211 "': No setter method or public member field found " + 212 "with that name."); 213 } 214 Object value; 215 if (setterMethod != null) { 216 value = parseAttributeValue(attributeName, 220 TEST_DATA.getProperty(key), 221 setterMethod.getParameterTypes()[0]); 222 223 Object [] args = { value }; 224 try { 225 setterMethod.invoke(test, args); 226 } catch (InvocationTargetException e) { 227 Throwable cause = e.getTargetException(); 228 if (cause instanceof RuntimeException ) { 229 throw (RuntimeException )cause; 230 } else if (cause instanceof Error ) { 231 throw (Error )cause; 232 } else fail("Exception in " + setterMethod.getName() + 233 ": " + cause.toString()); 234 } catch (IllegalAccessException e) { 235 fail("Cannot set value for attribute '" + attributeName + 236 "': " + e.toString()); 237 } 238 239 } else { 240 value = parseAttributeValue(attributeName, 241 TEST_DATA.getProperty(key), 242 field.getType()); 243 try { 244 field.set(test, value); 245 } catch (IllegalAccessException e) { 246 fail("Cannot set value for attribute '" + attributeName + 247 "': " + e.toString()); 248 } 249 } 250 } catch (AssertionFailedError e) { 251 result.addFailure(this, e); 252 success = false; 253 } 254 } 255 return success; 256 } 257 258 265 private Method findSetterMethod(Class testClass, String attrName) { 266 String methodName = "set" + Character.toUpperCase(attrName.charAt(0)) 267 + attrName.substring(1); 268 Method [] allMethods = testClass.getMethods(); 269 270 Method candidate = null; 274 boolean foundMatchingName = false; 275 int matchingMethodCount = 0; 276 for (int i=0; i<allMethods.length; i++) { 277 if (!allMethods[i].getName().equals(methodName)) continue; 278 foundMatchingName = true; 279 Class [] params = allMethods[i].getParameterTypes(); 280 if (params.length != 1) continue; 281 if (isValidAttributeType(params[0])) { 282 candidate = allMethods[i]; 283 matchingMethodCount++; 284 } 285 } 286 if (!foundMatchingName) return null; 289 290 if (matchingMethodCount != 1) { 295 fail("Cannot set value for attribute '" + attrName + "': " 296 + (matchingMethodCount==0?"No":"More than one") 297 + " setter method with compatible parameter types found."); 298 } 299 300 return candidate; 302 } 303 304 310 private Field findPublicField(Class testClass, String fieldName) { 311 try { 312 Field field = testClass.getField(fieldName); 313 if (!isValidAttributeType(field.getType())) { 314 fail("Cannot set value for attribute '" + fieldName + 315 "': Public member field has incompatible type '" + 316 field.getType().getName() + "'"); 317 } 318 return field; 319 } catch (NoSuchFieldException e) { 320 return null; 321 } 322 } 323 324 325 private boolean isValidAttributeType(Class type) { 326 if (type.isPrimitive() && !type.equals(Void.TYPE)) return true; 327 if (Boolean .class.equals(type) || Character .class.equals(type) 328 || Byte .class.equals(type) || Short .class.equals(type) 329 || Integer .class.equals(type) || Long .class.equals(type) 330 || Float .class.equals(type) || Double .class.equals(type) 331 || String .class.equals(type)) return true; 332 333 final Class [] ARG_TYPES = { String .class }; 337 try { 338 type.getConstructor(ARG_TYPES); 339 return true; 340 } catch (NoSuchMethodException e) { 341 return false; 342 } 343 } 344 345 349 private Object parseAttributeValue(String name, String value, Class type) { 350 try { 351 if (value == null) return value; 352 if (Boolean.TYPE.equals(type) || Boolean .class.equals(type)) { 353 return Boolean.valueOf(value); 354 } else if (Character.TYPE.equals(type) || Character .class.equals(type)) { 355 if (value.length() != 1) { 356 fail("Cannot set attribute '" + name + "' to value '" + 357 value + "': Value too long."); 358 } 359 return new Character (value.charAt(0)); 360 } else if (Byte.TYPE.equals(type) || Byte .class.equals(type)) { 361 return Byte.valueOf(value); 362 } else if (Short.TYPE.equals(type) || Short .class.equals(type)) { 363 return Short.valueOf(value); 364 } else if (Integer.TYPE.equals(type) || Integer .class.equals(type)) { 365 return Integer.valueOf(value); 366 } else if (Long.TYPE.equals(type) || Long .class.equals(type)) { 367 return Long.valueOf(value); 368 } else if (Float.TYPE.equals(type) || Float .class.equals(type)) { 369 return Float.valueOf(value); 370 } else if (Double.TYPE.equals(type) || Double .class.equals(type)) { 371 return Double.valueOf(value); 372 } else if (String .class.equals(type)) { 373 return value; 374 } else { 375 final Class [] ARG_TYPES = { String .class }; 376 Constructor c = type.getConstructor(ARG_TYPES); 377 try { 378 final Object [] ARG = { value }; 379 return c.newInstance(ARG); 380 } catch (InvocationTargetException e) { 381 throw e.getTargetException(); 382 } 383 } 384 } catch (AssertionFailedError e) { 385 throw e; 386 } catch (Throwable t) { 387 fail("Cannot set attribute '" + name + "' to value '" + 388 value + "': " + t.toString()); 389 return null; 392 } 393 } 394 395 public String toString() { 396 return WRAPPED.toString() + "#" + getKeyPrefix(); 397 } 398 399 } 400 401 409 private Properties prepareTestData(Properties rawData, boolean clone) { 410 if (rawData == null) { 411 super.addTest(warning("No test data specified for test suite '" + getName() + "'!")); 412 return new Properties (); 413 } else if (rawData.isEmpty()) { 414 super.addTest(warning("Test data definition for test suite '" + getName() + "' is empty!")); 415 } 416 if (clone) return (Properties )rawData.clone(); 417 else return rawData; 418 } 419 420 428 private Properties findTestData(String name) { 429 String resourceName = name + ".properties"; 430 InputStream res = getClass().getResourceAsStream(resourceName); 431 432 Properties prop = new Properties (); 433 if (res == null) { 434 super.addTest(warning("Cannot load test data: Resource '" 435 + resourceName + "' not found!")); 436 } else { 437 try { 438 prop.load(res); 439 } catch (IOException e) { 440 super.addTest(warning("Cannot load test data from resource '" 441 + resourceName +"': " + e.toString())); 442 } 443 } 444 return prepareTestData(prop, false); 445 } 446 447 456 private Properties findTestData(Class testClass) { 457 return findTestData("/" + testClass.getName().replace('.', '/')); 458 } 459 460 466 public ParametrizedTestSuite(Properties testData) { 467 this(null, testData); 468 } 469 470 478 public ParametrizedTestSuite(String name, Properties testData) { 479 super(name); 480 TEST_DATA = prepareTestData(testData, true); 481 } 482 483 491 public ParametrizedTestSuite(String name) { 492 super(name); 493 TEST_DATA = findTestData(name); 494 } 495 496 523 public ParametrizedTestSuite(Class testClass) { 524 super(testClass.getName()); 525 TEST_DATA = findTestData(testClass); 526 if (!TEST_DATA.isEmpty()) { 527 addTestSuite(testClass); 528 } 529 } 530 531 539 private ParametrizedTestSuite(Class testClass, String name, Properties testData) { 540 super(testClass); 549 TEST_DATA = testData; 550 setName(name); 551 } 552 553 565 protected Test warning(String message) { 566 return new Warning(message); 567 } 568 569 572 private Set getTestDataKeys() { 573 Set keys = new TreeSet (); 576 Enumeration allKeys = TEST_DATA.keys(); 577 while (allKeys.hasMoreElements()) { 578 String rawKey = allKeys.nextElement().toString(); 579 int endIdx = rawKey.lastIndexOf('.'); 580 keys.add((endIdx!=-1?rawKey.substring(0,endIdx):rawKey)); 581 } 582 return keys; 583 } 584 585 615 public void addTestSuite(Class testClass) { 616 Iterator keys = getTestDataKeys().iterator(); 617 while (keys.hasNext()) { 618 super.addTest(new ParametrizedTestSuite(testClass, 621 keys.next().toString(), 622 TEST_DATA)); 623 } 624 } 625 626 634 public void addTest(Test test) { 635 if (test instanceof Warning || test instanceof TestSuite) super.addTest(test); 641 else super.addTest(new ParametrizedTestWrapper(test)); 642 } 643 } 644 | Popular Tags |