/* * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /** * AbstractMekaClassifierTest.java * Copyright (C) 2015 University of Waikato, Hamilton, New Zealand */ package meka.classifiers; import junit.framework.TestCase; import meka.classifiers.multilabel.Evaluation; import meka.classifiers.multilabel.MultiLabelClassifier; import meka.core.MLUtils; import meka.core.Result; import weka.classifiers.CheckClassifier; import weka.classifiers.Classifier; import weka.core.CheckGOE; import weka.core.CheckOptionHandler; import weka.core.Instances; import weka.core.OptionHandler; import weka.core.converters.ConverterUtils; import weka.test.Regression; /** * Abstract test for classifiers within the MEKA framework. * <br> * The following system properties can be set: * <ul> * <li>meka.test.debug [true|false] to set tester object debug flag</li> * <li>meka.test.silent [true|false] to set tester object silent flag</li> * </ul> * * @author fracpete (fracpete at waikato dot ac dot nz) * @version $Revision: 117 $ */ public abstract class AbstractMekaClassifierTest extends TestCase { /** whether to run testers in DEBUG mode */ public boolean DEBUG = System.getProperty("meka.test.debug", "false").equals("true"); /** whether to run testers in SILENT mode */ public boolean SILENT = System.getProperty("meka.test.silent", "true").equals("true"); /** * Dummy class to expose protected methods. */ public static class MekaCheckClassifier extends CheckClassifier { /** * Checks whether the scheme's toString() method works even though the * classifies hasn't been built yet. * * @return index 0 is true if the toString() method works fine */ public boolean[] testToString() { return super.testToString(); } /** * tests for a serialVersionUID. Fails in case the scheme doesn't declare a * UID. * * @return index 0 is true if the scheme declares a UID */ public boolean[] declaresSerialVersionUID() { return super.declaresSerialVersionUID(); } } /** The classifier to be tested */ protected Classifier m_Classifier; /** For testing the classifier */ protected MekaCheckClassifier m_Tester; /** the OptionHandler tester */ protected CheckOptionHandler m_OptionTester; /** for testing GOE stuff */ protected CheckGOE m_GOETester; /** * Initializes the test. * * @param name the name of the test */ public AbstractMekaClassifierTest(String name) { super(name); System.setProperty("weka.test.Regression.root", "src/test/resources"); } /** * configures the CheckClassifier instance used throughout the tests * * @return the fully configured CheckClassifier instance used for testing */ protected MekaCheckClassifier getTester() { MekaCheckClassifier result; result = new MekaCheckClassifier(); result.setSilent(SILENT); result.setClassifier(m_Classifier); result.setNumInstances(20); result.setDebug(DEBUG); return result; } /** * Configures the CheckOptionHandler uses for testing the optionhandling. Sets * the classifier return from the getClassifier() method. * * @return the fully configured CheckOptionHandler * @see #getClassifier() */ protected CheckOptionHandler getOptionTester() { CheckOptionHandler result; result = new CheckOptionHandler(); result.setOptionHandler((OptionHandler) getClassifier()); result.setUserOptions(new String[0]); result.setSilent(SILENT); result.setDebug(DEBUG); return result; } /** * Configures the CheckGOE used for testing GOE stuff. Sets the Classifier * returned from the getClassifier() method. * * @return the fully configured CheckGOE * @see #getClassifier() */ protected CheckGOE getGOETester() { CheckGOE result; result = new CheckGOE(); result.setObject(getClassifier()); result.setSilent(SILENT); result.setDebug(DEBUG); return result; } /** * Called by JUnit before each test method. This implementation creates the * default classifier to test and loads a test set of Instances. * * @exception Exception if an error occurs reading the example instances. */ @SuppressWarnings("unchecked") @Override protected void setUp() throws Exception { m_Classifier = getClassifier(); m_Tester = getTester(); m_OptionTester = getOptionTester(); m_GOETester = getGOETester(); } /** Called by JUnit after each test method */ @SuppressWarnings("unchecked") @Override protected void tearDown() { m_Classifier = null; m_Tester = null; m_OptionTester = null; m_GOETester = null; } /** * Used to create an instance of a specific classifier. * * @return a suitably configured <code>Classifier</code> value */ public abstract Classifier getClassifier(); /** * tests whether the toString method of the classifier works even though the * classifier hasn't been built yet. */ public void testToString() { boolean[] result; result = m_Tester.testToString(); if (!result[0]) { fail("Error in toString() method!"); } } /** * tests whether the scheme declares a serialVersionUID. */ public void testSerialVersionUID() { boolean[] result; result = m_Tester.declaresSerialVersionUID(); if (!result[0]) { fail("Doesn't declare serialVersionUID!"); } } /** * tests the listing of the options */ public void testListOptions() { if (!m_OptionTester.checkListOptions()) { fail("Options cannot be listed via listOptions."); } } /** * tests the setting of the options */ public void testSetOptions() { if (!m_OptionTester.checkSetOptions()) { fail("setOptions method failed."); } } /** * tests whether the default settings are processed correctly */ public void testDefaultOptions() { if (!m_OptionTester.checkDefaultOptions()) { fail("Default options were not processed correctly."); } } /** * tests whether there are any remaining options */ public void testRemainingOptions() { if (!m_OptionTester.checkRemainingOptions()) { fail("There were 'left-over' options."); } } /** * tests the whether the user-supplied options stay the same after setting. * getting, and re-setting again. * * @see #getOptionTester() */ public void testCanonicalUserOptions() { if (!m_OptionTester.checkCanonicalUserOptions()) { fail("setOptions method failed"); } } /** * tests the resetting of the options to the default ones */ public void testResettingOptions() { if (!m_OptionTester.checkSetOptions()) { fail("Resetting of options failed"); } } /** * tests for a globalInfo method */ public void testGlobalInfo() { if (!m_GOETester.checkGlobalInfo()) { fail("No globalInfo method"); } } /** * tests the tool tips */ public void testToolTips() { if (!m_GOETester.checkToolTips()) { fail("Tool tips inconsistent"); } } /** * Returns whether a regression test should be executed. * * @return true if regression test */ protected boolean hasRegressionTest() { return true; } /** * Returns the filenames of the datasets for the regression test (train). * * @return the filenames */ protected String[] getRegressionTrainFiles() { return new String[]{"Music-train.arff"}; } /** * Returns the filenames of the datasets for the regression test (test). * * @return the filenames */ protected String[] getRegressionTestFiles() { return new String[]{"Music-test.arff"}; } /** * Returns the data to use in the regression test. * * @param files the files to load * @return the data */ protected Instances[] getRegressionData(String[] files) { Instances[] result; int i; result = new Instances[files.length]; for (i = 0; i < files.length; i++) { try { result[i] = loadData(files[i]); } catch (Exception e) { System.err.println("Failed to load data: " + files[i]); e.printStackTrace(); return new Instances[0]; } try { MLUtils.prepareData(result[i]); } catch (Exception e) { System.err.println("Failed to prepare data: " + files[i]); e.printStackTrace(); return new Instances[0]; } } return result; } /** * Returns the setups for the regression tests. * * @return the configured classifier(s) */ protected MultiLabelClassifier[] getRegressionSetups() { return new MultiLabelClassifier[]{(MultiLabelClassifier) getClassifier()}; } /** * Turns the results from the regression test into a string. * * @param result the result to process * @return the generated string */ protected String postProcessRegressionResults(Result result) { StringBuilder processed; String[] lines; processed = new StringBuilder(); lines = result.toString().split("\n"); for (String line: lines) { if (line.toLowerCase().contains("time")) continue; if (processed.length() > 0) processed.append("\n"); processed.append(line); } return processed.toString(); } /** * Performs a regression test, if enabled. * * @see #hasRegressionTest() */ public void testRegression() { Instances[] train; Instances[] test; MultiLabelClassifier[] setups; int i; Evaluation eval; Result result; Regression regression; String diff; if (!hasRegressionTest()) return; train = getRegressionData(getRegressionTrainFiles()); test = getRegressionData(getRegressionTestFiles()); setups = getRegressionSetups(); assertEquals("number of train datasets and setups differ", setups.length, train.length); assertEquals("number of test datasets and setups differ", setups.length, test.length); regression = new Regression(getClassifier().getClass()); for (i = 0; i < setups.length; i++) { try { eval = new Evaluation(); result = eval.evaluateModel(setups[i], train[i], test[i]); regression.println(postProcessRegressionResults(result)); } catch (Exception e) { System.err.println("Failed to evaluate classifier #" + i); e.printStackTrace(); fail("Failed to evaluate classifier #" + i + ": " + e); } } try { diff = regression.diff(); if (diff == null) System.err.println("Warning: No reference available, creating."); else if (!diff.equals("")) { fail("Regression test failed. Difference:\n" + diff); } } catch (Exception ex) { System.err.println("Problem during regression testing:"); ex.printStackTrace(); fail("Problem during regression testing.\n" + ex); } } /** * Loads the dataset from disk. * * @param file the dataset to load (e.g., "weka/classifiers/data/something.arff") * @throws Exception if loading fails, e.g., file does not exit */ public static Instances loadData(String file) throws Exception { return ConverterUtils.DataSource.read(file); } }