I wrote a code to convert numbers to romaji in TDD

Introduction

Recently, screen development using typescript has become the main focus. I thought about writing java for the first time in a long time, and since it was a great opportunity, I would like to practice TDD.

The development here is summarized below. https://github.com/ko-flavor/study-TDD/pull/2

I wrote the large table of contents and commits as much as possible. Please refer to those who want to compare with the code.

Searching for the subject

I borrowed the material from Kata-Log. The theme is to write a method to convert numbers to Roman numerals.

Get ready

This area has nothing to do with TDD, so I'll break it down.

--Gradle project automatic generation --Edit package name, class name, method name --The package name is RomanNumerals romanNumerals --The class name is RomanNumeralConverter --The method name is convert as instructed --Edit .gitignore --I wanted to use jUnit5 and assertThat, so edit build.gradle --Set the contents of the test to fail () and you are ready to go.

Commit here

Create a to-do list

For the time being, I made a to-do list with my own knowledge. When I make a to-do list, I'm wondering, "What if it's 0?" Or "How do you express a large number?" Well, for the time being, I'll close my eyes and implement it according to the following. (Sorry, the TODO list committed on github is garbled.)

-[] Convert from 1 to I -[] Convert from 2 to II -[] Convert from 3 to III -[] Convert from 4 to IV -[] Convert from 5 to V -[] Convert from 6 to VI -[] 9 → Convert to IX -[] Convert from 10 to X -[] 11 Convert to XI

Create the first test, make it GREEN

Write the first test to commemorate.

@Test
public void test_one_is_converted_to_I() {
	RomanNumeralConverter converter = new RomanNumeralConverter();
	String result = converter.convert(1);
	assertThat(result).isEqualTo("I");
}

Of course, I haven't implemented it, so the test will fail. I want to bring it to the GREEN bar as quickly as possible, so I implemented the method below.

public String convert(int i) {
	return "I";
}

With this, I was able to worship the GREEN bar for the first time.

-[x] Convert from 1 to I

Create a second test, make it GREEN

Now let's write the second test.

@Test
public void test_two_is_converted_to_II() {
	RomanNumeralConverter converter = new RomanNumeralConverter();
	String result = converter.convert(2);
	assertThat(result).isEqualTo("II");
}

Now it's RED, hurry up to GREEN.

public String convert(int i) {
	return i == 1 ? "I" : "II";
}

This completes the following of the TODO list.

-[x] Convert from 2 to II

Refactor in the GREEN bar

As shown below, it is possible to do it even if the number is other than 1/2.

public String convert(int number) {
	StringBuilder builder = new StringBuilder();
	IntStream.rangeClosed(1, number).forEach(i -> builder.append("I"));
	return builder.toString();
}

The tests have passed cleanly, so you can rest assured that the refactoring will not affect you. The test class also moved the instance that was new in each method to the field.

Create a third test and refactor

I created a third test.

@Test
public void test_three_is_converted_to_III() {
	String result = this.converter.convert(3);
	assertThat(result).isEqualTo("III");
}

It was already GREEN. Since the test method seems to be inlined, I modified the test class as follows.

@Test
public void test_three_is_converted_to_III() {
	assertThat(this.converter.convert(3)).isEqualTo("III");
}

-[x] Convert from 3 to III

Create a fourth test, make it GREEN

It seems that IV is difficult, so I will start with the simple V pattern.

@Test
public void test_five_is_converted_to_V() {
	assertThat(this.converter.convert(5)).isEqualTo("V");
}

Naturally, the test will fail. I want to worship the GREEN bar at the fastest speed, so write the following code and pass it through.

public String convert(int number) {
	StringBuilder builder = new StringBuilder();
	if (number == 5) {
		return "V";
	}
	IntStream.rangeClosed(1, number).forEach(i -> builder.append("I"));
	return builder.toString();
}

-[x] Convert from 5 to V

Refactor in GREEN (part2)

If it is 5, I don't like the part of returning V. The process you want to do is to add V if it is 5 or more and subtract 5 from the number to be converted.

I will drop it in the code as it is.

if (number >= 5) {
	builder.append("V");
	number = number - 5;
}

It's OK. The test has passed properly.

Create a test where 6 is converted to a VI

Describe the test below. The test will pass.

@Test
public void test_six_is_converted_to_VI() {
	assertThat(this.converter.convert(6)).isEqualTo("VI");
}

