测试驱动开发TDD(2)

Joy ·
更新时间:2024-11-11
· 566 次阅读

  的TDD练习又开始了。回头看看上一次留下的任务。   To-Do-List:   猜测数字   输入验证   生成答案   输入次数   输出猜测结果   我们把输入验证和随机生成答案搞定。   新建ValidationTest文件。   分析需求:(1)不重复。(2)4位(3)数字。(4)不为空。   按照我们分析出来的4个明确点我们开始写CASE。   注意命名! [TestClass] public class ValidatorTest { private Validator validator; [TestInitialize] public void Init() { validator = new Validator(); } [TestMethod] public void should_return_input_must_be_four_digits_when_input_figures_digit_is_not_four_digits() { var input = "29546"; validator.Validate(input); var actual = validator.ErrorMsg; Assert.AreEqual("the input must be four digits.", actual); } [TestMethod] public void should_return_input_must_be_fully_digital_when_input_is_not_all_digital() { var input = "a4s5"; validator.Validate(input); var actual = validator.ErrorMsg; Assert.AreEqual("the input must be fully digital.", actual); } [TestMethod] public void should_return_input_can_not_be_empty_when_input_is_empty() { var input = ""; validator.Validate(input); var actual = validator.ErrorMsg; Assert.AreEqual("the input can't be empty.", actual); } [TestMethod] public void should_return_input_can_not_contain_duplicate_when_input_figures_contain_duplicate() { var input = "2259"; validator.Validate(input); var actual = validator.ErrorMsg; Assert.AreEqual("the input figures can't contain duplicate.", actual); } }

  按照第一篇的步骤。实现validator。争取让所有的CASE都过。 public class Validator { public string ErrorMsg { get; private set; } public bool Validate(string input) { if (string.IsNullOrEmpty(input)) { ErrorMsg = "the input can't be empty."; return false; } if (input.Length != 4) { ErrorMsg = "the input must be four digits."; return false; } var regex = new Regex(@"^[0-9]*$"); if (!regex.IsMatch(input)) { ErrorMsg = "the input must be fully digital."; return false; } if (input.Distinct().Count() != 4) { ErrorMsg = "the input figures can't contain duplicate."; return false; } return true; } } Run...

  一个CASE对应这一个IF。也可合并2个CASE。可以用"^d{4}$"去Cover"4位数字"。可以根据自己的情况去定。   小步前进不一定要用很小粒度去一步一步走。这样开发起来的速度可能很慢。依靠你自身的情况去决定这一小步到底应该有多大。正所谓"步子大了容易扯到蛋,步子小了前进太慢"。只要找到合适自己的步子。才会走的更好。   这么多IF看起来很蛋疼。有测试。可以放心大胆的重构。把每个IF抽出一个方法。看起来要清晰一些。 public class Validator { public string ErrorMsg { get; private set; } public bool Validate(string input) { return IsEmpty(input) && IsFourdigits(input) && IsDigital(input) && IsRepeat(input); } private bool IsEmpty(string input) { if (!string.IsNullOrEmpty(input)) { return true; } ErrorMsg = "the input can't be empty."; return false; } private bool IsFourdigits(string input) { if (input.Length == 4) { return true; } ErrorMsg = "the input must be four digits."; return false; } private bool IsDigital(string input) { var regex = new Regex(@"^[0-9]*$"); if (regex.IsMatch(input)) { return true; } ErrorMsg = "the input must be fully digital."; return false; } private bool IsRepeat(string input) { if (input.Distinct().Count() == 4) { return true; } ErrorMsg = "the input figures can't contain duplicate."; return false; } }

  为了确保重构正确。重构之后一定要把所有的CASE在跑一遍,确定所有的都PASS。   To-Do-List:   猜测数字   输入验证   生成答案   输入次数   输出猜测结果   验证搞定了。我们来整整随机数。   分析需求:产品代码需要一个随机生成的答案。(1)不重复。(2)4位(3)数字。   这里有个问题:大家都知道随机数是个概率的问题。因为每次生成的数字都不一样。看看之前Guesser类的代码。 public class Guesser { private const string AnswerNumber = "2975"; public string Guess(string inputNumber) { var aCount = 0; var bCount = 0; for (var index = 0; index < AnswerNumber.Length; index++) { if (AnswerNumber[index]==inputNumber[index]) { aCount++; continue; } if (AnswerNumber.Contains(inputNumber[index].ToString())) { bCount++; } } return string.Format("{0}a{1}b", aCount, bCount); } }   这里我们如果把private const string AnswerNumber = "2975";改为随机的话,那Guesser类测试的结果是不能确定的。也是说测试依赖了一些可变的东西。比如:随机数、时间等等。   遇到这种情况应该怎么办呢?一种随机数是给产品代码用,我们可以MOCK另外一种"固定随机数"(但是要满足生成随机数的条件)来给测试用。   还是一样先写测试。 [TestClass] public class AnswerGeneratorTest { [TestMethod] public void should_pass_when_answer_generator_number_is_four_digits_and_fully_digital() { Regex regex = new Regex(@"^d{4}$"); var answerGenerator = new AnswerGenerator(); var actual = regex.IsMatch(answerGenerator.Generate()); Assert.AreEqual(true, actual); } [TestMethod] public void should_pass_when_answer_generator_number_do_not_repeat() { var answerGenerator = new AnswerGenerator(); var actual = answerGenerator.Generate().Distinct().Count() == 4; Assert.AreEqual(true, actual); } }    实现AnswerGenerator类让测试通过。    引用cao大一段代码稍加修改 public class AnswerGenerator { public string Generate() { var answerNumber = new StringBuilder(); Enumerable.Range(0, 9) .Select(x => new { v = x, k = Guid.NewGuid().ToString() }) .OrderBy(x => x.k) .Select(x => x.v) .Take(4).ToList() .ForEach(num => answerNumber.Append(num.ToString())); return answerNumber.ToString(); } }   运行测试。

  为了解决测试依赖可变的问题。定义IAnswerGenerator。让两种随机数类继承。 public interface IAnswerGenerator { string Generate(); } [csharp] view plaincopy public class AnswerGenerator : IAnswerGenerator { public string Generate() { var answerNumber = new StringBuilder(); Enumerable.Range(0, 9) .Select(x => new { v = x, k = Guid.NewGuid().ToString() }) .OrderBy(x => x.k) .Select(x => x.v) .Take(4).ToList() .ForEach(num => answerNumber.Append(num.ToString())); return answerNumber.ToString(); } } public class AnswerGeneratorForTest : IAnswerGenerator { public string Generate() { return "2975"; } }

  AnswerGenerator给产品代码用。AnswerGeneratorForTest给测试代码用。这样可以避免测试依赖可变的问题。   相应的给Guesser类以及测试代码做个修改。 public class Guesser { public string AnswerNumber { get; private set; } public Guesser(IAnswerGenerator generator) { AnswerNumber = generator.Generate(); } public string Guess(string inputNumber) { ... } } [TestClass] public class GuesserTest { private Guesser guesser; [TestInitialize] public void Init() { guesser = new Guesser(new AnswerGeneratorForTest()); } ... }   这样我在测试代码当中会给一个不可变的随机数。AnswerGeneratorForTest。所以之前的Guesser测试代码也不会因为每次的随机数不一样导致挂掉。   产品代码呢?直接丢AnswerGenerator进去妥妥地。   To-Do-List:   猜测数字   输入验证   生成答案   输入次数   输出猜测结果   OK。的收获。   (1)小步前进:依靠自身情况决定“小步”应该有多大。   (2)重构:之前的测试是我们重构的保障。   (3)测试依赖:测试不应该依赖于一些可变的东西。   都到这了,有没有点TDD的感觉。知道TDD的步骤了吗?   (1)新增一个测试。   (2)运行所有的测试程序并失败。   (3)做一些小小的改动。   (4)运行所有的测试,并且全部通过。   (5)重构代码以消除重复设计,优化设计。   (6)重复上面的工作。实现1~5小范围迭代。直到满足的Story。   上一篇还有个遗留的问题。我把它记在小本上。



测试驱动 测试

需要 登录 后方可回复, 如果你还没有账号请 注册新账号
相关文章