问题
无论运行多少个测试,从我的脚本和 Cobertura 创建的代码覆盖率报告似乎都会给我相同的覆盖率。如果我运行所有测试或不运行任何测试,Jenkins 的 Cobertura 插件总是向我显示相同的图表。
问题描述
我是 Jenkins、Cobertura 和整个代码覆盖率的初学者。我有一个简单的 Java 项目(请参见下面的代码:程序),它在 TestNG 中运行一些简单的测试(请参见下面的代码:测试)。一些脚本正在处理编译和测试(请参阅下面的代码:脚本),在 Jenkins 的构建:执行 Shell 中提供给 Jenkins 的脚本按以下顺序:
- clean.sh
- 编译.sh
- 测试覆盖率.sh
- (test.sh)
现在,在 test-coverage.sh 中,Cobertura 检测程序的源代码(代码:程序 部分),创建一个 coverage.xml 应该(并且看起来确实)包含运行测试后我的代码的覆盖范围。
但是,如果我应该删除一个测试(或全部,这并不重要)并再次运行它,coverage.xml 似乎会提供与我运行所有测试相同的统计信息测试完好无损。
我做错了什么?我误解了这个概念吗?我还应该检测我的测试类吗?
代码:程序
该程序是 FizzBuzz 数学游戏的自动化、简单版本,每个玩家轮流计数,用所述均匀面值替换每个数字分母为另一个单词(例如“Fizz”,但在本例中为“Cowabunga”)。
main.java
public class main {
public static void main(String[] args){
StartGame game = new StartGame();
game.start();
}
}
StartGame.java
public class StartGame {
public static void start() {
GameRules gr = new GameRules(3,5);
gr.startGame();
}
}
GameRules.java
public class GameRules {
private int currentNumber;
private int currentPlayer;
private int playUntilThisNumber;
private int dividerToSkip;
private List<Player> playerlist;
public GameRules(int numberOfPlayers, int divider){
playerlist = new ArrayList<Player>();
currentNumber=1;
currentPlayer=3;
playUntilThisNumber = 100;
dividerToSkip = divider;
fillPlayerList(numberOfPlayers);
}
public int getCurrentNumber() {
return currentNumber;
}
public void increaseCurrentNumber() {
this.currentNumber++;
}
private void fillPlayerList(int numberOfPlayers){
for(int i = 0 ; i < numberOfPlayers ; i++){
Player p = new Player(this, i+1,dividerToSkip);
playerlist.add(p);
}
}
public void startGame(){
while(currentNumber<=playUntilThisNumber){
System.out.println(playerlist.get(currentPlayer++%playerlist.size()).play());
}
}
}
Player.java
public class Player {
private GameRules rules;
private int dividerToSkip;
private int playerNumber;
public Player(GameRules r, int playerNr, int divider){
rules = r;
dividerToSkip = divider;
playerNumber = playerNr;
}
public String play() {
String s;
if(rules.getCurrentNumber()%dividerToSkip==0){
s = "Player " + playerNumber + ": Cowabunga";
}
else {
s = "Player " + playerNumber + ": " +rules.getCurrentNumber();
}
rules.increaseCurrentNumber();
return s;
}
}
代码:测试
GameRuleTest.java
public class GameRulesTest {
private int nrOfPLayers;
private int divider;
private GameRules gr;
@BeforeMethod
public void setUp(){
nrOfPLayers = 35;
divider = 13;
gr = new GameRules(nrOfPLayers,divider);
}
@Test
public void testCurrentNumber(){
assert (gr.getCurrentNumber()>0);
}
@Test
public void testIncreaseCurrentNumber() {
int cn = gr.getCurrentNumber();
gr.increaseCurrentNumber();
assert(gr.getCurrentNumber()==cn+1);
}
}
PlayerTest.java
public class PlayerTest {
private int nrOfPLayers;
private int divider;
private GameRules gr;
private Player player;
@BeforeMethod
public void setUp(){
nrOfPLayers = 35;
divider = 13;
gr = new GameRules(nrOfPLayers,divider);
player = new Player(gr,100,divider);
}
@Test
public void testReturnString() {
String res = player.play();
assert "Player 100: 1".equals(res) : "Expected correct return string, got " + res;
assertEquals("Player 100: 1", res);
}
}
性能测试.java
public class PerformanceTest {
private StartGame game;
@BeforeMethod
public void setUp(){
game = new StartGame();
}
@Test
public void testExecutionTime(){
long startTime = ManagementFactory.getThreadMXBean().getCurrentThreadCpuTime();
game.start();
long finnishTime = ManagementFactory.getThreadMXBean().getCurrentThreadCpuTime();
System.out.println(startTime);
System.out.println(finnishTime);
assert (finnishTime-startTime<0.0000005);
}
}
代码:脚本
clean.sh
#!/bin/bash
find . -name '*.class' | xargs rm
rm -rf instrumented cobertura.ser cobertura_report test-output
编译.sh
#!/bin/bash
javac src/MyProject/*.java
javac -cp src/:testng-6.8/testng-6.8.jar test/*.java
测试.sh
#!/bin/bash
java -cp testng-6.8/testng-6.8.jar:test:src org.testng.TestNG -verbose 2 test/test.xml
测试覆盖率.sh
#!/bin/bash
COBERTURA=cobertura-2.0.3
INSTRUMENTED=instrumented
REPORTDIR=cobertura_report
mkdir -p $INSTRUMENTED
mkdir -p $REPORTDIR
# Instrument the classes that we want to check coverage on
$COBERTURA/cobertura-instrument.sh src/MyProject/*.class --destination $INSTRUMENTED || exit
# Run the tests
java -cp $COBERTURA/$COBERTURA.jar:$INSTRUMENTED:testng-6.8/testng-6.8.jar:test org.testng.TestNG -verbose 2 test/test.xml
# Generate report
$COBERTURA/cobertura-report.sh --format xml --destination $REPORTDIR src
# Check coverage
#$COBERTURA/cobertura-check.sh --branch 0
测试.xml
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<suite name="MindGameSuite" verbose="1" >
<test name="MindGame" >
<classes>
<class name="PlayerTest" />
<class name="GameRulesTest" />
<class name="PerformanceTest" />
</classes>
</test>
</suite>
coverage.xml(生成时)
<?xml version="1.0"?>
<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-04.dtd">
<coverage line-rate="0.8888888888888888" branch-rate="1.0" lines-covered="32" lines-valid="36" branches-covered="6" branches-valid="6" complexity="1.3333333333333333" version="2.0.3" timestamp="1412942052582">
<sources>
<source>src</source>
</sources>
<packages>
<package name="MyProject" line-rate="0.8888888888888888" branch-rate="1.0" complexity="1.3333333333333333">
<classes>
<class name="MyProject.GameRules" filename="MyProject/GameRules.java" line-rate="1.0" branch-rate="1.0" complexity="1.4">
<methods>
<method name="<init>" signature="(II)V" line-rate="1.0" branch-rate="1.0">
<lines>
<line number="23" hits="1" branch="false"/>
<line number="24" hits="1" branch="false"/>
<line number="25" hits="1" branch="false"/>
<line number="26" hits="1" branch="false"/>
<line number="27" hits="1" branch="false"/>
<line number="28" hits="1" branch="false"/>
<line number="29" hits="1" branch="false"/>
<line number="30" hits="1" branch="false"/>
</lines>
</method>
<method name="fillPlayerList" signature="(I)V" line-rate="1.0" branch-rate="1.0">
<lines>
<line number="50" hits="4" branch="true" condition-coverage="100% (2/2)">
<conditions>
<condition number="0" type="jump" coverage="100%"/>
</conditions>
</line>
<line number="51" hits="3" branch="false"/>
<line number="52" hits="3" branch="false"/>
<line number="54" hits="1" branch="false"/>
</lines>
</method>
<method name="getCurrentNumber" signature="()I" line-rate="1.0" branch-rate="1.0">
<lines>
<line number="36" hits="180" branch="false"/>
</lines>
</method>
<method name="increaseCurrentNumber" signature="()V" line-rate="1.0" branch-rate="1.0">
<lines>
<line number="43" hits="100" branch="false"/>
<line number="44" hits="100" branch="false"/>
</lines>
</method>
<method name="startGame" signature="()V" line-rate="1.0" branch-rate="1.0">
<lines>
<line number="61" hits="101" branch="true" condition-coverage="100% (2/2)">
<conditions>
<condition number="0" type="jump" coverage="100%"/>
</conditions>
</line>
<line number="62" hits="100" branch="false"/>
<line number="64" hits="1" branch="false"/>
</lines>
</method>
</methods>
<lines>
<line number="23" hits="1" branch="false"/>
<line number="24" hits="1" branch="false"/>
<line number="25" hits="1" branch="false"/>
<line number="26" hits="1" branch="false"/>
<line number="27" hits="1" branch="false"/>
<line number="28" hits="1" branch="false"/>
<line number="29" hits="1" branch="false"/>
<line number="30" hits="1" branch="false"/>
<line number="36" hits="180" branch="false"/>
<line number="43" hits="100" branch="false"/>
<line number="44" hits="100" branch="false"/>
<line number="50" hits="4" branch="true" condition-coverage="100% (2/2)">
<conditions>
<condition number="0" type="jump" coverage="100%"/>
</conditions>
</line>
<line number="51" hits="3" branch="false"/>
<line number="52" hits="3" branch="false"/>
<line number="54" hits="1" branch="false"/>
<line number="61" hits="101" branch="true" condition-coverage="100% (2/2)">
<conditions>
<condition number="0" type="jump" coverage="100%"/>
</conditions>
</line>
<line number="62" hits="100" branch="false"/>
<line number="64" hits="1" branch="false"/>
</lines>
</class>
<class name="MyProject.Player" filename="MyProject/Player.java" line-rate="1.0" branch-rate="1.0" complexity="1.5">
<methods>
<method name="<init>" signature="(LMyProject/GameRules;II)V" line-rate="1.0" branch-rate="1.0">
<lines>
<line number="20" hits="3" branch="false"/>
<line number="21" hits="3" branch="false"/>
<line number="22" hits="3" branch="false"/>
<line number="23" hits="3" branch="false"/>
<line number="24" hits="3" branch="false"/>
</lines>
</method>
<method name="play" signature="()Ljava/lang/String;" line-rate="1.0" branch-rate="1.0">
<lines>
<line number="32" hits="100" branch="true" condition-coverage="100% (2/2)">
<conditions>
<condition number="0" type="jump" coverage="100%"/>
</conditions>
</line>
<line number="33" hits="20" branch="false"/>
<line number="36" hits="80" branch="false"/>
<line number="38" hits="100" branch="false"/>
<line number="39" hits="100" branch="false"/>
</lines>
</method>
</methods>
<lines>
<line number="20" hits="3" branch="false"/>
<line number="21" hits="3" branch="false"/>
<line number="22" hits="3" branch="false"/>
<line number="23" hits="3" branch="false"/>
<line number="24" hits="3" branch="false"/>
<line number="32" hits="100" branch="true" condition-coverage="100% (2/2)">
<conditions>
<condition number="0" type="jump" coverage="100%"/>
</conditions>
</line>
<line number="33" hits="20" branch="false"/>
<line number="36" hits="80" branch="false"/>
<line number="38" hits="100" branch="false"/>
<line number="39" hits="100" branch="false"/>
</lines>
</class>
<class name="MyProject.StartGame" filename="MyProject/StartGame.java" line-rate="1.0" branch-rate="1.0" complexity="1.0">
<methods>
<method name="<init>" signature="()V" line-rate="1.0" branch-rate="1.0">
<lines>
<line number="6" hits="1" branch="false"/>
</lines>
</method>
<method name="start" signature="()V" line-rate="1.0" branch-rate="1.0">
<lines>
<line number="8" hits="1" branch="false"/>
<line number="9" hits="1" branch="false"/>
<line number="10" hits="1" branch="false"/>
</lines>
</method>
</methods>
<lines>
<line number="6" hits="1" branch="false"/>
<line number="8" hits="1" branch="false"/>
<line number="9" hits="1" branch="false"/>
<line number="10" hits="1" branch="false"/>
</lines>
</class>
<class name="MyProject.main" filename="MyProject/main.java" line-rate="0.0" branch-rate="1.0" complexity="1.0">
<methods>
<method name="<init>" signature="()V" line-rate="0.0" branch-rate="1.0">
<lines>
<line number="7" hits="0" branch="false"/>
</lines>
</method>
<method name="main" signature="([Ljava/lang/String;)V" line-rate="0.0" branch-rate="1.0">
<lines>
<line number="14" hits="0" branch="false"/>
<line number="15" hits="0" branch="false"/>
<line number="16" hits="0" branch="false"/>
</lines>
</method>
</methods>
<lines>
<line number="7" hits="0" branch="false"/>
<line number="14" hits="0" branch="false"/>
<line number="15" hits="0" branch="false"/>
<line number="16" hits="0" branch="false"/>
</lines>
</class>
</classes>
</package>
</packages>
</coverage>
最佳答案
谜团解开了。
检测类覆盖测试类完成的所有阅读。这意味着来自这些的任何调用都将导致(如果有的话)覆盖。 在本例中,我运行了几个单元测试,在生成代码后,我编写了 PerformanceTest.java 只是为了尝试更高级别的测试。
现在的问题是性能测试运行程序来计算执行时间。这意味着在此测试期间几乎运行了程序的整个代码,这使得单元测试变得过时,并且单元测试应该涵盖的内容无论如何都被涵盖了。
总结
使用较小的测试,例如单元测试或模块测试来查看代码覆盖率。当要查看一行代码是否运行时,较高级别的测试会使大多数较小的测试变得过时。
关于java - 无论测试扩展如何,Jenkins 中的 Cobertura 代码覆盖率报告似乎都会提供相同的统计数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26296185/