** Isn't this test unnecessary? Of course, there is an argument that **. It's not just a matter of increasing the number of test methods. ..

Since it is different from the purpose of this time, I will omit it, When putting it into practice, I think it will be a source of discussion as a team.

There is no rule that you can't change the to-do list once you've made it, so I think that we should update it steadily in response to the discussion.

-[x] 6 → Convert to VI

Create a test where 10 is converted to X and make it GREEN

At this point, I think you've gradually learned how to proceed with test-driven development. Create the following test, and of course the test will fail.

@Test
public void test_ten_is_converted_to_X() {
	assertThat(this.converter.convert(10)).isEqualTo("X");
}

It seemed like I should use the case of V, so I added the following branches and passed the test.

if (number >= 10) {
	builder.append("X");
	number = number - 10;
}

-[x] Convert from 10 to X

Refactor in GREEN (part3)

I forgot to write an article and did various things. The final result is as follows.

StringBuilder builder;
int number;

public String convert(int numberToConvert) {
	initVariables(numberToConvert);
	appendX();
	appendV();
	appendI();
	return builder.toString();
}

private void initVariables(int numberToConvert) {
	this.builder = new StringBuilder();
	this.number = numberToConvert;
}

private void appendX() {
	if (number >= 10) {
		builder.append("X");
		number = number - 10;
	}
}

Move StringBuilder and the number to be converted to the field, Changed to initialize every time convert.

However, due to this, multi-thread support is no longer possible. The caller needs to consider it.

I added the following to the test class so that the instance is new every time.

@BeforeEach
public void setup() {
	this.converter = new RomanNumeralConverter();
}

Create a test where 11 is converted to XI

I don't think there is any need for explanation.

@Test
public void test_eleven_is_converted_to_XI() {
	assertThat(this.converter.convert(11)).isEqualTo("XI");
}

-[x] 11 Convert to XI

Create a test where 4 is converted to IV

Create the test below.

@Test
public void test_four_is_converted_to_IV() {
	assertThat(this.converter.convert(4)).isEqualTo("IV");
}

In order to make it GREEN in a hurry, I implemented the following method and it became GREEN safely.

private void appendIV() {
	if (number >= 4) {
		builder.append("IV");
		number = number - 4;
	}
}

Similarly, we implemented 9 → IX conversion testing and logic.

-[x] Convert from 4 to IV -[x] 9 → Convert to IX

With this, all the to-do lists that were originally prepared are checked.

Review the to-do list

Now that I've learned more about Roman numeral specifications, I've revisited my to-do list. It seems that the conversion of Roman numerals is perfect if all of the following are met.

-[] Convert from 40 to XL -[] Convert from 50 to L -[] Convert from 90 to XC -[] Convert from 100 to C -[] 400 → Convert to CD -[] Convert from 500 to D -[] Convert from 900 to CM -[] Convert from 1000 to M -[] 3999 → Convert to MMMCMXCIX -[] An error will occur if the argument is 0 or less. -[] An error will occur if the argument is 4000 or more.

Refactor in GREEN (part4)

The following append Hoges have been increasing steadily, and the desire for commonality has increased.

private void appendIX() {
	if (number >= 9) {
		builder.append("IX");
		number = number - 9;
	}
}

private void appendV() {
	if (number >= 5) {
		builder.append("V");
		number = number - 5;
	}
}

Using a functional interface, I've declared the following and completely abolished methods like the one above. The implementation class is now about half the length.

private BiConsumer<Integer, String> appendRoman = (i, str) -> {
	while (number >= i) {
		builder.append(str);
		number = number - i;
	}
};

The methods previously declared in appendHoge can now be described as follows.

appendRoman.accept(5, "V");
appendRoman.accept(4, "IV");
appendRoman.accept(1, "I");

Add other tests

At this point, implementation is insanely easy. It takes about 30 seconds to add and implement a test case.

-[x] Convert from 40 to XL -[x] Convert from 50 to L -[x] Convert from 90 to XC -[x] Convert from 100 to C -[x] 400 → Convert to CD -[x] Convert from 500 to D -[x] Convert from 900 to CM -[x] Convert from 1000 to M -[x] 3999 → Convert to MMMCMXCIX

Argument check test implementation

I implemented the test below.

@Test
public void test_zero_cannot_be_converted() {
	assertThrows(IllegalArgumentException.class, () -> this.converter.convert(0));
}

