Unit testing with generated inputs and expected outputs - unit-testing

I'm writing some simple unit tests for a method which acts like a database query: it returns a list of results that can be customised using a limit (max of n results) and a page (the p'th set of n results). I'd like to test a few cases, such as:
Return all the results
Return the first page of a limited result set
Return an aribtrary page of a limited result set
Return the last page of a limited result set which overruns the total number of results
Return a page beyond the total number of results
I need to mock the results (effectively names) that are returned, so I'm just generating them with a counter to make a list of "Name #1", "Name #2", etc. for as many as I need. Each test looks (roughly) like:
public void testGetMockCandidatesLimited() throws Exception {
int numResults = 4;
setupMock(numResults);
results = queryFunction(...);
// Check that the expected number of results was returned
assertEquals(numResults, results.size());
// Check that the results are correct and in order
for (int i = 0; i < numResults; i++) {
assertEquals(result.get(i).getName(), "Name #" + (i + 1));
}
}
My question is: is it okay to 'generate' the expected answers in this way? This is a trivial example, but the next step was to write a test to write a test to get the second page of two results each, and I had:
final int TEST_LIMIT = 2, TEST_PAGE = 1;
// Check that the expected number of results was returned
assertEquals(numResults, results.size());
// Check that the results are correct and in order
for (int i = TEST_LIMIT * TEST_PAGE; i < TEST_LIMIT * TEST_PAGE + TEST_LIMIT; i++) {
assertEquals(result.get(i).getName(), "Name #" + (i + 1));
}
Now it just so happens that this test is 'correct' in the sense that i will take on the values that I expect it to and ensure that the results are "Name #3" and "Name #4". However it also happens that my queryFunction calculates which results to return in the same way (limit * page + limit kind of thing).
This is concerning because if the test generates the expected answers in the same way that the unit under test does, it won't detect if there's a bug in that approach. However naming the input values with constants makes the tests much more readable than just have magic integers between 1 and 4 plugged in everywhere.
What is the best way to deal with this situation, for both readability and maintainability (e.g. changing values in the future, should you need to)?

In asking this question I think I've convinced myself that the solution is to define the expected values as constants up front as well, such as:
final int LIMIT = 2, PAGE = 1, MIN_RESULT = 3, MAX_RESULT = 4;
... and then use those values in the assertions. This retains all the readability of named key values, and discourages the temptation to generate the expected outputs. In this case I would still be using a loop to generate each expected name between Name #MIN and Name #MAX, but the bounds themselves would be explicitly static.
Is this as good as it gets, or are there better ways to deal with this repetitiveness?

Related

Understanding & Converting ThinkScripts CompoundValue Function

