summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSzymon Szukalski <szymon@skas.io>2024-07-24 17:31:54 +1000
committerSzymon Szukalski <szymon@skas.io>2024-07-24 17:31:54 +1000
commitc459e7d5abd66d7bcf38e151aa2632fcb139f4f5 (patch)
tree2f19d20ed0cf9566eb4390f01ebf4d2be7fd6657
parentf08b2fa7e6a977a18d6b9f14fb73c18ec73ec5df (diff)
Use Apache Commons Math for calculations and implement service tests
Implement TestResultService tests and supporting entity builders. Switch to Apache Commons Math library for descriptive statistics.
-rw-r--r--pom.xml6
-rw-r--r--src/main/java/com/stileeducation/markr/entity/TestResult.java10
-rw-r--r--src/main/java/com/stileeducation/markr/service/TestResultsService.java82
-rw-r--r--src/test/java/com/stileeducation/markr/service/TestResultsServiceTest.java175
-rw-r--r--src/test/java/com/stileeducation/markr/util/StudentBuilder.java50
-rw-r--r--src/test/java/com/stileeducation/markr/util/TestBuilder.java43
-rw-r--r--src/test/java/com/stileeducation/markr/util/TestResultBuilder.java36
7 files changed, 360 insertions, 42 deletions
diff --git a/pom.xml b/pom.xml
index 0281f72..55b3297 100644
--- a/pom.xml
+++ b/pom.xml
@@ -65,6 +65,12 @@
</dependency>
<dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-math3</artifactId>
+ <version>3.6.1</version>
+ </dependency>
+
+ <dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
diff --git a/src/main/java/com/stileeducation/markr/entity/TestResult.java b/src/main/java/com/stileeducation/markr/entity/TestResult.java
index cb7cea5..0d68f62 100644
--- a/src/main/java/com/stileeducation/markr/entity/TestResult.java
+++ b/src/main/java/com/stileeducation/markr/entity/TestResult.java
@@ -8,6 +8,16 @@ import java.util.Objects;
@Table(name = "test_results")
public class TestResult {
+ public TestResult() {
+ }
+
+ public TestResult(Long id, Student student, Test test, Integer marksAwarded) {
+ this.id = id;
+ this.student = student;
+ this.test = test;
+ this.marksAwarded = marksAwarded;
+ }
+
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
diff --git a/src/main/java/com/stileeducation/markr/service/TestResultsService.java b/src/main/java/com/stileeducation/markr/service/TestResultsService.java
index 8d1b314..95b42f3 100644
--- a/src/main/java/com/stileeducation/markr/service/TestResultsService.java
+++ b/src/main/java/com/stileeducation/markr/service/TestResultsService.java
@@ -7,16 +7,18 @@ import com.stileeducation.markr.entity.TestResult;
import com.stileeducation.markr.repository.StudentRepository;
import com.stileeducation.markr.repository.TestRepository;
import com.stileeducation.markr.repository.TestResultRepository;
+import org.apache.commons.math3.stat.descriptive.moment.StandardDeviation;
+import org.apache.commons.math3.stat.descriptive.rank.Percentile;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
-import java.util.stream.Collectors;
@Service
public class TestResultsService {
+ public static final boolean IS_BIAS_CORRECTED = false;
@Autowired
private TestResultRepository testResultRepository;
@@ -78,68 +80,64 @@ public class TestResultsService {
.orElse(0.0);
}
- public double calculateStandardDeviationOfTestResults(List<TestResult> results, double mean) {
+ public double calculateStandardDeviationOfTestResults(List<TestResult> results) {
if (results.isEmpty()) {
- return 0.0; // or throw an exception if no results are found
+ return 0.0;
}
+ double[] marks =
+ results.stream()
+ .mapToDouble(TestResult::getMarksAwarded)
+ .toArray();
- // Calculate the variance
- double variance = results.stream()
- .mapToDouble(result -> Math.pow(result.getMarksAwarded() - mean, 2))
- .average()
- .orElse(0.0);
-
- // Return the standard deviation
- return Math.sqrt(variance);
+ StandardDeviation standardDeviation = new StandardDeviation(IS_BIAS_CORRECTED);
+ return standardDeviation.evaluate(marks);
}
- public double calculatePercentile(List<Integer> sortedMarks, double percentile) {
- if (sortedMarks.isEmpty()) {
+ public double calculate25thPercentile(List<TestResult> results) {
+ if (results.isEmpty()) {
return 0.0;
}
- int index = (int) Math.floor(percentile / 100.0 * sortedMarks.size()) - 1;
- return sortedMarks.get(Math.min(index, sortedMarks.size() - 1));
+ double[] marks =
+ results.stream()
+ .mapToDouble(TestResult::getMarksAwarded)
+ .toArray();
+ return new Percentile().evaluate(marks, 25.0);
}
- public List<Integer> getSortedMarks(List<TestResult> testResults) {
- return testResults.stream()
- .map(TestResult::getMarksAwarded)
- .sorted()
- .collect(Collectors.toList());
- }
-
- public double calculate25thPercentile(List<Integer> sortedMarks) {
- return calculatePercentile(sortedMarks, 25.0);
- }
-
- public double calculate50thPercentile(List<Integer> sortedMarks) {
- return calculatePercentile(sortedMarks, 50.0);
+ public double calculate50thPercentile(List<TestResult> results) {
+ if (results.isEmpty()) {
+ return 0.0;
+ }
+ double[] marks =
+ results.stream()
+ .mapToDouble(TestResult::getMarksAwarded)
+ .toArray();
+ return new Percentile().evaluate(marks, 50.0);
}
- public double calculate75thPercentile(List<Integer> sortedMarks) {
- return calculatePercentile(sortedMarks, 75.0);
+ public double calculate75thPercentile(List<TestResult> results) {
+ if (results.isEmpty()) {
+ return 0.0;
+ }
+ double[] marks =
+ results.stream()
+ .mapToDouble(TestResult::getMarksAwarded)
+ .toArray();
+ return new Percentile().evaluate(marks, 75.0);
}
public AggregatedTestResultsDTO aggregateTestResults(String testId) {
List<TestResult> testResults = findAllByTestId(testId);
- List<Integer> sortedMarks = getSortedMarks(testResults);
AggregatedTestResultsDTO results = new AggregatedTestResultsDTO();
results.setMean(calculateMeanOfTestResults(testResults));
-
- results.setStddev(calculateStandardDeviationOfTestResults(testResults, results.getMean()));
-
+ results.setStddev(calculateStandardDeviationOfTestResults(testResults));
results.setMin(calculateMinOfTestResults(testResults));
-
results.setMax(calculateMaxOfTestResults(testResults));
-
- results.setP25(calculate25thPercentile(sortedMarks));
-
- results.setP50(calculate50thPercentile(sortedMarks));
-
- results.setP75(calculate75thPercentile(sortedMarks));
-
+ results.setP25(calculate25thPercentile(testResults));
+ results.setP50(calculate50thPercentile(testResults));
+ results.setP75(calculate75thPercentile(testResults));
results.setCount(testResults.size());
return results;
diff --git a/src/test/java/com/stileeducation/markr/service/TestResultsServiceTest.java b/src/test/java/com/stileeducation/markr/service/TestResultsServiceTest.java
new file mode 100644
index 0000000..474d731
--- /dev/null
+++ b/src/test/java/com/stileeducation/markr/service/TestResultsServiceTest.java
@@ -0,0 +1,175 @@
+package com.stileeducation.markr.service;
+
+import com.stileeducation.markr.entity.Student;
+import com.stileeducation.markr.entity.TestResult;
+import com.stileeducation.markr.repository.StudentRepository;
+import com.stileeducation.markr.repository.TestRepository;
+import com.stileeducation.markr.repository.TestResultRepository;
+import com.stileeducation.markr.util.StudentBuilder;
+import com.stileeducation.markr.util.TestBuilder;
+import com.stileeducation.markr.util.TestResultBuilder;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+
+class TestResultsServiceTest {
+
+ private static Student student1;
+ private static Student student2;
+ private static Student student3;
+ private static Student student4;
+
+ private static com.stileeducation.markr.entity.Test test1;
+
+ private static TestResult testResult1;
+ private static TestResult testResult2;
+ private static TestResult testResult3;
+ private static TestResult testResult4;
+ private static TestResult testResult5;
+ private static TestResult testResult6;
+
+ private static List<TestResult> test1Results;
+
+ @Mock
+ private TestResultRepository testResultRepository;
+
+ @Mock
+ private StudentRepository studentRepository;
+
+ @Mock
+ private TestRepository testRepository;
+
+ @InjectMocks
+ private TestResultsService testResultsService;
+
+ @BeforeAll
+ static void setupTestData() {
+ student1 = new StudentBuilder()
+ .withId(1L)
+ .withStudentNumber("555123")
+ .withFirstName("Emily")
+ .withLastName("Clark")
+ .build();
+
+ student2 = new StudentBuilder()
+ .withId(2L)
+ .withStudentNumber("555456")
+ .withFirstName("James")
+ .withLastName("Taylor")
+ .build();
+
+ student3 = new StudentBuilder()
+ .withId(3L)
+ .withStudentNumber("555789")
+ .withFirstName("Sophia")
+ .withLastName("Martinez")
+ .build();
+
+ student4 = new StudentBuilder()
+ .withId(4L)
+ .withStudentNumber("555012")
+ .withFirstName("Liam")
+ .withLastName("Anderson")
+ .build();
+
+ test1 = new TestBuilder()
+ .withId(1L)
+ .withTestId("1234")
+ .withMarksAvailable(100)
+ .build();
+
+ testResult1 = new TestResultBuilder()
+ .withStudent(student1)
+ .withTest(test1)
+ .withMarksAwarded(10)
+ .build();
+
+ testResult2 = new TestResultBuilder()
+ .withStudent(student2)
+ .withTest(test1)
+ .withMarksAwarded(20)
+ .build();
+
+ testResult3 = new TestResultBuilder()
+ .withStudent(student3)
+ .withTest(test1)
+ .withMarksAwarded(40)
+ .build();
+
+ testResult4 = new TestResultBuilder()
+ .withStudent(student4)
+ .withTest(test1)
+ .withMarksAwarded(50)
+ .build();
+
+ testResult5 = new TestResultBuilder()
+ .withStudent(student3)
+ .withTest(test1)
+ .withMarksAwarded(70)
+ .build();
+
+ testResult6 = new TestResultBuilder()
+ .withStudent(student4)
+ .withTest(test1)
+ .withMarksAwarded(80)
+ .build();
+
+ test1Results = List.of(testResult1, testResult2, testResult3, testResult4, testResult5, testResult6);
+ }
+
+ @BeforeEach
+ void setUp() {
+ MockitoAnnotations.openMocks(this);
+ }
+
+ @Test
+ void calculateMeanOfTestResults() {
+ double mean = testResultsService.calculateMeanOfTestResults(test1Results);
+ assertEquals(45.0, mean, 0.01);
+ }
+
+ @Test
+ void calculateMinOfTestResults() {
+ double min = testResultsService.calculateMinOfTestResults(test1Results);
+ assertEquals(10.0, min, 0.01);
+ }
+
+ @Test
+ void calculateMaxOfTestResults() {
+ double max = testResultsService.calculateMaxOfTestResults(test1Results);
+ assertEquals(80.0, max, 0.01);
+ }
+
+ @Test
+ void calculateStandardDeviationOfTestResults() {
+ double standardDeviationOfTestResults = testResultsService.calculateStandardDeviationOfTestResults(test1Results);
+ assertEquals(25, standardDeviationOfTestResults, 0.01);
+ }
+
+ @Test
+ void calculate25thPercentile() {
+ double percentile = testResultsService.calculate25thPercentile(test1Results);
+ assertEquals(17.5, percentile, 0.01);
+ }
+
+ @Test
+ void calculate50thPercentile() {
+ double percentile = testResultsService.calculate50thPercentile(test1Results);
+ assertEquals(45, percentile, 0.01);
+ }
+
+ @Test
+ void calculate75thPercentile() {
+ double percentile = testResultsService.calculate75thPercentile(test1Results);
+ assertEquals(72.5, percentile, 0.01);
+ }
+
+} \ No newline at end of file
diff --git a/src/test/java/com/stileeducation/markr/util/StudentBuilder.java b/src/test/java/com/stileeducation/markr/util/StudentBuilder.java
new file mode 100644
index 0000000..5189f19
--- /dev/null
+++ b/src/test/java/com/stileeducation/markr/util/StudentBuilder.java
@@ -0,0 +1,50 @@
+package com.stileeducation.markr.util;
+
+import com.stileeducation.markr.entity.Student;
+import com.stileeducation.markr.entity.TestResult;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class StudentBuilder {
+ private Long id;
+ private String firstName;
+ private String lastName;
+ private String studentNumber;
+ private Set<TestResult> testResults = new HashSet<>();
+
+ public StudentBuilder withId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public StudentBuilder withFirstName(String firstName) {
+ this.firstName = firstName;
+ return this;
+ }
+
+ public StudentBuilder withLastName(String lastName) {
+ this.lastName = lastName;
+ return this;
+ }
+
+ public StudentBuilder withStudentNumber(String studentNumber) {
+ this.studentNumber = studentNumber;
+ return this;
+ }
+
+ public StudentBuilder withTestResults(Set<TestResult> testResults) {
+ this.testResults = testResults;
+ return this;
+ }
+
+ public Student build() {
+ Student student = new Student();
+ student.setId(id);
+ student.setFirstName(firstName);
+ student.setLastName(lastName);
+ student.setStudentNumber(studentNumber);
+ student.setTestResults(testResults);
+ return student;
+ }
+} \ No newline at end of file
diff --git a/src/test/java/com/stileeducation/markr/util/TestBuilder.java b/src/test/java/com/stileeducation/markr/util/TestBuilder.java
new file mode 100644
index 0000000..5c513f3
--- /dev/null
+++ b/src/test/java/com/stileeducation/markr/util/TestBuilder.java
@@ -0,0 +1,43 @@
+package com.stileeducation.markr.util;
+
+import com.stileeducation.markr.entity.Test;
+import com.stileeducation.markr.entity.TestResult;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class TestBuilder {
+ private Long id;
+ private String testId;
+ private Integer marksAvailable;
+ private Set<TestResult> testResults = new HashSet<>();
+
+ public TestBuilder withId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public TestBuilder withTestId(String testId) {
+ this.testId = testId;
+ return this;
+ }
+
+ public TestBuilder withMarksAvailable(Integer marksAvailable) {
+ this.marksAvailable = marksAvailable;
+ return this;
+ }
+
+ public TestBuilder withTestResults(Set<TestResult> testResults) {
+ this.testResults = testResults;
+ return this;
+ }
+
+ public Test build() {
+ Test test = new Test();
+ test.setId(id);
+ test.setTestId(testId);
+ test.setMarksAvailable(marksAvailable);
+ test.setTestResults(testResults);
+ return test;
+ }
+}
diff --git a/src/test/java/com/stileeducation/markr/util/TestResultBuilder.java b/src/test/java/com/stileeducation/markr/util/TestResultBuilder.java
new file mode 100644
index 0000000..db26e15
--- /dev/null
+++ b/src/test/java/com/stileeducation/markr/util/TestResultBuilder.java
@@ -0,0 +1,36 @@
+package com.stileeducation.markr.util;
+
+import com.stileeducation.markr.entity.Student;
+import com.stileeducation.markr.entity.Test;
+import com.stileeducation.markr.entity.TestResult;
+
+public class TestResultBuilder {
+ private Long id;
+ private Student student;
+ private Test test;
+ private Integer marksAwarded;
+
+ public TestResultBuilder withId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public TestResultBuilder withStudent(Student student) {
+ this.student = student;
+ return this;
+ }
+
+ public TestResultBuilder withTest(Test test) {
+ this.test = test;
+ return this;
+ }
+
+ public TestResultBuilder withMarksAwarded(Integer marksAwarded) {
+ this.marksAwarded = marksAwarded;
+ return this;
+ }
+
+ public TestResult build() {
+ return new TestResult(id, student, test, marksAwarded);
+ }
+} \ No newline at end of file