It's been a long time since I became RED, so I'll hurry to make it GREEN.

private void checkArgument(int numberToConvert) {
	if (numberToConvert == 0) {
		throw new IllegalArgumentException();
	}
}

Similarly, the test for 4000 is also described, and it will be refactored after setting it to GREEN. The final result is as follows.

private void checkArgument(int numberToConvert) {
	if (!(0 < numberToConvert && numberToConvert < 4000)) {
		throw new IllegalArgumentException("Argument must be between 1 and 3999.");
	}
}

-[x] An error will occur if the argument is 0 or less. -[x] Error if the argument is 4000 or more

Test class refactoring

By the way, if you look at the execution result of jUnit, there are a lot of test cases. There are 20 tests written in parallel, and you can see what you are doing by looking at each method, but ** I don't know if I can test it at a glance. ** **

So refactor. You can structure your test class from jUnit5 with the @ Nested annotation. Use this to refactor your test class for working documentation.

Well, it's a lot easier to understand than 20 tests lined up in parallel!

image.png

end.

Summary

When I noticed it, it became a big article. .. Also, despite the fact that I chose it properly, I learned a lot and I was absorbed in it. After all, it's fun to make something that works by yourself.

I've been touching mainly on typescript recently, so I was happy to realize that the functional interface could be abstracted without any hesitation.

However, I feel that there are many parts that cannot be easily conveyed by sentences alone. I would like to continue to spread TDD through live coding, pair pros, mob pros, etc.!

Recommended Posts

I wrote a code to convert numbers to romaji in TDD
I tried to write code like a type declaration in Ruby
[Ruby] I want to put an array in a variable. I want to convert to an array
I wrote a route search program in TDD and refactored it
Convert numbers to Roman numerals in Ruby
Code to escape a JSON string in Java
I wanted to make (a == 1 && a == 2 && a == 3) true in Java
I wrote a prime factorization program in Java
Sample code to convert List to List <String> in Java Stream
I want to use a little icon in Rails
I tried to create a Clova skill in Java
How to display a browser preview in VS Code
How to convert A to a and a to A using AND and OR in Java
I want to click a GoogleMap pin in RSpec
[Java] I want to convert a byte array to a hexadecimal number
I want to find a relative path in a situation using Path
A memorandum when I investigated how to convert from Epoch Time to Date type in Scala
I want to convert characters ...
Convert a Java byte array to a string in hexadecimal notation
[Beginner] I made a program to sell cakes in Java
I just wanted to make a Reactive Property in Java
A story I was addicted to in Rails validation settings
How to select a specified date by code in FSCalendar
Even in Java, I want to output true with a == 1 && a == 2 && a == 3
I want to create a Parquet file even in Ruby
I tried to implement a buggy web application in Kotlin
I tried to make a client of RESAS-API in Java
I wrote a C parser (like) using PEG in Ruby
[Ruby] How to batch convert strings in an array to numbers
I tried to create a simple map app in Android Studio
I tried to illuminate the Christmas tree in a life game
[Ruby 3.0] A memo that I added a type definition to a library I wrote
I wrote a Stalin sort that feels like a mess in Java
I got stuck trying to write a where in clause in ActiveRecord
I want to convert InputStream to String
I wrote Goldbach's theorem in java
[Android] Convert Android Java code to Kotlin
I tried to make a parent class of a value object in Ruby
[Rails] I want to send data of different models in a form
I want to select multiple items with a custom layout in Dialog
Even in Java, I want to output true with a == 1 && a == 2 && a == 3 (PowerMockito edition)
I wanted to implement a slide show in a fashionable way with slick.
I want to display a PDF in Chinese (Korean) with thin reports
I wrote a Lambda function in Java and deployed it with SAM
I thought about the best way to create a ValueObject in Ruby
I tried to make a talk application in Java using AI "A3RT"
I want to ForEach an array with a Lambda expression in Java
[Ruby] Returns characters in a pyramid shape according to the entered numbers
"Teacher, I want to implement a login function in Spring" ① Hello World
I want to develop a web application!
I tried a calendar problem in Ruby
I want to write a nice build.gradle
I tried migrating Processing to VS Code
Convert request parameter to Enum in Spring
How to insert a video in Rails
I want to write a unit test!
Convert SVG files to PNG files in Java
Steps to set a favicon in Rails
Convert an array of strings to numbers
Notation to put a variable in a string
I want to use @Autowired in Servlet