Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #124: Introduce basic stub for AnswerClassificationController #187

Merged
merged 1 commit into from
Sep 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.oppia.domain.classify

import org.oppia.app.model.Interaction
import org.oppia.app.model.InteractionObject
import org.oppia.app.model.Outcome
import javax.inject.Inject

// TODO(#59): Restrict the visibility of this class to only other controllers.
/**
* Controller responsible for classifying user answers to a specific outcome based on Oppia's interaction rule engine.
* This controller is not meant to be interacted with directly by the UI. Instead, UIs wanting to submit answers should
* do so via various progress controllers, like [StoryProgressController].
*
* This controller should only be interacted with via background threads.
*/
class AnswerClassificationController @Inject constructor() {
/**
* Classifies the specified answer in the context of the specified [Interaction] and returns the [Outcome] that best
* matches the learner's answer.
*/
internal fun classify(interaction: Interaction, answer: InteractionObject): Outcome {
// Assume only the default outcome is returned currently since this stubbed implementation is not actually used by
// downstream stubbed progress controllers.
return interaction.defaultOutcome
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package org.oppia.domain.classify

import android.app.Application
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import dagger.BindsInstance
import dagger.Component
import dagger.Module
import dagger.Provides
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.oppia.app.model.AnswerGroup
import org.oppia.app.model.Interaction
import org.oppia.app.model.InteractionObject
import org.oppia.app.model.Outcome
import org.oppia.app.model.SubtitledHtml
import org.robolectric.annotation.Config
import javax.inject.Inject
import javax.inject.Singleton

/** Tests for [AnswerClassificationController]. */
@RunWith(AndroidJUnit4::class)
@Config(manifest = Config.NONE)
class AnswerClassificationControllerTest {
private val ARBITRARY_SAMPLE_ANSWER = InteractionObject.newBuilder().setNormalizedString("Some value").build()

private val OUTCOME_0 = Outcome.newBuilder()
.setDestStateName("First state")
.setFeedback(SubtitledHtml.newBuilder().setContentId("content_id_0").setHtml("Feedback 1"))
.build()
private val OUTCOME_1 = Outcome.newBuilder()
.setDestStateName("Second state")
.setFeedback(SubtitledHtml.newBuilder().setContentId("content_id_1").setHtml("Feedback 2"))
.build()
private val OUTCOME_2 = Outcome.newBuilder()
.setDestStateName("Third state")
.setFeedback(SubtitledHtml.newBuilder().setContentId("content_id_2").setHtml("Feedback 3"))
.build()

@Inject
lateinit var answerClassificationController: AnswerClassificationController

@Before
fun setUp() {
setUpTestApplicationComponent()
}

@Test
fun testClassify_testInteraction_withOnlyDefaultOutcome_returnsDefaultOutcome() {
val interaction = Interaction.newBuilder()
.setDefaultOutcome(OUTCOME_0)
.build()

val outcome = answerClassificationController.classify(interaction, ARBITRARY_SAMPLE_ANSWER)

assertThat(outcome).isEqualTo(OUTCOME_0)
}

@Test
fun testClassify_testInteraction_withMultipleDefaultOutcomes_returnsDefaultOutcome() {
val interaction = Interaction.newBuilder()
.setDefaultOutcome(OUTCOME_1)
.addAnswerGroups(AnswerGroup.newBuilder().setOutcome(OUTCOME_2))
.build()

val outcome = answerClassificationController.classify(interaction, ARBITRARY_SAMPLE_ANSWER)

assertThat(outcome).isEqualTo(OUTCOME_1)
}

@Test
fun testClassify_afterPreviousInteraction_returnsDefaultOutcomeOfSecondInteraction() {
val interaction1 = Interaction.newBuilder()
.setDefaultOutcome(OUTCOME_1)
.addAnswerGroups(AnswerGroup.newBuilder().setOutcome(OUTCOME_0))
.build()
val interaction2 = Interaction.newBuilder()
.setDefaultOutcome(OUTCOME_2)
.build()
answerClassificationController.classify(interaction1, ARBITRARY_SAMPLE_ANSWER)

val outcome = answerClassificationController.classify(interaction2, ARBITRARY_SAMPLE_ANSWER)

assertThat(outcome).isEqualTo(OUTCOME_2)
}

private fun setUpTestApplicationComponent() {
DaggerAnswerClassificationControllerTest_TestApplicationComponent.builder()
.setApplication(ApplicationProvider.getApplicationContext())
.build()
.inject(this)
}

// TODO(#89): Move this to a common test application component.
@Module
class TestModule {
@Provides
@Singleton
fun provideContext(application: Application): Context {
return application
}
}

// TODO(#89): Move this to a common test application component.
@Singleton
@Component(modules = [TestModule::class])
interface TestApplicationComponent {
@Component.Builder
interface Builder {
@BindsInstance
fun setApplication(application: Application): Builder

fun build(): TestApplicationComponent
}

fun inject(answerClassificationControllerTest: AnswerClassificationControllerTest)
}
}