Test-driven development uses a repeating sequence: write a small test for the next required behaviour, observe it fail for the expected reason, add enough implementation to make it pass, and refactor while keeping the tests passing. The initial failure checks that the test can actually detect the absent behaviour. This differs from writing a whole implementation first and only then constructing tests for it; it also uses the tests to guide interface and design decisions rather than only to inspect a finished program.[ref]
Test-driven development in the Agile Alliance glossary. Meaning of TDD and its short test/implementation/refactoring cycle
A mixture of test-selection styles is possible; the defining test-first step is normally behaviour-driven. Choosing inputs and expected results from a public contract is black-box testing with respect to the implementation, even if the developer writes the test. Knowing the internal branches can then motivate white-box tests for boundary paths or exceptional cases. TDD specifies the order of development activities, not a rule forbidding either source of test cases. It therefore does not guarantee branch coverage, and passing the growing test suite is not a proof of correctness for untested inputs.
The observer pattern connects one event-producing subject to a changing set of interested consumers. Consumers register through an observation interface; when a relevant change occurs, the producer notifies them through that interface rather than naming every concrete consumer. Consumers can be added or removed without rewriting the producer's core behaviour.
This is useful for keeping displays, controls or other derived views synchronized with changing state. The design avoids tight coupling from the producer to all concrete dependants, which otherwise makes every new consumer require changes to the producer. Publishing after the producer's representation invariant has been restored also lets callbacks observe a coherent state. The pattern does not by itself prevent notification cycles or obsolete subscriptions; update policy and unregistration still matter.
A widget or model implementing
Publisher is the subject. It calls publish(event) when an observable change occurs. An interested Reactor subscribes with listenTo(subject), and adds a partial function to reactions that handles the event types it understands. deafTo(subject) ends that subscription when it is no longer needed.The connection is therefore made through publisher and reactor interfaces rather than through a hard-coded list of particular application classes. A reactor may observe several publishers and inspect an event's source to distinguish them. The implementation's registration machinery delivers published events to interested reactions; registering a reaction alone is not a substitute for subscribing to the source.[ref]
Scala Swing event-system documentation. Official library README, event-system description; Reactor API entries listenTo, deafTo and reactions
For the converter, the three text fields are publishers and the frame is a reactor. Register the three fields once, then handle their edit-completion events to update the stored numeric state and refresh the appropriate displayed values.
Choose the following explicit policy: an edit of pounds recomputes euros; an edit of euros recomputes pounds; an edit of the rate keeps pounds fixed and recomputes euros. Amounts may be any finite signed values, while a rate must be finite and strictly positive. Invalid text, non-finite numbers and overflowing conversions leave the last accepted state intact and restore its display.The numeric state has the invariant and ; euros are derived instead of being stored independently. A proposal is validated before either stored value changes. The update flag prevents a synchronous programmatic display update from recursively committing another edit. Currency amounts display two decimal places, while the rate retains its parsed precision; display rounding is not repeatedly fed back into the numeric model.
import scala.swing._
import scala.swing.event.EditDone
import scala.util.Try
object CurrencyConverter extends SimpleSwingApplication {
def top: Frame = new MainFrame {
title = "Currency converter"
val pounds = new TextField(8)
val euros = new TextField(8)
val rate = new TextField(8)
private var poundsValue = 10.0
private var rateValue = 1.15
private var updating = false
private def finite(x: Double): Boolean =
java.lang.Double.isFinite(x)
private def money(x: Double): String =
java.lang.String.format(java.util.Locale.ROOT, "%.2f",
java.lang.Double.valueOf(x))
private def showValues(): Unit = {
updating = true
try {
pounds.text = money(poundsValue)
euros.text = money(poundsValue * rateValue)
rate.text = java.lang.Double.toString(rateValue)
} finally updating = false
}
private def commit(field: TextField): Unit = {
if (!updating) {
val proposed = Try(field.text.trim.toDouble).toOption
.filter(finite).map { value =>
if (field eq pounds) (value, rateValue)
else if (field eq euros) (value / rateValue, rateValue)
else (poundsValue, value)
}
proposed.foreach { case (newPounds, newRate) =>
if (finite(newPounds) && finite(newRate) && newRate > 0.0 &&
finite(newPounds * newRate)) {
poundsValue = newPounds
rateValue = newRate
}
}
showValues()
}
}
contents = new FlowPanel {
contents += new Label("Pounds")
contents += pounds
contents += new Label("Euros")
contents += euros
contents += new Label("Euros per pound")
contents += rate
}
listenTo(pounds, euros, rate)
reactions += {
case EditDone(source) if source eq pounds => commit(pounds)
case EditDone(source) if source eq euros => commit(euros)
case EditDone(source) if source eq rate => commit(rate)
}
showValues()
}
}Locale.ROOT ensures that formatted decimal points match the input parser.The code handles the
EditDone events used by the paper rather than trying to parse every intermediate keystroke. It uses Scala 2 style and Java 8-or-newer numeric helpers. No Scala compiler or Swing runtime execution was available in the checking environment; the state transitions were checked independently, and the API usage was compared with the supplied contract and official Scala/Swing documentation.[ref]Java 8 Double API documentation. isFinite(double), parseDouble(String) and toString(double)
Articles by others on the same topic
There are currently no matching articles.