qileilove

blog已经转移至github,大家请访问 http://qaseven.github.io/

测试驱动开发TDD(2)

今天的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。
  上一篇还有个遗留的问题。我把它记在小本上。
相关文章:

posted on 2014-01-20 10:09 顺其自然EVO 阅读(201) 评论(0)  编辑  收藏 所属分类: 测试学习专栏


只有注册用户登录后才能发表评论。


网站导航:
 
<2014年1月>
2930311234
567891011
12131415161718
19202122232425
2627282930311
2345678

导航

统计

常用链接

留言簿(55)

随笔分类

随笔档案

文章分类

文章档案

搜索

最新评论

阅读排行榜

评论排行榜