Why do I like TDD but was skeptical about it at the beginning?

# for mix application without phoenix dependency
mix new tdd_practice

# for phoenix application
mix phx.new tdd_practice
defmodule TddPractice.MixProject do
use Mix.Project

def project do
[
app: :tdd_practice,
version: "0.1.0",
elixir: "~> 1.13",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end

# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger]
]
end

# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:mix_test_watch, "~> 1.0", [only: :dev, runtime: false]}
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
]
end
end
mix deps.get
# watch all tests
mix test.watch

# watch a specific test file
mix test.eatch absolute_or_relative_path_to_file

# watch a specific test case in a file
# mix test.eatch absolute_or_relative_path_to_file:line_number_of_test_start
# eg.
mix test.eatch absolute_or_relative_path_to_file:10

# run previously failed tests
mix test.watch --failed
the file location of the safe_divide_test
defmodule SafeDivideTest do
use ExUnit.Case
end
SafeDivide module
  test "safe_divide/2 returns the result of dividing the first argument by the second" do
assert SafeDivide.safe_divide(4, 2) == 2
end
  test "safe_divide/2 returns nil when the second argument is zero" do
assert SafeDivide.safe_divide(4, 0) == nil
end
  test "safe_divide/2 returns the result of dividing when argument is string" do
assert SafeDivide.safe_divide("4", "2") == 2
end
  test "safe_divide/2 returns nil when one of the argument is invalid" do
assert SafeDivide.safe_divide("a", 2) == nil
assert SafeDivide.safe_divide(4, "b") == nil
assert SafeDivide.safe_divide("a", "b") == nil
end
defmodule SafeDivideTest do
use ExUnit.Case
alias SafeDivide

test "safe_divide/2 returns the result of dividing the first argument by the second" do
assert SafeDivide.safe_divide(4, 2) == 2
end

test "safe_divide/2 returns nil when the second argument is zero" do
assert SafeDivide.safe_divide(4, 0) == nil
end

test "safe_divide/2 returns the result of dividing when argument is string" do
assert SafeDivide.safe_divide("4", "2") == 2
end

test "safe_divide/2 returns nil when one of the argument is invalid" do
assert SafeDivide.safe_divide("a", 2) == nil
assert SafeDivide.safe_divide(4, "b") == nil
assert SafeDivide.safe_divide("a", "b") == nil
end
end
mix_test_watch in action
  def safe_divide(a, b) do
2
end
  def safe_divide(a, b) do
a / b
end
  def safe_divide(a, b) do
cond do
b == 0 -> nil
true -> a / b
end
end
defmodule SafeDivide do
def safe_divide(a, b) do
cond do
b == 0 ->
nil

is_number(a) and is_number(b) ->
a / b

is_castable_number(a) && is_castable_number(b) ->
a =
Float.parse(a)
|> elem(0)

b = Float.parse(b) |> elem(0)

a / b

!is_castable_number(a) || !is_castable_number(b) ->
nil

true ->
a / b
end
end

defp is_castable_number(num) do
cond do
is_number(num) ->
true

is_binary(num) ->
Float.parse(num)
|> case do
{_, ""} -> true
_ -> false
end

true ->
false
end
end
end
  • TDD relieves lots of stress while development as requirements are concrete after writing the tests, and I do not need to be overwhelmed with the overall requirement. Moreover, it is easy to plan big tasks when split into small tests.
  • Make code less error-prone.
  • I believe TDD is arguably a faster process for developing reliable software.
  • Tests are good documentation of functional use cases.
  • With TDD, it is possible to trace cases that were considered while development, and the new cases can be quickly checked by just writing additional tests and also easy to find out the missing case.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store