I'm currently converting a ThinkScript indicator to C#, however, I've run into this CompoundValue function and I'm unsure how to covert it.
The documents reads :
Calculates a compound value according to following rule: if a bar
number is greater than length then the visible data value is returned,
otherwise the historical data value is returned. This function is used
to initialize studies with recursion.
Example Use:
declare lower;
def x = CompoundValue(2, x[1] + x[2], 1);
plot FibonacciNumbers = x;
My interpretation:
Based on description and example. It appears we are passing a calculation in x[1] + x[2] and it performing this calculation on the current bar and the previous bar (based on first param of 2). I'm unsure what the parameter 1 is for.
My Question:
Please explain what this function is actually doing. If possible, please illustrate how this method works using pseudo-code.
For the TLDR; crowd, some simple code that hopefully explains what the CompoundValue() function is trying to do, and which might help in converting it's functionality:
# from: Chapter 12. Past/Future Offset and Prefetch
# https://tlc.thinkorswim.com/center/reference/thinkScript/tutorials/Advanced/Chapter-12---Past-Offset-and-Prefetch
# According to this tutorial, thinkScript uses the highest offset, overriding
# all lower offsets in the script - WOW
declare lower;
# recursive addition using x[1] is overridden by 11 in the plot for
# Average(close, 11) below; SO `x = x[1] + 1` becomes `x = x[11] + 1`
def x = x[1] + 1;
# using CompoundValue, though, we can force the use of the *desired* value
# arguments are:
# - length: the number of bars for this variable's offset (`1` here)
# - "visible data": value to use IF VALUES EXIST for a bar (a calculation here)
# - "historical data": value to use IF NO VALUE EXISTS for a bar (`1` here)
def y = CompoundValue(1, y[1] + 1, 1);
# *plotting* this Average statement will change ALL offsets to 11!
plot Average11 = Average(close, 11);
# `def`ing the offset DOES NOT change other offsets, so no issue here
# (if the `def` setup DID change the offsets, then `x[1]` would
# become `x[14]`, as 14 is higher than 11. However, `x[1]` doesn't change.
def Average14 = Average(close, 14);
plot myline = x;
plot myline2 = y;
# add some labels to tell us what thinkScript calculated
def numBars = HighestAll(BarNumber());
AddLabel(yes, "# Bars on Chart: " + numBars, Color.YELLOW);
AddLabel(yes, "x # bar 1: " + GetValue(x, numBars), Color.ORANGE);
AddLabel(yes, "x # bar " + numBars + ": " + x, Color.ORANGE);
AddLabel(yes, "y # bar 1: " + GetValue(y, numBars), Color.LIGHT_ORANGE);
AddLabel(yes, "y # bar " + numBars + ": " + y, Color.ORANGE);
Now, some, er, lots of details...
First, a quick note on "offset" values:
thinkScript, like other trading-related languages, uses an internal looping system. This is like a for loop, iterating through all the "periods" or "bars" on a chart (eg, 1 bar = 1 day on a daily chart; 1 bar = 1 minute on a 1 minute intraday chart, etc). Every line of code in thinkScript is run for each and every bar in the chart or length of time specified in the script.
As noted by the OP, x[1] represents an offset of one bar before the current bar the loop is processing. x[2] represents two bars before the current bar, and so on. Additionally, it's possible to offset into the future by using negative numbers: x[-1] means one bar ahead of the current bar, for example.
These offsets work similarly to the for loop in C#, except they're backwards: x[0] in C# would represent the current x value, as it would in thinkScript; however, moving forward in the loop, x[1] would be the next value, and x[-1] wouldn't exist because, well, there is no past value before 0. (In general, of course! One can definitely loop with negative numbers in C#. The point is that positive offset indices in thinkScript represent past bars, while negative offset indices in thinkScript represent future bars - not the case in C#.)
Also important here is the concept of "length": in thinkScript, length parameters represent the distance you want to go - like the offset, but a range instead of one specific bar. In my example code above, I used the statement plot Average11 = Average(close, 11); In this case, the 11 parameter represents plotting the close for a period of 11 bars, ie, offsets x[0] through x[10].
Now, to explain the CompoundValue() function's purpose:
The Chapter 12. Past/Future Offset and Prefetch thinkScript tutorial explains that thinkScript actually overrides smaller offset or length values with the highest value in a script. What that means is that if you have two items defined as follows:
def x = x[1] + 1;
plot Average11 = Average(close, 11);
thinkScript will actually override the x[1] offset with the higher length used in the Average statement - therefore causing x[1] to become x[11]!
Yike! That means that the specified offsets, except the highest offset, mean nothing to thinkScript! So, wait a minute - does one have to use all the same offsets for everything, then? No! This is where CompoundValue() comes in...
That same chapter explains that CompoundValue() allows one to specify an offset for a variable that won't be changed, even if a higher offset exists.
The CompoundValue() function, with parameter labels, looks like this:
CompoundValue(length, "visible data", "historical data")
As the OP noted, this isn't really particularly clear. Here's what the parameters represent:
length: the offset number of bars for this variable.
In our example, def x = x[1] + 1, there is a 1 bar offset, so our statement starts as CompoundValue(length=1, ...). If instead, it was a larger offset, say 14 bars, we'd put CompoundValue(length=14, ...)
"visible data": the value or calculation thinkScript should perform if DATA IS AVAILABLE for the current bar.
Again, in our example, we're using a calculation of x[1] + 1, so CompoundValue(length=1, "visible data"=(x[1] + 1), ...). (Parentheses around the equation aren't necessary, but may help with clarity.)
"historical data": the value to use if NO DATA IS AVAILABLE for the current bar.
In our example, if no data is available, we'll use a value of 1.
Now, in thinkScript, parameter labels aren't required if the arguments are in order and/or defaults are supplied. So, we could write this CompoundValue statement like this without the labels:
def y = CompoundValue(1, y[1] + 1, 1);
or like this with the labels:
def y = CompoundValue(length=1, "visible data"=(y[1] + 1), "historical data"=1);
(Note that parameter names containing spaces have to be surrounded by double quotes. Single-word parameter names don't need the quotes. Also, I've placed parens around the equation just for the sake of clarity; this is not required.)
In summary: CompoundValue(...) is needed to ensure a variable uses the actual desired offset/number of bars in a system (thinkScript) that otherwise overrides the specified offsets with a higher number if present.
If all the offsets in a script are the same, or if one is using a different programming system, then CompoundValue() can simply be broken down into its appropriate calculations or values, eg def x = x[1] + 1 or, alternatively, an if/else statement that fills in the values desired at whatever bars or conditions are needed.
Please let me provide two equivalent working versions of the code in thinkscript itself. We use this approach to prove equivalence by subtracting the equivalent outputs from each other - the result should be 0.
# The original Fibonacci code with a parameter "length" added.
# That parameter is the first parameter of the CompoundValue function.
declare lower;
def length = 2;
def x = CompoundValue(length, x[1] + x[2], 1);
# plot FibonacciNumbers = x;
# Equivalent code using the `if` statement:
def y;
if(BarNumber() > length){
# Visible data. This is within the guarded branch of the if statement.
# Historical data y[1] (1 bar back) and y[2] (2 bars back) is available
y = y[1] + y[2];
}else{
# Not enough historical data so we use the special case satisfying the
# original rule.
y = 1;
}
plot FibonacciNumbersDiff = y - x;
Thinkscript "recursion" is a somewhat inflated term. The function name CompoundValue is not very helpful so it may create confusion.
The version using the if statement is more useful in general because when walking through the time series of bars, we often need a program structure with multiple nested if statements - this cannot be done with the CompoundValue function. Please see my other articles which make use of this in the context of scanning.
In Java, using the same structure, it looks like this:
int size = 100;
int length = 2;
int[] values = new int[size];
for(int index = 1; index < size; index++){
if(index > length){
values[index] = values[index - 1] + values[index - 2];
}else{
values[index] = 1;
}
}
The fundamental difference is the for loop which is not present in the thinkscript code. thinkscript provides the loop in a kind of inversion of control where it executes user code multiple times, once for each bar.

