Testing
We want to test if native code we ported complies with the original Java code.
That is, if it yields the same results as the original piece of code.
Software testing
We don't care how the native implementation was conceived
It may be poor in terms of testability in favour of good performance and resource usage.
JNI is beautiful but debugging is very painful
Black box testing
Feed certain input, expect certain output.
If the input is the same in both codes, then the output must be as well
input --> [ black box ] --> output
A great assumptions - determinism: same inputs always yield outputs for a same black box
what about random number generator
what about parallel execution flow interface
what about time dependencies
what about environment dependencies
Avoiding non-determinism
Random number generator:
actually, pseudorandom or quasi-random: the generated sequence depends on seeds
seed must be explicitly set to a known constant value - no environment-dependent values!
known konstant value should be passed as a paremater to the execution:
it should be included in the list of input parameters
it should be optional, to be used only for testing purposes, so that it is not defined then its value is set to the production, non-testing value
Parallel execution flow interface:
race condition: the result of a computation depends on the order or synchronisam of events triggered by separate execution flows
result may not be wrong, but it is probabilisticaly defined (randomnes)
caused by shared states: shared variables accessed concurrently
solution: mutual exclusion - only one execution flow is allowed to access a shared variable at once
pay extreme care to :
critical sections: pieces of code that access a shared and must not be concurrently accessed by more than one execution flow
atomic operations: consist of a set linear steps that either execute as an atomic sequence or not at all (cannot be hung between two steps)
in Java, mutual exclusion is implemented with the kwyword eynchronized, and can be applied:
over methods: locks the execution of any synchronized method on the same object
over objects: locks the execution of a piece of code synchronized by the same object
Methods:
class MyClass {
synchronized void firstMethod() {
...
}
synchronized void secondMethod() {
...
}
}
Statements:
...
synchronized (object) {
doThis();
doSomethingElse();
doMore();
}
In Java, read and write operations are atomic for all variables qualified as volatile
class MyClass() {
volatile long variable;
void addVal(int val) {
variable += val;
}
}
Time dependencies:
conditions that depend on time are dangerous
performing X operations within a time frame
executing function X when Z second passed
time consumed for computations is hardware-dependent: faster hardware, less time needed
avoid time-related constraints!
Environment dependencies:
depending on the environment is very tricky:
using the current time or the process ID
performing an action if certain environment condition is met (e.g. library is available)
a solution: creating flag for testing and surrounding environement-dependent code with a flagged conditional
BUGS
If the library is used within Java: printf debbuging
If the library has proper JNI-agnostic API - create an executable code to run the library:
discard chances that the bug be due to Java
be able to use debugging tools (e.g. GNU gdb)
Allows library code to be executed, tested and debugged without being enclosed in a Java shell.
Last updated
Was this helpful?