I'm writing a vector class for 2D geometry applications, and below is a rough draft for a unit test using Cpputest. I'm familiar with unit tests, but this is the first I've done for a purely mathematical class, and I'd really appreciate any insight or feedback no matter how small; even personal preference and knit-picking is welcome.
#include "CppUTest/TestHarness.h"
#include "../Mathos.hpp"
#include <stdio.h>
// Todo: Put padding (2 newlines) between tests.
double const TOLERANCE = 0.000001;
static void VECTORS_EQUAL(double x, double y, mat::Vector2D const &V, double tolerance)
{
DOUBLES_EQUAL(x, V.x, tolerance);
DOUBLES_EQUAL(y, V.y, tolerance);
}
static void VECTORS_EQUAL(mat::Vector2D const &expect,
mat::Vector2D const &actual,
double tolerance)
{
VECTORS_EQUAL(expect.x, expect.y, actual, tolerance);
}
TEST_GROUP(VectorTestGroup)
{
};
TEST(VectorTestGroup, VectorSize)
{
CHECK_EQUAL(16, sizeof(mat::Vector2D));
}
TEST(VectorTestGroup, VectorIsZeroByDefault)
{
mat::Vector2D U;
VECTORS_EQUAL(0, 0, U, TOLERANCE);
}
TEST(VectorTestGroup, VectorCanBeCreatedFromValues)
{
// x is provided, no y
mat::Vector2D U(1);
VECTORS_EQUAL(1, 0, U, TOLERANCE);
mat::Vector2D V(1, 2);
VECTORS_EQUAL(1, 2, V, TOLERANCE);
}
TEST(VectorTestGroup, VectorCanBeCreatedFromVector)
{
mat::Vector2D U(mat::Vector2D(1, 2));
VECTORS_EQUAL(1, 2, U, TOLERANCE);
mat::Vector2D V(U);
VECTORS_EQUAL(1, 2, V, TOLERANCE);
// Calls the mat::Vector(double, double)
// constructor
mat::Vector2D W = mat::Vector2D(4, 3);
VECTORS_EQUAL(4, 3, W, TOLERANCE);
// Calls the copy constructor
mat::Vector2D Z = W;
VECTORS_EQUAL(4, 3, Z, TOLERANCE);
}
TEST(VectorTestGroup, VectorAssignment)
{
mat::Vector2D U(1, 2);
mat::Vector2D V(3, 4);
U = V;
VECTORS_EQUAL(3, 4, U, TOLERANCE);
}
TEST(VectorTestGroup, Vector_addition)
{
mat::Vector2D U(1, 2);
mat::Vector2D V(3, 4);
mat::Vector2D W = U + V;
VECTORS_EQUAL(4, 6, W, TOLERANCE);
}
TEST(VectorTestGroup, Vector_addition_self_assignment)
{
mat::Vector2D A;
const mat::Vector2D B( 1, 2);
const mat::Vector2D C(-3, 4);
const mat::Vector2D D(-5, -6);
const mat::Vector2D E( 7, -8);
A += B;
VECTORS_EQUAL(1, 2, A, TOLERANCE);
A += C;
VECTORS_EQUAL(-2, 6, A, TOLERANCE);
A += D;
VECTORS_EQUAL(-7, 0, A, TOLERANCE);
A += E;
VECTORS_EQUAL(0, -8, A, TOLERANCE);
mat::Vector2D F;
mat::Vector2D G(1, 1);
F = G += A;
VECTORS_EQUAL(1, -7, F, TOLERANCE);
}
TEST(VectorTestGroup, Vector_subtraction)
{
mat::Vector2D U(1, 2);
mat::Vector2D V(3, 4);
mat::Vector2D W = U - V;
VECTORS_EQUAL(-2, -2, W, TOLERANCE);
}
TEST(VectorTestGroup, Vector_subtraction_self_assignment)
{
mat::Vector2D A;
const mat::Vector2D B( 1, 2);
const mat::Vector2D C(-3, 4);
const mat::Vector2D D(-5, -6);
const mat::Vector2D E( 7, -8);
A -= B;
VECTORS_EQUAL(-1, -2, A, TOLERANCE);
A -= C;
VECTORS_EQUAL(2, -6, A, TOLERANCE);
A -= D;
VECTORS_EQUAL(7, 0, A, TOLERANCE);
A -= E;
VECTORS_EQUAL(0, 8, A, TOLERANCE);
mat::Vector2D F;
mat::Vector2D G(1, 1);
F = G -= A;
VECTORS_EQUAL(1, -7, F, TOLERANCE);
}
TEST(VectorTestGroup, Scalar_multiplication)
{
mat::Vector2D U(1, 2);
mat::Vector2D V = 10 * U; // Number comes first
VECTORS_EQUAL(10, 20, V, TOLERANCE);
V = V * 3; // Vector comes first
VECTORS_EQUAL(30, 60, V, TOLERANCE);
}
TEST(VectorTestGroup, Scalar_multiplication_self_assignment)
{
mat::Vector2D A(1, 2);
A *= 2;
VECTORS_EQUAL(2, 4, A, TOLERANCE);
A *= -4.5;
VECTORS_EQUAL(-9, -18, A, TOLERANCE);
mat::Vector2D B = A *= 0.25;
VECTORS_EQUAL(-2.25, -4.5, B, TOLERANCE);
}
TEST(VectorTestGroup, Scalar_division)
{
mat::Vector2D U(1, 2);
mat::Vector2D V = U / 4;
VECTORS_EQUAL(0.25, 0.50, V, TOLERANCE);
}
TEST(VectorTestGroup, Scalar_division_self_assignment)
{
mat::Vector2D A(1, 2);
A /= 2;
VECTORS_EQUAL(0.5, 1, A, TOLERANCE);
A /= -4;
VECTORS_EQUAL(-0.125, -0.25, A, TOLERANCE);
mat::Vector2D B = A /= 0.1;
VECTORS_EQUAL(-1.25, -2.5, B, TOLERANCE);
}
void printVectorTest(const mat::Vector2D& expected, const mat::Vector2D& actual)
{
// Print vector as minimum one digit with 24 digits
// trailing after the decimal point.
printf("\n");
printf("Expected: <%1.24f, %1.24f>\n", expected.x, expected.y);
printf(" Got: <%1.24f, %1.24f>\n", actual.x, actual.y);
}
TEST(VectorTestGroup, Vector_equality)
{
mat::Vector2D const A(1, 2);
mat::Vector2D const B(3, 4);
mat::Vector2D const C(1, 2);
CHECK(A == A);
CHECK(A == C);
CHECK_FALSE(A == B);
}
TEST(VectorTestGroup, Vectors_that_are_about_the_same_are_equal)
{
// This is not exactly 0.3
mat::Vector2D const x(0.3);
// Floating-point rounding errors will build up here
mat::Vector2D const myPointNine = x + x + x;
mat::Vector2D const expectedPointNine(0.9);
printVectorTest(expectedPointNine, myPointNine);
// Even though the result is not exactly 0.9, it's close
// enough to be considered equal to 0.9
CHECK(myPointNine == expectedPointNine);
}
TEST(VectorTestGroup, Subtracting_two_equal_vectors_gives_the_zero_vector)
{
mat::Vector2D const b(3 * mat::Vector2D(0.3) - mat::Vector2D(0.9));
mat::Vector2D const ZERO_VECTOR;
printVectorTest(ZERO_VECTOR, b);
CHECK(b == ZERO_VECTOR);
}
TEST(VectorTestGroup, Very_small_vectors_are_not_equal_to_the_zero_vector)
{
CHECK_FALSE(mat::Vector2D(0.000001) == mat::Vector2D(0));
CHECK_FALSE(mat::Vector2D(0.000000001) == mat::Vector2D(0));
CHECK_FALSE(mat::Vector2D(0.000000000001) == mat::Vector2D(0));
}
TEST(VectorTestGroup, Vector_inequality)
{
mat::Vector2D const A(1, 2);
mat::Vector2D const B(3, 4);
mat::Vector2D const C(1, 2);
CHECK_FALSE(A != A);
CHECK_FALSE(A != C);
CHECK(A != B);
}
TEST_GROUP(VectorOperationsGroup)
{
void setup()
{
mat::Vector2D U;
}
void teardown()
{
}
};
IGNORE_TEST(VectorOperationsGroup, LENGTH)
{
// DOUBLES_EQUAL(5.000000, mat::length( mat::Vector2D( 4, 3) ), TOLERANCE);
// DOUBLES_EQUAL(9.055385, mat::length( mat::Vector2D(-1, 9) ), TOLERANCE);
// DOUBLES_EQUAL(8.246211, mat::length( mat::Vector2D(-8, -2) ), TOLERANCE);
// DOUBLES_EQUAL(7.810250, mat::length( mat::Vector2D( 6, -5) ), TOLERANCE);
CHECK(true);
}
IGNORE_TEST(VectorOperationsGroup, UNIT)
{
// mat::Vector2D const A( 4, 3);
// mat::Vector2D const B(-1, 9);
// mat::Vector2D const C(-8, -2);
// mat::Vector2D const D( 6, -5);
// mat::Vector2D a( mat::unit( A ) );
// mat::Vector2D b( mat::unit( B ) );
// mat::Vector2D c( mat::unit( C ) );
// mat::Vector2D d( mat::unit( D ) );
// DOUBLES_EQUAL(1.000000, mat::length(a), TOLERANCE);
// DOUBLES_EQUAL(1.000000, mat::length(b), TOLERANCE);
// DOUBLES_EQUAL(1.000000, mat::length(c), TOLERANCE);
// DOUBLES_EQUAL(1.000000, mat::length(d), TOLERANCE);
// VECTORS_EQUAL(A, a * mat::length(A), TOLERANCE);
// VECTORS_EQUAL(B, b * mat::length(B), TOLERANCE);
// VECTORS_EQUAL(C, c * mat::length(C), TOLERANCE);
// VECTORS_EQUAL(D, d * mat::length(D), TOLERANCE);
CHECK(true);
}
IGNORE_TEST(VectorOperationsGroup, DOT)
{
// mat::Vector2D const A( 4, 3);
// mat::Vector2D const B(-1, 9);
// mat::Vector2D const C(-8, -2);
// mat::Vector2D const D( 6, -5);
// DOUBLES_EQUAL( 23.00000, mat::dot(A, B), TOLERANCE);
// DOUBLES_EQUAL(-38.00000, mat::dot(A, C), TOLERANCE);
// DOUBLES_EQUAL( 9.000000, mat::dot(A, D), TOLERANCE);
CHECK(true);
}
Vector2D.hpp
#ifndef VECTOR_2D_HPP
#define VECTOR_2D_HPP
#include <ostream>
namespace mat {
class Vector2D {
public:
double x, y;
Vector2D();
Vector2D(double x, double y = 0);
Vector2D(const Vector2D& A);
const Vector2D& operator =(const Vector2D& rhs);
Vector2D operator +(const Vector2D& rhs) const;
Vector2D operator -(const Vector2D& rhs) const;
Vector2D operator /(double k) const;
const Vector2D& operator +=(const Vector2D& rhs);
const Vector2D& operator -=(const Vector2D& rhs);
const Vector2D& operator *=(double k);
const Vector2D& operator /=(double k);
bool operator ==(const Vector2D& rhs) const;
bool operator !=(const Vector2D& rhs) const;
};
Vector2D operator*(double k, const Vector2D& A);
Vector2D operator*(const Vector2D& A, double k);
std::ostream& operator<<(std::ostream& os, const Vector2D& A);
double length(Vector2D const &U);
Vector2D unit(Vector2D const &U);
double dot(Vector2D const &U, Vector2D const &V);
}
#endif