thinkscript if function useless in important case

The thinkscript if function fails to branch as expected in an important case. The following test case can be used to reproduce this severe bug / defect.
In a nutshell, an if statement may normally be used to prevent a function call from being executed if one of its function parameters is invalid. We show that this is not the case. In fact, both branches are executed, including the branch not meeting the if condition.
This absolutely defeats the purpose of the test of the if condition, the test that every if statement in every language has.
Following is some sample code that shows the problem on a chart. The result can be seen by clicking on the "i" message icon blinking in the left top corner of the chart:
Folding: 'from' cannot be greater than 'to': 1 > -1.
# Get the current offset from the right edge from BarNumber()
# BarNumber(): The current bar number. On a chart, we can see that the number increases
# from left 1 to number of bars e.g. 140 at the right edge.
def barNumber = BarNumber();
def barCount = HighestAll(barNumber);
# rightOffset: 0 at the right edge, i.e. at the rightmost bar,
# increasing from right to left.
def rightOffset = barCount - barNumber;
# This script gets the minimum value from data in the offset range between startIndex
# and endIndex. It serves as a functional but not direct replacement for the
# GetMinValueOffset function where a dynamic range is required. Expect it to be slow.
script getMinValueBetween {
input data = low;
input startIndex = 0;
input endIndex = 0;
plot minValue = fold index = startIndex to endIndex with minRunning = Double.POSITIVE_INFINITY do Min(GetValue(data, index), minRunning);
}
# Call this only once at the last bar.
script buildConditions {
input startIndex = 1;
input endIndex = -1;
# Since endIndex < startIndex, getMinValueBetween() should never
# be executed. However it is executed nevertheless.
plot minValue = if (endIndex > startIndex) then getMinValueBetween(low, startIndex, endIndex) else close[startIndex];
}
plot scan;
if (rightOffset == 0) {
scan = buildConditions();
} else {
scan = 0;
}
declare lower;
The question has the answer in its first sentence.
One might contemplate using the if statement (vs the if function). However, that is broken as demonstrated in
thinkscript if statement failure
At least as of April 2021, the documentation for the if reserved word says:
... while the if-expression always calculates both then and else branches, the if-statement only calculates the branch defined by whether the condition is true or false.
(bolding and italics mine)
Definitely confusing and unexpected behavior!

