π€ Chapter 5. λͺ©κ³Ό ν μ€νΈ μ·¨μ½μ±
π₯ λͺ©κ³Ό μ€ν ꡬλΆβ
π ν μ€νΈ λμ μ νβ
ν
μ€νΈ λμμ λͺ¨λ μ νμ λΉμ΄μμ© κ°μ§ μμ‘΄μ±μ μ€λͺ
νλ ν¬κ΄μ μΈ μ©μ΄λ€. ν
μ€νΈ λμμ μ£Ό μ©λλ ν
μ€νΈλ₯Ό νΈλ¦¬νκ² νλ κ²μ΄λ€.
ν
μ€νΈ λμμ λͺ©κ³Ό μ€ν
μ λ κ°μ§ μ νμΌ λ‘ λλ μ μλ€.
μ΄ λ μ νμ μ°¨μ΄μ μ λ€μκ³Ό κ°λ€.
- λͺ©μ μΈλΆλ‘ λκ°λ μνΈ μμ©μ λͺ¨λ°©νκ³ κ²μ¬νλ λ° λμμ΄ λλ€. μ΄λ¬ν μνΈ μμ©μ SUTκ° μνλ₯Ό λ³κ²½νκΈ° μν μμ‘΄μ±μ νΈμΆνλ κ²μ ν΄λΉνλ€.
- μ€ν μ λ΄λΆλ‘ λ€μ΄μ€λ μνΈ μμ©μ λͺ¨λ°©νλ λ° λμμ΄ λλ€. μ΄λ¬ν μνΈ μμ©μ SUTκ° μ λ ₯ λ°μ΄ν°λ₯Ό μ»κΈ° μν μμ‘΄μ±μ νΈμΆνλ κ²μ ν΄λΉνλ€.
μ€νμ΄λ λͺ©κ³Ό κ°μ μν μ νλ€. μ€νμ΄λ μλμΌλ‘ μμ±νλ λ°λ©΄, λͺ©μ λͺ© νλ μμν¬μ λμμ λ°μ μμ±λλ€.
μ€ν
, λλ―Έ, νμ΄ν¬μ μ°¨μ΄λ μΌλ§λ λλνμ§μ μλ€. λλ―Έλ λ κ°μ΄λ κ°μ§ λ¬Έμμ΄κ³Ό κ°μ΄ λ¨μνκ³ νλμ½λ©λ κ°μ΄λ€. SUTμ λ©μλ μκ·Έλμ²λ₯Ό λ§μ‘±μν€κΈ° μν΄ μ¬μ©νκ³ μ΅μ’
κ²°κ³Όλ₯Ό λ§λλ λ° μν₯μ μ£Όμ§ μλλ€. μ€ν
μ λ μ κ΅νλ€. μλ리μ€λ§λ€ λ€λ₯Έ κ°μ λ°ννκ²λ ꡬμ±ν μ μλλ‘ νμν κ²μ λ€ κ°μΆ μμ ν μμ‘΄μ±μ΄λ€. λ§μ§λ§μΌλ‘ νμ΄ν¬λ λλ€μμ λͺ©μ μ λΆν©νλ μ€ν
κ³Ό κ°λ€. μ°¨μ΄μ μ μμ±μ μλ€. νμ΄ν¬λ λ³΄ν΅ μμ§ μ‘΄μ¬νμ§ μλ μμ‘΄μ±μ λ체νκ³ μ ꡬννλ€.
λͺ©κ³Ό μ€ν μ μ°¨μ΄μ μλ μ μνλΌ. λͺ©μ SUTμ κ΄λ ¨ μμ‘΄μ± κ°μ μνΈ μμ©μ λͺ¨λ°©νκ³ κ²μ¬νλ λ°λ©΄, μ€ν μ λͺ¨λ°©λ§ νλ€. μ΄λ μ€μν μ°¨μ΄μ μ΄λ€.
[Fact]
public void Sending_a_greetings_email() {
var mock = new Mock<IEmailGateway>();
var sut = new Controller(mock.Object);
sut.GreetUser("user@email.com");
mock.Verify(
x => x.SendGreetingsEmail("user@email.com"),
Times.Once
);
}
μ΄ ν΄λμ€λ ν μ€νΈ λμ(λͺ©)μ λ§λ€ μ μλ λꡬλ€. λꡬλ‘μμ λͺ©μ μ¬μ©ν΄ λͺ©κ³Ό μ€ν , μ΄ λκ°μ§ μ νμ ν μ€νΈ λμμ μμ±ν μ μκΈ° λλ¬Έμ λꡬλ‘μμ λͺ©κ³Ό ν μ€νΈ λμμΌλ‘μ λͺ©μ νΌλνμ§ μλ κ²μ΄ μ€μνλ€.
[Fact]
public void Creating_a_report() {
var stub = new Mock<IDatabase>();
stub.Setup(x => x.GetNumberOfUsers()).Returns(10);
var sut = new Controller(stub.Object);
Report report = sut.CreateReport();
Assert.Equal(10, report.NumberOfUsers);
}
μ΄ ν
μ€νΈ λμμ λ΄λΆλ‘ λ€μ΄μ€λ μνΈ μμ©, μ¦ SUTμ μ
λ ₯ λ°μ΄ν°λ₯Ό μ 곡νλ νΈμΆμ λͺ¨λ°©νλ€. λ°λ©΄ μ΄μ μμ μμ SendGreetingsEmail
μ λν νΈμΆμ μΈλΆλ‘ λκ°λ μνΈ μμ©μ΄κ³ κ·Έ λͺ©μ μ λΆμμ©μ μΌμΌν€λ κ²(μ΄λ©μΌ λ°μ‘)λΏμ΄λ€.
π μ€ν μΌλ‘ μνΈ μμ©μ κ²μ¦νμ§ λ§λΌβ
λͺ©κ³Ό μ€ν μ μ°¨μ΄λ μ€ν κ³Όμ μνΈ μμ©μ κ²μ¦νμ§ λ§λΌλ μ§μΉ¨μμ λΉλ‘―λλ€. SUTμμ μ€ν μΌλ‘μ νΈμΆμ SUTκ° μμ±νλ μ΅μ’ κ²°κ³Όκ° μλλ€. μ΄λ¬ν νΈμΆμ μ΅μ’ κ²°κ³Όλ₯Ό μ°μΆνκΈ° μν μλ¨μΌ λΏμ΄λ€. μ¦, μ€ν μ SUTκ° μΆλ ₯μ μμ±νλλ‘ μ λ ₯μ μ 곡νλ€.
4μ₯μμ μ΄ν΄λ΄£λ―μ΄, ν μ€νΈμμ κ±°μ§ μμ±μ νΌνκ³ λ¦¬ν©ν°λ§ λ΄μ±μ ν₯μμν€λ λ°©λ²μ ꡬν μΈλΆ μ¬νμ΄ μλλΌ μ΅μ’ κ²°κ³Όλ₯Ό κ²μ¦νλ κ²λΏμ΄λ€.
mock.Verify(x => x.SendGreetingsEmail("user@email.com"));
μ μμ μ μ ꡬ문μ μ€μ κ²°κ³Όμ λΆν©νλ©°, ν΄λΉ κ²°κ³Όλ λλ©μΈ μ λ¬Έκ°μκ² μλ―Έκ° μλ€.
λ€μ μμ λ κΉ¨μ§κΈ° μ¬μ΄ ν μ€νΈμ μμ ν΄λΉνλ€.
[Fact]
public void Creating_a_report() {
var stub = new Mock<IDatabase>();
stub.Setup(x => x.GetNumberOfUsers()).Returns(10);
var sut = new Controller(stub.Object);
Report report = sut.CreateReport();
Assert.Equal(10, report.NumberOfUsers);
// μ€ν
μΌλ‘ μνΈ μμ© κ²μ¦
stub.Verify(
x => x.GetNumberOfUsers(),
Times.Once
);
}
μ΅μ’
κ²°κ³Όκ° μλ μ¬νμ κ²μ¦νλ μ΄λ¬ν κ΄νμ κ³Όμ λͺ
μΈλΌκ³ λΆλ₯Έλ€.
κ³Όμ λͺ
μΈλ μνΈ μμ©μ κ²μ¬ν λ κ°μ₯ ννκ² λ°μνλ€. μ€ν
κ³Όμ μνΈ μμ©μ νμΈνλ κ²μ μ½κ² λ°κ²¬ν μ μλ κ²°ν¨μ΄λ€. ν
μ€νΈμ μ€ν
κ³Όμ μνΈ μμ©μ νμΈν΄μλ μ λκΈ° λλ¬Έμ΄λ€. λͺ©μ λ 볡μ‘νλ€. λͺ©μ μ°λ©΄ 무쑰건 ν
μ€νΈ μ·¨μ½μ±μ μ΄λνλ κ²μ μλμ§λ§, λλ€μκ° κ·Έλ λ€.
π λͺ©κ³Ό μ€ν ν¨κ» μ°κΈ°β
[Fact]
public void Purchase_fails_when_not_enough_inventory() {
var storeMock = new Mock<IStore>();
storeMock.Setup(x => x.HasEnoughInventory(Product.Shampoo, 5)).Returns(false);
var sut = new Customer();
bool success = sut.Purchase(
storeMock.Object, Product.Shampoo, 5
);
Assert.False(success);
storeMock.Verify(
x => x.RemoveInventory(Product.Shampoo, 5),
Times.Never
);
}
μ΄ ν
μ€νΈλ λ κ°μ§ λͺ©μ μΌλ‘ storeMock
μ μ¬μ©νλ€. μ€λΉλ μλ΅μ λ°ννκ³ SUTμμ μνν λ©μλ νΈμΆμ κ²μ¦νλ€. κ·Έλ¬λ μ΄λ λ κ°μ§μ μλ‘ λ€λ₯Έ λ©μλλ€. ν
μ€νΈλ HasEnoughInventory()
μμ μλ΅μ μ€μ ν λ€μ RemoveInventory()
μ λν νΈμΆμ κ²μ¦νλ€. λ°λΌμ μ€ν
κ³Όμ μνΈ μμ©μ κ²μ¦νμ§ λ§λΌλ κ·μΉμ μ¬κΈ°μλ μλ°°λμ§ μλλ€.
ν μ€νΈ λμμ λͺ©μ΄λ©΄μ μ€ν μ΄μ§λ§, μ¬μ ν λͺ©μ΄λΌκ³ λΆλ₯΄μ§ μ€ν μ΄λΌκ³ λΆλ¦¬μ§λ μλλ€.
π λͺ©κ³Ό μ€ν μ λͺ λ Ήκ³Ό μ‘°νμ μ΄λ»κ² κ΄λ ¨λΌ μλκ°?β
λͺ©κ³Ό μ€ν μ κ°λ μ λͺ λ Ή μ‘°ν λΆλ¦¬(CQS) μμΉκ³Ό κ΄λ ¨μ΄ μλ€. CQS μμΉμ λ°λ₯΄λ©΄ λͺ¨λ λ©μλλ λͺ λ Ήμ΄λ μ‘°νμ¬μΌ νλ©°, μ΄ λμ νΌμ©ν΄μλ μ λλ€. λͺ λ Ήμ λΆμμ©μ μΌμΌν€κ³ μ΄λ€ κ°λ λ°ννμ§ μλ λ©μλλ€. λΆμμ©μ μλ‘ κ°μ²΄ μν λ³κ²½, νμΌ μμ€ν λ΄ νμΌ λ³κ²½ λ±μ΄ μλ€. μ‘°νλ κ·Έ λ°λλ‘, λΆμμ©μ΄ μκ³ κ°μ λ°ννλ€.
μ΄ μμΉμ λ°λ₯΄κ³ μ ν κ²½μ°, λ©μλκ° λΆμμ©μ μΌμΌν€λ©΄ ν΄λΉ λ©μλμ λ°ν νμ
μ΄ void
μΈμ§ νμΈνλΌ. κ·Έλ¦¬κ³ λ©μλκ° κ°μ λ°ννλ©΄ λΆμμ©μ΄ μμ΄μΌ νλ€. λ€μ λ§ν΄, μ§λ¬Έμ ν λ λ΅μ΄ λ¬λΌμ Έμλ μ λλ€.
λͺ λ Ήμ λ체νλ ν μ€νΈ λμμ λͺ©μ΄λ€. λ§μ°¬κ°μ§λ‘ μ‘°νλ¦ λ체νλ ν μ€νΈ λμμ μ€ν μ΄λ€.
μ μμ μ λ κ°μ§ ν μ€νΈλ₯Ό λ€μ μ΄ν΄λ³΄μ
var mock = new Mock<IEmailGateway>();
mock.Verify(x => x.SendGreetingsEmail("user@email.com"));
var stub = new Mock<IDatabase>();
stub.Setup(x => x.GetNumberOfUsers()).Returns(10);
SendGreetingsEmail()
μ μ΄λ©μΌμ 보λ΄λ λΆμμ©μ΄ μλ λͺ
λ Ήμ΄λ€. μ΄ λͺ
λ Ήμ λ체νλ ν
μ€νΈ λμμ΄ λͺ©μ΄λ€. λ°λ©΄ GetNumberOfUsers()
λ κ°μ λ°ννκ³ λ°μ΄ν°λ² μ΄μ€ μνλ₯Ό λ³κ²½νμ§ μλ μ‘°νλ€. ν΄λΉ ν
μ€νΈμ γ·ν
μ€νΈ λμμ μ€ν
μ΄λ€.
π₯ μλ³ν μ μλ λμκ³Ό ꡬν μΈλΆ μ¬νβ
π μλ³ν μ μλ λμμ κ³΅κ° APIμ λ€λ₯΄λ€β
λͺ¨λ μ ν μ½λλ 2μ°¨μμΌλ‘ λΆλ₯ν μ μλ€.
- κ³΅κ° API λλ λΉκ³΅κ° API
- μλ³ν μ μλ λμ λλ ꡬν μΈλΆ μ¬ν
μλ³ν μ μλ λμκ³Ό λ΄λΆ ꡬν μΈλΆ μ¬νμλ λ―Έλ¬ν μ°¨μ΄κ° μλ€. μ½λκ° μμ€ν μ μλ³ν μ μλ λμμ΄λΌλ©΄ λ€μ μ€ νλλ₯Ό ν΄μΌ νλ€.
- ν΄λΌμ΄μΈνΈκ° λͺ©νλ₯Ό λ¬μ±νλ λ° λμμ΄ λλ μ°μ°μ λ ΈμΆνλΌ. μ°μ°μ κ³μ°μ μννκ±°λ λΆμμ©μ μ΄λνκ±°λ λ λ€ νλ λ©μλλ€.
- ν΄λΌμ΄μΈνΈκ° λͺ©νλ₯Ό λ¬μ±νλ λ° λμμ΄ λλ μνλ₯Ό λ ΈμΆνλΌ. μνλ μμ€ν μ νμ¬ μνλ€.
ꡬν μΈλΆ μ¬νμ μ΄ λ κ°μ§ μ€ μ무κ²λ νμ§ μλλ€.
μ½λκ° μλ³ν μ μλ λμμΈμ§ μ¬λΆλ ν΄λΉ ν΄λΌμ΄μΈνΈκ° λꡬμΈμ§, κ·Έλ¦¬κ³ ν΄λΉ ν΄λΌμ΄μΈνΈμ λͺ©νκ° λ¬΄μμΈμ§μ λ¬λ € μλ€. μλ³ν μ μλ λμμ΄ λλ €λ©΄ μ½λκ° μ΄λ¬ν λͺ©ν μ€ νλμλΌλ μ§μ μ μΈ κ΄κ³κ° μμ΄μΌ νλ€. ν΄λΌμ΄μΈνΈλΌλ λ¨μ΄λ μ½λκ° μλ μμΉμ λ°λΌ λ€λ₯Έ κ²μ μλ―Έν μ μλ€.
μ΄μμ μΌλ‘ μμ€ν μ κ³΅κ° APIλ μλ³ν μ μλ λμκ³Ό μΌμΉν΄μΌ νλ©°, λͺ¨λ ꡬν μΈλΆ μ¬νμ ν΄λΌμ΄μΈνΈ λμ 보μ΄μ§ μμμΌ νλ€. μ΄λ¬ν μμ€ν μ API μ€κ³κ° μλΌ μλ€.
π ꡬν μΈλΆ μ¬ν μ μΆ: μ°μ°μ μβ
ꡬν μΈλΆ μ¬νμ΄ κ³΅κ° APIλ‘ μ μΆλλ μ½λμ μλ₯Ό μ΄ν΄λ³΄μ.
public class User {
public string Name { get; set; }
public string NormalizeName(string name) {
string result = (name ?? "").Trim();
if (result.length > 50)
return result.Substring(0, 50);
return result;
}
}
public class UserController {
public void RenameUser(int userId, string newName) {
User user = GetUserFromDatabase(userId);
string normalizedName = user.NormalizeName(newName);
user.Name = normalizedName;
SaveUserToDatabase(user);
}
}
μμ±κ³Ό λ©μλκ° λ λ€ κ³΅κ°λ€. λ°λΌμ ν΄λμ€ APIλ₯Ό μ μ€κ³νλ €λ©΄ ν΄λΉ λ©€λ²κ° μλ³ν μ μλ λμμ΄ λκ² ν΄μΌ νλ€. μ΄λ₯Ό μν΄μλ λ€μ λ κ°μ§ μ€ νλλ₯Ό ν΄μΌ νλ€.
- ν΄λΌμ΄μΈνΈκ° λͺ©νλ₯Ό λ¬μ±νλ λ° λμμ΄ λλ μ°μ°μ λ ΈμΆνλΌ.
- ν΄λΌμ΄μΈνΈκ° λͺ©νλ₯Ό λ¬μ±νλ λ° λμμ΄ λλ μνλ₯Ό λ ΈμΆνλΌ.
Name
μμ±λ§ μ΄ μꡬ μ¬νμ μΆ©μ‘±νλ€. NormalizeName
λ©μλλ μμ
μ΄μ§λ§, ν΄λΌμ΄μΈνΈμ λͺ©νμ μ§κ²°λμ§ μλλ€. NormalizeName
μ ν΄λμ€μ κ³΅κ° APIλ‘ μ μΆλλ ꡬν μΈλΆ μ¬νμ΄.
public class User {
private string _name;
public string Name {
get => _name;
set => _name = NormalizeName(value);
}
public string NormalizeName(string name) {
string result = (name ?? "").Trim();
if (result.Length > 50)
return result.Substring(0, 50);
return result;
}
}
public class UserController {
public void RenameUser(int userId, string newName) {
User user = GetUserFromDatabase(userId);
user.Name = newName;
SaveUserToDatabase(user);
}
}
μλ³ν μ μλ λμ(Name
μμ±)λ§ κ³΅κ°λΌμκ³ , ꡬν μΈλΆ μ¬ν₯(NormalizeName
λ©μλ)μ λΉκ³΅κ° API λ€μ μ¨κ²¨μ Έ μλ€.
ν΄λμ€κ° ꡬν μΈλΆ μ¬νμ μ μΆνλμ§ νλ¨νλ λ° λμμ΄ λλ μ μ©ν κ·μΉμ΄ μλ€. λ¨μΌν λͺ©νλ₯Ό λ¬μ±νκ³ μ ν΄λμ€μμ νΈμΆν΄μΌ νλ μ°μ°μ μκ° 1λ³΄λ€ ν¬λ©΄ ν΄λΉ ν΄λμ€μμ ꡬν μΈλΆ μ¬νμ μ μΆν κ°λ₯μ±μ΄ μλ€. μ΄μμ μΌλ‘λ λ¨μΌ μ°μ°μΌλ‘ κ°λ³ λͺ©νλ₯Ό λ¬μ±ν΄μΌ νλ€. μλ₯Ό λ€μ΄ 첫 μμ μΈ UserController
λ User
μ λ κ°μ§ μμ
μ μ¬μ©ν΄μΌ νλ€.
string normalizedName = user.NormalizeName(newName);
user.Name = normalizedName;
리ν©ν°λ§ νμ μ°μ° μκ° 1λ‘ κ°μνλ€.
user.Name = newName;
π μ μ€κ³λ APIμ μΊ‘μνβ
ꡬν μΈλΆ μ¬νμ λ
ΈμΆνλ©΄ λΆλ³μ± μλ°μ κ°μ Έμ¨λ€. μλ λ²μ μ User
λ ꡬν μΈλΆ μ¬νμ μ μΆν λΏλ§ μλλΌ μΊ‘μνλ₯Ό μ λλ‘ μ μ§νμ§ λͺ»νλ€.
κ³μν΄μ μ¦κ°νλ μ½λ 볡μ‘λμ λμ²ν μ μλ λ°©λ²μ μ€μ§μ μΌλ‘ μΊ‘μν λ§κ³ λ μλ€. μ½λ APIκ° ν΄λΉ μ½λλ‘ ν μ μλ κ²κ³Ό ν μ μλ κ²μ μλ €μ£Όμ§ μμΌλ©΄ μ½λκ° λ³κ²½λμ λ λͺ¨μμ΄ μκΈ°μ§ μλλ‘ λ§μ μ 보λ₯Ό μΌλμ λ¬μΌ νλ€. μ΄λ κ² νλ λ° κ°μ₯ μ’μ λ°©λ²μ μΊ‘μνλ₯Ό μ¬λ°λ₯΄κ² μ μ§ν΄ μ½λλ² μ΄μ€μμ μλͺ»ν μ μλ μ΅μ μ‘°μ°¨ μ 곡νμ§ μλλ‘ νλ κ²μ΄λ€. μΊ‘μνλ κΆκ·Ήμ μΌλ‘ λ¨μ ν μ€νΈμ λμΌν λͺ©νλ₯Ό λ¬μ±νλ€. μ¦, μννΈμ¨μ΄ νλ‘μ νΈμ μ§μμ μΈ μ±μ₯μ κ°λ₯νκ² νλ κ²μ΄λ€.
- ꡬν μΈλΆ μ¬νμ μ¨κΈ°λ©΄ ν΄λΌμ΄μΈνΈμ μμΌμμ ν΄λμ€ λ΄λΆλ₯Ό κ°λ¦΄ μ μκΈ° λλ¬Έμ λ΄λΆλ₯Ό μμμν¬ μνμ΄ μ λ€.
- λ°μ΄ν°μ μ°μ°μ κ²°ν©νλ©΄ ν΄λΉ μ°μ°μ΄ ν΄λμ€μ λΆλ³μ±μ μλ°νμ§ μλλ‘ ν μ μλ€.
π ꡬν μΈλΆ μ¬ν μ μΆ: μνμ μβ
λ€μ μμ λ 4μ₯μμ λ³Έ MessageRenderer
ν΄λμ€κ° μλ€.
public class MessageRenderer : IRenderer {
public IReadOnlyList<IRenderer> SubRenderers { get; }
public MessageRenderer() {
SubRenderers = new List<IRenderer> {
new HeaderRenderer(),
new BodyRenderer(),
new FooterRenderer(),
};
}
public string Render(Message message) {
return SubRenderers
.Select(x => x.Render(message))
.Aggregate("", (str1, str2) => str1 + str2);
}
}
νμ λ λλ§ ν΄λμ€ μ»¬λ μ
μ΄ κ³΅κ°λ€. κ·Έλ¬λ μ΄ μ»¬λ μ
μ΄ μλ³ν μ μλ λμμΈκ°? μλλ€. ν΄λΌμ΄μΈνΈμκ² νμν ν΄λμ€ λ©€λ²λ Render
λ©μλλΏμ΄λ€. λ°λΌμ SubRenderers
λ ꡬν μΈλΆ μ¬ν μ μΆμ΄λ€.
μ΄ ν
μ€νΈλ ꡬν μΈλΆ μ¬νμ κ²°ν©λΌ μμ΄ κΉ¨μ§κΈ° μ½κ³ ν
μ€νΈ λμμ Render
λ©μλλ‘ λ°κΏμ λΆμμ μ±μ ν΄μνλ€. μ’μ λ¨μ ν
μ€νΈμ μ μ€κ³λ API μ¬μ΄μλ λ³Έμ§μ μΈ κ΄κ³κ° μλ€. λͺ¨λ ꡬν μΈλΆ μ¬νμ λΉκ³΅κ°λ‘ νλ©΄ ν
μ€νΈκ° μλ³ν μ μλ λμμ κ²μ¦νλ κ² μΈμλ λ€λ₯Έ μ νμ§κ° μμΌλ©°, μ΄λ‘ μΈν΄ 리ν©ν°λ§ λ΄μ±λ μλμΌλ‘ μ’μμ§λ€.
APIλ₯Ό μ μ€κ³νλ©΄ λ¨μ ν μ€νΈλ μλμΌλ‘ μ’μμ§λ€.
μ μ€κ³λ APIμ μ μμμ λΉλ‘―λ λ λ€λ₯Έ μ§μΉ¨μΌλ‘, μ°μ°κ³Ό μνλ₯Ό μ΅μνμΌλ‘ λ ΈμΆν΄μΌ νλ€. ν΄λΌμ΄μΈνΈκ° λͺ©νλ₯Ό λ¬μ±νλ λ° μ§μ μ μΌλ‘ λμμ΄ λλ μ½λλ§ κ³΅κ°ν΄μΌ νλ©°, λ€λ₯Έ λͺ¨λ κ²μ ꡬν μΈλΆ μ¬νμ΄λ―λ‘ λΉκ³΅κ° API λ€μ μ¨κ²¨μΌ νλ€.
μλ³ν μ μλ λμ | ꡬν μΈλΆ μ¬ν | |
---|---|---|
κ³΅κ° | μ’μ | λμ¨ |
λΉκ³΅κ° | ν΄λΉ μμ | μ’μ |
π₯ λͺ©κ³Ό ν μ€νΈ μ·¨μ½μ± κ°μ κ΄κ³β
π μ‘κ°ν μν€ν μ² μ μβ
μ ν리μΌμ μλΉμ€ κ³μΈ΅μ λλ©μΈ κ³μΈ΅ μμ μμΌλ©° μΈλΆ νκ²½κ³Όμ ν΅μ μ μ‘°μ νλ€. μ΄ κ³μΈ΅μ λλ©μΈ ν΄λμ€μ νλ‘μΈμ€ μΈλΆ μμ‘΄μ± κ°μ μμ μ μ‘°μ νλ€. λ€μμ μ ν리μΌμ΄μ μλΉμ€μ λν μ‘°μ μ μλ°.
- λ°μ΄ν°λ² μ΄μ€λ₯Ό μ‘°ννκ³ ν΄λΉ λ°μ΄ν°λ‘ λλ©μΈ ν΄λμ€ μΈμ€ν΄μ€ ꡬ체ν
- ν΄λΉ μΈμ€ν΄μ€μ μ°μ° νΈμΆ
- κ²°κ³Όλ₯Ό λ°μ΄ν°λ² μ΄μ€μ λ€μ μ μ₯
μ ν리μΌμ΄μ μλΉμ€ κ³μΈ΅κ³Ό λλ©μΈ κ³μΈ΅μ μ‘°ν©μ μ‘κ°νμ νμ±νλ©°, μ΄ μ‘κ°νμ μ ν리μΌμ΄μ μ λνλΈλ€. λν λ€λ₯Έ μ ν리μΌμ΄μ κ³Ό μν΅ν μ μκ³ , λ€λ₯Έ μ ν리μΌμ΄μ λ μ‘κ°νμ λνλΈλ€. μ‘κ°ν μν€ν μ²μ λͺ©μ μ μΈ κ°μ§ μ€μν μ§μΉ¨μ κ°μ‘°νλ κ²μ΄λ€.
- λλ©μΈ κ³μΈ΅κ³Ό μ ν리μΌμ΄μ μλΉμ€ κ³μΈ΅ κ°μ κ΄μ¬μ¬ λΆλ¦¬
- μ ν리μΌμ΄μ λ΄λΆ ν΅μ : μ‘κ°ν μν€ν μ²λ μ ν리μΌμ΄μ λ²μμ€ κ³μΈ΅μμ λλ©μΈ κ³μΈ΅μΌλ‘ νλ₯΄λ λ¨λ°©ν₯ μμ‘΄μ± νλ¦μ κ·μ νλ€. μ ν리μΌμ΄μ μλΉμ€ κ³μΈ΅κ³Ό λλ©μΈ κ³μΈ΅ κ°μ κ΄μ¬μ¬λ₯Ό λΆλ¦¬νλ κ²μ μ ν리μΌμ΄μ μλΉμ€ κ³μΈ΅μ΄ λλ©μΈ κ³μΈ΅μ λν΄ μλ κ²μ μλ―Ένμ§λ§, λ°λλ μλλ€. λλ©μΈ κ³μΈ΅μ μΈλΆ νκ²½μμ μμ ν 격리λΌμΌ νλ€.
- μ ν리μΌμ΄μ κ°μ ν΅μ : μΈλΆ μ ν리μΌμ΄μ μ μ ν리μΌμ΄μ μλΉμ€ κ³μΈ΅μ μλ κ³΅ν΅ μΈν°νμ΄μ€λ₯Ό ν΅ν΄ ν΄λΉ μ ν리μΌμ΄μ μ μ°κ²°λλ€. μ무λ λλ©μΈ κ³μΈ΅μ μ§μ μ κ·Όν μ μλ€. μ‘κ°νμ κ° λ©΄μ μ ν리μΌμ΄μ λ΄μΈλΆ μ°κ²°μ λνλΈλ€.
μλ³ν μ μλ λμμ λ°κΉ₯ κ³μΈ΅μμ μμͺ½μΌλ‘ νλ₯Έλ€. μΈλΆ ν΄λΌμ΄μΈνΈμκ² μ€μν λͺ©νλ κ°λ³ λλ©μΈ ν΄λμ€μμ λ¬μ±ν νμ λͺ©νλ‘ λ³νλλ€. λλ©μΈ ν΄λμ€μ κ²½μ° ν΄λΌμ΄μΈνΈλ μ ν리μΌμ΄μ μλΉμ€μ ν΄λΉνκ³ , μ ν리μΌμ΄μ μλΉμ€λ©΄ μΈλΆ ν΄λΌμ΄μΈνΈμ ν΄λΉνλ€.
π μμ€ν λ΄λΆ ν΅μ κ³Ό μμ€ν κ° ν΅μ β
μΌλ°μ μΈ μ ν리μΌμ΄μ μλ μμ€ν λ΄λΆ ν΅μ κ³Ό μμ€ν κ° ν΅μ μ΄ μλ€. μμ€ν λ΄λΆ ν΅μ μ μ ν리μΌμ΄μ λ΄ ν΄λμ€ κ°μ ν΅μ μ΄λ€. μμ€ν κ° ν΅μ μ μ ν리μΌμ΄μ μ΄ λ€λ₯Έ μ ν리μΌμ΄μ κ³Ό ν΅μ νλ κ²μ λ§νλ€.
μμ€ν λ΄λΆ ν΅μ μ ꡬν μΈλΆ μ¬νμ΄κ³ , μμ€ν κ° ν΅μ μ κ·Έλ μ§ μλ€.
μμ€ν κ° ν΅μ μ νΉμ±μ λ³λ μ ν리μΌμ΄μ κ³Ό ν¨κ» μ±μ₯νλ λ°©μμμ λΉλ‘―λλ€. μ±μ₯μ μ£Όμ μμΉ μ€ νλλ‘ νμ νΈνμ±μ μ§ν€λ κ²μ΄λ€. μμ€ν λ΄λΆμμ νλ 리ν©ν°λ§κ³Ό λ€λ₯΄κ², μΈλΆ μ ν리μΌμ΄μ κ³Ό ν΅μ ν λ μ¬μ©νλ ν΅μ ν¨ν΄μ νμ μΈλΆ μ ν리μΌμ΄μ μ΄ μ΄ν΄ν μ μλλ‘ μ μ§ν΄μΌ νλ€.
λͺ©μ μ¬μ©νλ©΄ μμ€ν κ³Ό μΈλΆ μ ν리μΌμ΄μ κ°μ ν΅μ ν¨ν΄μ νμΈν λ μ’λ€. λ°λλ‘ μμ€ν λ΄ ν΄λμ€ κ°μ ν΅μ μ κ²μ¦νλ λ° μ¬μ©νλ©΄ ν μ€νΈ ꡬν μΈλΆ μ¬νκ³Ό κ²°ν©λλ©°, κ·Έμ λ°λΌ 리ν©ν°λ§ λ΄μ± μ§νκ° λ―Έν‘ν΄μ§λ€.
π μμ€ν λ΄λΆ ν΅μ κ³Ό μμ€ν κ° ν΅μ μ μβ
CustomerController
ν΄λμ€λ λλ©μΈ ν΄λμ€μ μΈλΆ μ ν리μΌμ΄μ
κ°μ μμ
μ μ‘°μ νλ μ ν리μΌμ΄μ
μλΉμ€λ€.
public class CustomerController {
public bool Purchase(int customerId, int productId, int quantity) {
Customer customer = _customerRepository.GetById(customerId);
Product product = _productRepository.GetById(productId);
bool isSuccess = customer.Purchase(
_mainStore, product, quantity
);
if (isSuccess) {
_emailGateway.SendReceipt(
customer.Email, product.Name, quantity
);
}
return isSuccess;
}
}
ꡬ맀λΌλ λμμ μμ€ν
λ΄λΆ ν΅μ κ³Ό μμ€ν
κ° ν΅μ μ΄ λͺ¨λ μλ λΉμ¦λμ€ μ μ€μΌμ΄μ€λ€. μμ€ν
κ° ν΅μ μ CustomerController
μ ν리μΌμ΄μ
μλΉμ€μ λ κ°μ μΈλΆ μμ€ν
μΈ μλνν° μ ν리μΌμ΄μ
κ³Ό μ΄λ©μΌ κ²μ΄νΈμ¨μ΄ κ°μ ν΅μ μ΄λ€. μμ€ν
λ΄λΆ ν΅μ μ Customer
μ Store
λλ©μΈ ν΄λμ€ κ°μ ν΅μ μ΄λ€.
λ€μ μμ λ λͺ©μ μ¬μ©νλ νλΉν μλ₯Ό 보μ¬μ€λ€.
[Fact]
public void Successful_purchase() {
var mock = new Mock<IEmailGateway>();
var sut = new CustomerController(mock.Object);
bool isSuccess = sut.Purchase(
customerId: 1, productId: 2, quantity: 5
);
Assert.True(isSuccess);
mock.Verify(
x => x.SendReceipt(
"customer@email.com", "Shampoo", 5
), Times.Once
);
}
isSuccess
νλκ·Έλ μΈλΆ ν΄λΌμ΄μΈνΈμμλ νμΈν μ μμΌλ©°, κ²μ¦λ νμνλ€. νμ§λ§ μ΄ νλκ·Έλ λͺ©μ΄ νμ μκ³ , κ°λ¨ν κ° λΉκ΅λ§μΌλ‘ μΆ©λΆνλ€.
μ΄μ Customer
ν΄λμ€μ Store
ν΄λμ€ κ°μ ν΅μ μ λͺ©μ μ¬μ©ν ν
μ€νΈλ₯Ό μ΄ν΄λ³΄μ.
[Fact]
public void Purchase_succeeds_when_enough_inventory() {
var storeMock = new Mock<IStore>();
storeMock
.Setup(x => x.HasEnoughInventory(Product.Shampoo, 5))
.Returns(true);
var customer = new Customer();
bool success = customer.Purchase(
storeMock.Object, Product.Shampoo, 5
);
Assert.True(success);
storeMock.Verify(
x => x.RemoveInventory(Product.Shampoo, 5),
Times,Once
);
}
Customer
ν΄λμ€μμ Store
ν΄λμ€λ‘μ λ©μλ νΈμΆμ μ ν리μΌμ΄μ
κ²½κ³λ₯Ό λμ§ μλλ€. νΈμΆμμ μμ μ λͺ¨λ μ ν리μΌμ΄μ
λ΄μ μλ€. λν μ΄ λ©μλλ ν΄λΌμ΄μΈνΈκ° λͺ©νλ₯Ό λ¬μ±νλ λ° λμμ΄ λλ μ°μ°μ΄λ μνκ° μλλ€. μ΄ λ λλ©μΈ ν΄λμ€μ ν΄λΌμ΄μΈνΈλ ꡬ맀λ₯Ό λͺ©νλ‘ νλ CustomerController
λ€. μ΄ λͺ©νμ μ§μ μ μΈ κ΄λ ¨μ΄ μλ λ©€λ²λ customer.Purchase()
μ store.GetInventory()
μ΄λ κ² λλΏμ΄λ€.
π₯ λ¨μ ν μ€νΈμ κ³ μ νμ λ°λν μ¬κ³ β
λ³λνλ λΆλ³ μμ‘΄μ±μ μ μΈν λͺ¨λ μμ‘΄μ±μ λͺ© μ¬μ©μ κΆμ₯νλ©° μμ€ν λ΄ ν΅μ κ³Ό μμ€ν κ° ν΅μ μ ꡬλΆνμ§ μλλ€. κ·Έ κ²°κ³Ό, ν μ€νΈλ μ ν리μΌμ΄μ κ³Ό μΈλΆ μμ€ν κ°μ ν΅μ μ νμΈνλ κ²μ²λΌ ν΄λμ€ κ° ν΅μ λ νμΈνλ€.
λ°λνλ₯Ό λ°λΌ λͺ©μ 무λΆλ³νκ² μ¬μ©νλ©΄ μ’ μ’ κ΅¬ν μΈλΆ μ¬νμ κ²°ν©λΌ ν μ€νΈμ 리ν©ν°λ§ λ΄μ±μ΄ μκ² λλ€. 리ν©ν°λ§ λ΄μ±μ΄ μ νλλ©΄ ν μ€νΈλ κ°μΉκ° μμ΄μ§λ€.
κ³ μ νλ ν μ€νΈ κ°μ 곡μ νλ μμ‘΄μ±λ§ κ΅μ²΄νμκ³ νλ―λ‘ μ΄ λ¬Έμ μ ν¨μ¬ μ 리νλ€. κ·Έλ¬λ κ³ μ ν μμ μμ€ν κ° ν΅μ μ λν μ²λ¦¬μ μ΄μμ μ΄μ§λ μλ€. λ°λνλ§νΌμ μλμ§λ§, κ³ μ νλ λͺ© μ¬μ©μ μ§λμΉκ² μ₯λ €νλ€.
π λͺ¨λ νλ‘μΈμ€ μΈλΆ μμ‘΄μ±μ λͺ©μΌλ‘ ν΄μΌ νλ κ²μ μλλ€β
λ€μμ μμ‘΄μ± μ νμ΄λ€.
- 곡μ μμ‘΄μ±: ν μ€νΈ κ°μ 곡μ νλ μμ‘΄μ±
- νλ‘μΈμ€ μΈλΆ μμ‘΄μ±: νλ‘κ·Έλ¨μ μ€ν νλ‘μΈμ€ μΈμ λ€λ₯Έ νλ‘μΈμ€λ₯Ό μ μ νλ μμ‘΄μ±(μλ₯Ό λ€μ΄ λ°μ΄ν°λ² μ΄μ€, λ©μμ§ λ²μ€, SMTP μλΉμ€ λ±)
- λΉκ³΅κ° μμ‘΄μ±: 곡μ νμ§ μλ λͺ¨λ μμ‘΄μ±
κ³ μ νμμλ 곡μ μμ‘΄μ±μ νΌν κ²μ κΆκ³ νλ€. ν μ€νΈκ° μ€ν 컨ν μ€νΈλ₯Ό μλ‘ λ°©ν΄νκ³ , κ²°κ΅ λ³λ ¬ μ²λ¦¬λ₯Ό ν μ μκΈ° λλ¬Έμ΄λ€. ν μ€νΈλ₯Ό λ³λ ¬μ , μμ°¨μ λλ μμμ μμλ‘ μ€νν μ μλ κ²μ ν μ€νΈ 격리λΌκ³ λΆλ₯Έλ€. μ΄λ¬ν μμ‘΄μ±μ ν μ€νΈ λμ, μ¦ λͺ©κ³Ό μ€ν μΌλ‘ κ΅μ²΄νλ κ²μ΄λ€.
κ·Έλ¬λ λͺ¨λ νλ‘μΈμ€ μΈλΆ μμ‘΄μ±μ λͺ©μΌλ‘ ν΄μΌ νλ κ²μ μλλ€. νλ‘μΈμ€ μΈλΆ μμ‘΄μ±μ΄ μ ν리μΌμ΄μ μ ν΅ν΄μλ§ μ κ·Όν μ μμΌλ©΄, μ΄λ¬ν μμ‘΄μ±κ³Όμ ν΅μ μ μμ€ν μμ μλ³ν μ μλ λμμ΄ μλλ€. μ€μ λ‘ μΈλΆμμ κ΄μ°°ν μ μλ νλ‘μΈμ€ μΈλΆ μμ‘΄μ±μ μ ν리μΌμ΄μ μ μΌλΆλ‘ μμ©νλ€.
μμ ν ν΅μ κΆμ κ°μ§ νλ‘μΈμ€ μΈλΆ μμ‘΄μ±μ λͺ©μ μ¬μ©νλ©΄ κΉ¨μ§κΈ° μ¬μ΄ ν μ€νΈλ‘ μ΄μ΄μ§λ€. λ°μ΄ν°λ² μ΄μ€μμ ν μ΄λΈμ λΆν νκ±°λ μ μ₯ νλ‘μμ μμ 맀κ°λ³μ νμ μ λ³κ²½ν λλ§λ€ ν μ€νΈκ° λΉ¨κ°μμ΄ λλ κ²μ μ무λ μνμ§ μλλ€. λ°μ΄ν°λ² μ΄μ€μ μ ν리μΌμ΄μ μ νλμ μμ€ν μΌλ‘ μ·¨κΈν΄μΌ νλ€.
π λͺ©μ μ¬μ©ν λμ κ²μ¦β
μ’ μ’ λͺ©μ΄ λμμ κ²μ¦νλ€κ³ νλ€. νμ§λ§ λλΆλΆμ κ²½μ° κ·Έλ μ§ μλ€. λͺ©νλ₯Ό λ¬μ±νκ³ μ κ° κ°λ³ ν΄λμ€κ° μ΄μ ν΄λμ€μ μν΅νλ λ°©μμ μλ³ν μ μλ λμκ³Όλ μλ¬΄λ° κ΄κ³κ° μλ€. (μ΄λ ꡬν μΈλΆ μ¬νμ΄λ€.)
λͺ©μ μ ν리μΌμ΄μ μ κ²½κ³λ₯Ό λλλλ μνΈ μμ©μ κ²μ¦ν λμ μ΄λ¬ν μνΈ μμ©μ λΆμμ©μ΄ μΈλΆ νκ²½μμ λ³΄μΌ λλ§ λμκ³Ό κ΄λ ¨μ΄ μλ€.