/**
* Unit tests for CircularAverage class
*
* Copyright (C) 2014-2015 Dieter Adriaenssens
*
* 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
* 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/>.
*
* @package com.github.ruleant.getback_gps
* @author Dieter Adriaenssens <ruleant@users.sourceforge.net>
*/
package com.github.ruleant.getback_gps.lib;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
/**
* Unit tests for CircularAverage class.
*
* @author Dieter Adriaenssens <ruleant@users.sourceforge.net>
*/
@RunWith(RobolectricTestRunner.class)
public class CircularAverageTest {
/**
* Value for alpha parameter.
*/
private static final float ALPHA_VALUE = 0.5f;
/**
* Accuracy.
*/
private static final double ACCURACY = 0.0001;
/**
* Angle first quadrant.
*/
private static final float ANGLE_Q1 = 40;
/**
* Angle second quadrant.
*/
private static final float ANGLE_Q2 = 130;
/**
* Angle third quadrant.
*/
private static final float ANGLE_Q3 = 220;
/**
* Angle fourth quadrant.
*/
private static final float ANGLE_Q4 = 310;
/**
* Add step value 30.
*/
private static final float STEP_30 = 30;
/**
* Add step value 100.
*/
private static final float STEP_100 = 100;
/**
* Add step value 170.
*/
private static final float STEP_170 = 170;
/**
* Add step value 180.
*/
private static final float STEP_180 = 180;
/**
* Value reached after 1 cycle with alpha = 0.5 : 50 %.
*/
private static final float CYCLE1 = 0.5f;
/**
* Value reached after 2 cycles with alpha = 0.5 : 75 %.
*/
private static final float CYCLE2 = 0.75f;
/**
* Value reached after 3 cycles with alpha = 0.5 : 87.5 %.
*/
private static final float CYCLE3 = 0.875f;
/**
* Value reached after 4 cycles with alpha = 0.5 : 93.75 %.
*/
private static final float CYCLE4 = 0.9375f;
/**
* Value reached after 5 cycles with alpha = 0.5 : 96.875 %.
*/
private static final float CYCLE5 = 0.96875f;
/**
* Exception message when value is out of range.
*/
private static final String MESSAGE_VALUE_RANGE
= "parameter alpha is not in range 0.0 .. 1.0";
/**
* Expected Exception.
*/
@Rule
public final ExpectedException thrown = ExpectedException.none();
/**
* Tests empty value.
*/
@Test
public final void testNoValue() {
assertEquals(0.0f, CircularAverage.getAverageValue(0, 0, 0), ACCURACY);
}
/**
* Tests range of alpha parameter.
*/
@Test
public final void testAlphaParameterRange() {
// valid range for parameter alpha
assertEquals(0.0f, CircularAverage.getAverageValue(0, 0, 0), ACCURACY);
assertEquals(
0.0f,
CircularAverage.getAverageValue(0, 0, ALPHA_VALUE),
ACCURACY);
assertEquals(0.0f, CircularAverage.getAverageValue(0, 0, 1), ACCURACY);
}
/**
* Tests out of range value, smaller than lowest allowed value.
*/
@Test
public final void testOutOfRangeValueSmaller() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage(MESSAGE_VALUE_RANGE);
// invalid range for parameter alpha
CircularAverage.getAverageValue(0, 0, -1);
fail("Expected an IllegalArgumentException to be thrown");
}
/**
* Tests out of range value, bigger than highest allowed value.
*/
@Test
public final void testOutOfRangeValueBigger() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage(MESSAGE_VALUE_RANGE);
CircularAverage.getAverageValue(0, 0, 2);
fail("Expected an IllegalArgumentException to be thrown");
}
/**
* Tests getAverageValue() method, with a positive step.
*/
@Test
public final void testAverageValuePositiveStep() {
// initial value in first quadrant
// step to same quadrant
testAverageValueAfterStep(ANGLE_Q1, STEP_30);
// step to next quadrant
testAverageValueAfterStep(ANGLE_Q1, STEP_100);
// step to opposite quadrant
testAverageValueAfterStep(ANGLE_Q1, STEP_170);
// step to inverse angle
testAverageValueAfterStep(ANGLE_Q1, STEP_180);
// initial value in second quadrant
// step to same quadrant
testAverageValueAfterStep(ANGLE_Q2, STEP_30);
// step to next quadrant
testAverageValueAfterStep(ANGLE_Q2, STEP_100);
// step to opposite quadrant
testAverageValueAfterStep(ANGLE_Q2, STEP_170);
// step to inverse angle
testAverageValueAfterStep(ANGLE_Q2, STEP_180);
// initial value in third quadrant
// step to same quadrant
testAverageValueAfterStep(ANGLE_Q3, STEP_30);
// step to next quadrant
testAverageValueAfterStep(ANGLE_Q3, STEP_100);
// bigger steps in testAverageValuePositiveStepCrossMax()
// initial value in fourth quadrant
// step to same quadrant
testAverageValueAfterStep(ANGLE_Q4, STEP_30);
// bigger steps in testAverageValuePositiveStepCrossMax()
}
/**
* Tests getAverageValue() method, with a positive step,
* crossing maximum value, < 360° -> > 0°.
*/
@Test
public final void testAverageValuePositiveStepCrossMax() {
// initial value in third quadrant
// step to opposite quadrant
testAverageValueAfterStep(ANGLE_Q3, STEP_170);
// bigger steps in testAverageValuePositiveStepCrossMax()
// initial value in fourth quadrant
// step to next quadrant
testAverageValueAfterStep(ANGLE_Q4, STEP_100);
// step to opposite quadrant
testAverageValueAfterStep(ANGLE_Q4, STEP_170);
}
/**
* Tests getAverageValue() method, with a negative step.
*/
@Test
public final void testAverageValueNegativeStep() {
// initial value in fourth quadrant
// step to same quadrant
testAverageValueAfterStep(ANGLE_Q4, -1 * STEP_30);
// step to next quadrant
testAverageValueAfterStep(ANGLE_Q4, -1 * STEP_100);
// step to opposite quadrant
testAverageValueAfterStep(ANGLE_Q4, -1 * STEP_170);
// step to inverse angle
testAverageValueAfterStep(ANGLE_Q4, -1 * STEP_180);
// initial value in third quadrant
// step to same quadrant
testAverageValueAfterStep(ANGLE_Q3, -1 * STEP_30);
// step to next quadrant
testAverageValueAfterStep(ANGLE_Q3, -1 * STEP_100);
// step to opposite quadrant
testAverageValueAfterStep(ANGLE_Q3, -1 * STEP_170);
// step to inverse angle
testAverageValueAfterStep(ANGLE_Q3, -1 * STEP_180);
// initial value in second quadrant
// step to same quadrant
testAverageValueAfterStep(ANGLE_Q2, -1 * STEP_30);
// step to next quadrant
testAverageValueAfterStep(ANGLE_Q2, -1 * STEP_100);
// bigger steps in testAverageValueNegativeStepCrossMin()
// initial value in first quadrant
// step to same quadrant
testAverageValueAfterStep(ANGLE_Q1, -1 * STEP_30);
// bigger steps in testAverageValueNegativeStepCrossMin()
}
/**
* Tests getAverageValue() method, with a positive step,
* crossing minimum value, > 0° -> < 360°.
*/
@Test
public final void testAverageValueNegativeStepCrossMin() {
// initial value in first quadrant
// step to next quadrant
testAverageValueAfterStep(ANGLE_Q1, -1 * STEP_100);
// step to opposite quadrant
testAverageValueAfterStep(ANGLE_Q1, -1 * STEP_170);
// initial value in second quadrant
// step to opposite quadrant
testAverageValueAfterStep(ANGLE_Q2, -1 * STEP_170);
}
/**
* Tests apply 180° degree step.
*/
@Test
public final void testAverageValue180Step() {
// value will move clockwise
testAverageValueAfterStep(FormatUtils.CIRCLE_ZERO, STEP_180);
testAverageValueAfterStep(FormatUtils.CIRCLE_1Q, STEP_180);
// value will move counter-clockwise
testAverageValueAfterStep(FormatUtils.CIRCLE_HALF, -1 * STEP_180);
testAverageValueAfterStep(FormatUtils.CIRCLE_3Q, -1 * STEP_180);
}
/**
* Tests getAverageValue() in 5 cycles after a step is applied.
*
* @param initialValue Initial value
* @param stepValue Step to apply to initial value
*/
private void testAverageValueAfterStep(final float initialValue,
final float stepValue) {
// check initial state.
assertEquals(
FormatUtils.normalizeAngle(initialValue),
CircularAverage.getAverageValue(initialValue, initialValue,
ALPHA_VALUE),
ACCURACY
);
// with an alpha value of .5, the new value should be >95%
// of the set point value after 5 cycles.
float setPoint = (float) FormatUtils.normalizeAngle(
initialValue + stepValue);
// cycle 1 : 50%
float expectedCycle1 = (float) FormatUtils.normalizeAngle(
initialValue + CYCLE1 * stepValue);
assertEquals(
expectedCycle1,
CircularAverage.getAverageValue(initialValue,
setPoint, ALPHA_VALUE),
ACCURACY
);
// cycle 2 : 75%
float expectedCycle2 = (float) FormatUtils.normalizeAngle(
initialValue + CYCLE2 * stepValue);
assertEquals(
expectedCycle2,
CircularAverage.getAverageValue(expectedCycle1,
setPoint, ALPHA_VALUE),
ACCURACY
);
// cycle 3 : 87.5%
float expectedCycle3 = (float) FormatUtils.normalizeAngle(
initialValue + CYCLE3 * stepValue);
assertEquals(
expectedCycle3,
CircularAverage.getAverageValue(expectedCycle2,
setPoint, ALPHA_VALUE),
ACCURACY
);
// cycle 4 : 93.75%
float expectedCycle4 = (float) FormatUtils.normalizeAngle(
initialValue + CYCLE4 * stepValue);
assertEquals(
expectedCycle4,
CircularAverage.getAverageValue(expectedCycle3,
setPoint, ALPHA_VALUE),
ACCURACY
);
// cycle 5 : 96.875%
float expectedCycle5 = (float) FormatUtils.normalizeAngle(
initialValue + CYCLE5 * stepValue);
assertEquals(
expectedCycle5,
CircularAverage.getAverageValue(expectedCycle4,
setPoint, ALPHA_VALUE),
ACCURACY
);
}
}