Prevent a user from entering a value which would cause integer to overflow

I'm very new to C++ programming, and have written a simple program to calculate the factorial of an integer provided by the user. I am attempting to account for inputs which would cause an error, or do not make sense (e.g. I have accounted for input of a negative number/-1 already). I want to print out an error if the user enters a number whose factorial would be larger than the maximum integer size.
I started with:
if(factorial(n) > INT_MAX)
std::cout << "nope";
continue
I tested this with n = ~25 or 26 but it doesn't prevent the result from overflowing and printing out a large negative number instead.
Second, I tried assigning this to a variable using a function from the 'limits.h' header and then comparing the result of factorial(n) against this. Still no luck (you can see this solution in the code sample below).
I could of course assign the result to a long and test against that but you wouldn't have to go very far until you started to wrap around that value, either. I'd prefer to find a way to simply prevent the value from being printed if this happens.
#include <iostream>
#include <cstdlib>
#include <limits>
int factorial(int n)
{
auto total = 1;
for(auto i = 1; i <= n; i++)
{
total = total * i; //Product of all numbers up to n
}
return total;
}
int main()
{
auto input_toggle = true;
auto n = 0;
auto int_max_size = std::numeric_limits<int>::max();
while(input_toggle = true)
{
/* get user input, check it is an integer */
if (factorial(n) > int_max_size)
{
std::cout << "Error - Sorry, factorial of " << n << " is larger than \nthe maximum integer size supported by this system. " << std::endl;
continue;
}
/* else std::cout << factorial(n) << std::endl; */`
As with my other condition(s), I expect it to simply print out that small error message and then continue asking the user for input to calculate. The code does work, it just continues to print values that have wrapped around if I request the factorial of a value >25 or so. I feel this kind of error-checking will be quite useful.
Thanks!
You are trying to do things backwards.
First, no integer can actually be bigger than INT_MAX, by definition - this is a maximum value integer can be! So your condition factorial(n) > int_max_size is always going to be false.
Moreover, there is a logical flaw in your approach. You calculate the value first and than check if it is less than maximum value allowed. By that time it is too late! You have already calculated the value and went through any overflows you might have encountered. Any check you might be performing should be performed while you are still doing your calculations.
In essence, you need to check if multiplying X by Z will be within allowed range without actually doing the multiplication (unfortunately, C++ is very strict in leaving signed integer overflow undefined behavior, so you can't try and see.).
So how do you check if X * Y will be lesser than Z? One approach would be to divide Z by Y before engaging in calculation. If you end up with the number which is lesser than X, you know that multiplying X by Y will result in overflow.
I believe, you know have enough information to code the solution yourself.

Function for testing system stability, which receives predicted time series as input

I want to write a function that gets a time series and a standard deviation as parameters and returns an adjusted time series which looks like a forecast.
With this function I want to test a system for stability, which gets a forecasted time series list for weather as input parameter.
My approach for such a function, which is described below:
vector<tuple<datetime, double>> get_adjusted_timeseries(vector<tuple<datetime, double>>& timeseries_original, const double stddev, const double dist_mid)
{
auto timeseries_copy(timeseries_original);
int sign = randInRange(0, 1) == 0 ? 1 : -1;
auto left_limit = normal_cdf_inverse(0.5 - dist_mid, 0, stddev);
auto right_limit = normal_cdf_inverse(0.5 + dist_mid, 0, stddev);
for (auto& pair : timeseries_copy)
{
double number;
do
{
nd_value = normal_distribution_r(0, stddev);
}
while (sign == -1 && nd_value > 0.0 || sign == 1 && nd_value < 0.0);
pair = make_tuple(get<0>(pair), get<1>(pair) + (nd_value / 100) * get<1>(pair));
if (nd_value > 0.0 && nd_value < right_limit || nd_value < 0.0 && nd_value > left_limit)
{
sign = sign == -1 ? 1 : -1;
}
}
return timeseries_copy;
}
Make a copy from the original time series, which is also from type vector<tuple<datetime, double>>
Get a random number that is either 0 or 1 and use the number to set the sign.
Use the Inverse Cumulative distribution function to get the limits, which indicate when the sign is changed. The sign is changed when the value of the copied time series is close to the original value. The implementation of the inverse CDF is shown here
For-loop for each item in the time series:
get a normal distributed value, which should be lower zero when sign == -1 and greater zero when sign == 1
adjust old value of time series according to the normal distributed
value
change sign if the normal distributed value is close to the original value.
The result for a low standard deviation, for example, can be seen here in yellow:
If the mean absolute percentage error (MAPE) of the two time series is calculated, the following relationship results:
stddev: 5 -> MAPE: ~0.04
stddev: 10 -> MAPE: ~0.08
stddev: 15 -> MAPE: ~0.12
stddev: 20 -> MAPE: ~0.16
What do you think of this approach?
Can this function be used to test a system that has to deal with predicted time series?
You want to generate time series data that behave like some existing time series data that you have from real phenomena (weather and stock exchange). That generated time series data will be fed into some system to test its stability.
What you could do is: fit some model to your exiting data, and then use that model to generate data that follow the model, and hence your existing data. Fitting data to a model yields a set of model parameters and a set of deviations (differences not explained by the model). The deviations may follow some known density function but not necessarily. Given the model parameters and deviations, you can generate data that look like the original data. Note that if the model does not explain the data well, deviations will be large, and the data generated with the model will not look like the original data.
For example, if you know your data is linear, you fit a line through them, and your model would be:
y = M x + B + E
where E is a random variable that follows the distribution of the error around the line that fits your data, and where M and B are the model parameters. You can now use that model to generate (x, y) coordinates that are rougly linear. When sampling the random variable E, you can assume that it follows some known distribution like a normal distribution, or use an histogram, to generate deviations that follow arbitrary density functions.
There are several time series models that you could use to fit your weather and stock exchange data. You could look at exponential smoothing. It has several different models. I am sure you can find many other models on Wikipedia.
If a model does not fit well your data, you can also see its parameters as random variables. In our example above, suppose that we have observed data where it seems that the slope is changing. We would fit several lines and obtain a distribution for M. We would then sample that variable along with E when generating data.

C++ Trapezoidal Integration Function Returning Negative Numbers when it shouldn't

I am using the following function written in C++, whose purpose is to take the integral of one array of data (y) with respect to another (x)
// Define function to perform numerical integration by the trapezoidal rule
double trapz (double xptr[], double yptr[], int Npoints)
{
// The trapzDiagFile object and associated output file are how I monitor what data the for loop actually sees.
std::ofstream trapzDiagFile;
trapzDiagFile.open("trapzDiagFile.txt",std::ofstream::out | std::ofstream::trunc);
double buffer = 0.0;
for (int n = 0; n < (Npoints - 1); n++)
{
buffer += 0.5 * (yptr[n+1] + yptr[n]) * (xptr[n+1] - xptr[n]);
trapzDiagFile << xptr[n] << "," << yptr[n] << std::endl;
}
trapzDiagFile.close();
return buffer;
}
I validated this function for the simple case where x contains 100 uniformly spaced points from 0 to 1, and y = x^2, and it returned 0.33334, as it should.
But when I use it for a different data set, it returns -3.431, which makes absolutely no sense. If you look in the attached image file, the integral I am referring to is the area under the curve between the dashed vertical lines.
It's definitely a positive number.
Moreover, I used the native trapz command in MATLAB on the same set of numbers and that returned 1.4376.
In addition, I translated the above C++ trapz function into MATLAB, line for line as closely as possible, and again got 1.4376.
I feel like there's something C++ related I'm not seeing here. If it is relevant, I am using minGW-w64.
Apologies for the vagueness of this post. If I knew more about what kind of issue I am seeing, it would be easier to be concise about it.
Plot of the dataset for which the trapz function (my homemade C++ version) returns -3.431:
Please check the value of xptr[Npoints - 1]. It may be less than xptr[Npoints - 2], and was not included in the values that you output.